Update layout for projects#1305
Conversation
✅ Deploy Preview for antenna-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for antenna-ssec ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughAdds client-side sort controls and translations, integrates sorting into Projects page, extends ProjectViewSet to support name and recent-activity ordering via conditional queryset annotations, and adds a concurrent DB index plus model entry to support recent-occurrence sorting. ChangesSorting Feature and UI Cleanup
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ami/main/api/views.py`:
- Line 158: The Project model's name field is used in ordering (ordering_fields
includes "name") but lacks a DB index; update the Project model by adding
db_index=True to the name field declaration (Project.name) or alternatively
declare an Index on "name" inside the Project.Meta.indexes so sorts by name use
the index; migrate the DB after change to apply the new index.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 188d3959-ae86-4161-a080-ca9ff08e9603
⛔ Files ignored due to path filters (1)
ui/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (11)
ami/main/api/views.pyui/package.jsonui/src/design-system/components/sort-control.tsxui/src/pages/jobs/jobs.tsxui/src/pages/occurrences/occurrences.tsxui/src/pages/project-details/new-project-dialog.tsxui/src/pages/project/team/team-columns.tsxui/src/pages/project/team/team.tsxui/src/pages/projects/project-gallery.tsxui/src/pages/projects/projects.tsxui/src/utils/language.ts
💤 Files with no reviewable changes (2)
- ui/src/pages/project/team/team-columns.tsx
- ui/src/pages/projects/project-gallery.tsx
| serializer_class = ProjectSerializer | ||
| pagination_class = ProjectPagination | ||
| permission_classes = [ObjectPermission] | ||
| ordering_fields = ["name", "created_at", "updated_at"] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if Project model's name field has a database index
rg -nP --type=py -C5 'class Project\(' --glob '**/models*.py' | rg -P '(db_index|Index|indexes)' -A10 -B10Repository: RolnickLab/antenna
Length of output: 44
🏁 Script executed:
# Find Project model definition
rg -n 'class Project' --type=pyRepository: RolnickLab/antenna
Length of output: 1625
🏁 Script executed:
# Check if models files exist in ami/main
find ami/main -name '*models*' -type fRepository: RolnickLab/antenna
Length of output: 81
🏁 Script executed:
# Get context around line 158 in views.py to understand the model being used
cat -n ami/main/api/views.py | sed -n '150,170p'Repository: RolnickLab/antenna
Length of output: 1041
🏁 Script executed:
# Read the Project model definition
cat -n ami/main/models.py | sed -n '266,350p'Repository: RolnickLab/antenna
Length of output: 3554
🏁 Script executed:
# Also search for Meta class and any indexes in Project model
rg -n 'class Project\(|name.*Field|db_index|class Meta' ami/main/models.py | head -50Repository: RolnickLab/antenna
Length of output: 1559
🏁 Script executed:
# Read lines around 485 to check if Meta class is part of Project model
cat -n ami/main/models.py | sed -n '470,495p'Repository: RolnickLab/antenna
Length of output: 1354
🏁 Script executed:
# Read the complete Meta class for Project model
cat -n ami/main/models.py | sed -n '485,550p'Repository: RolnickLab/antenna
Length of output: 4529
🏁 Script executed:
# Continue reading the Meta class to see if indexes are defined
cat -n ami/main/models.py | sed -n '550,570p'Repository: RolnickLab/antenna
Length of output: 770
🏁 Script executed:
# Final confirmation: search for any indexes definition in Project model
rg -n 'indexes\s*=' ami/main/models.py | head -20Repository: RolnickLab/antenna
Length of output: 307
🏁 Script executed:
# Check which models have indexes defined (context around those lines)
rg -n -B10 'indexes\s*=' ami/main/models.py | grep -E '(^[0-9]+-class |indexes)' | head -30Repository: RolnickLab/antenna
Length of output: 307
🏁 Script executed:
# Check if there are any migrations that add an index to Project.name
find . -name '*migrations*' -type d | head -5Repository: RolnickLab/antenna
Length of output: 191
🏁 Script executed:
# Search for any migration files that might add index to name field
rg -n 'name.*index|AddIndex.*name' --type=py -i | grep -i project | head -20Repository: RolnickLab/antenna
Length of output: 44
Add database index to Project.name field for sort performance.
The name field in the Project model currently lacks a database index, but is included in ordering_fields, enabling API sorting by project name. As the number of projects grows, queries sorted by name will scan the full table without an index. Add db_index=True to the name field in ami/main/models.py line 269 or define an index in the Project model's Meta class.
🧰 Tools
🪛 Ruff (0.15.12)
[warning] 158-158: Mutable default value for class attribute
(RUF012)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ami/main/api/views.py` at line 158, The Project model's name field is used in
ordering (ordering_fields includes "name") but lacks a DB index; update the
Project model by adding db_index=True to the name field declaration
(Project.name) or alternatively declare an Index on "name" inside the
Project.Meta.indexes so sorts by name use the index; migrate the DB after change
to apply the new index.
Backs the project "recent activity" sort options. SourceImage is large in production (tens of millions of rows), so the indexes are built CONCURRENTLY in a non-atomic migration that clears statement_timeout for the build. Co-Authored-By: Claude <noreply@anthropic.com>
Add ordering by most recent capture timestamp, occurrence update and job update. The aggregate fields are annotated as correlated subqueries only when that ordering is requested, so the default project list stays cheap. Co-Authored-By: Claude <noreply@anthropic.com>
A column can declare a defaultSortOrder that is applied when the field is first selected, so date-like fields (e.g. "Recent ...") open newest-first. Co-Authored-By: Claude <noreply@anthropic.com>
Adds "Recent observations", "Recent identifications" and "Recent jobs" sort options, each defaulting to newest-first. Co-Authored-By: Claude <noreply@anthropic.com>
Use Max("deployments__last_capture_timestamp") for the last_capture_timestamp
ordering instead of a correlated subquery over SourceImage. The per-deployment
timestamp is already denormalized, so this avoids scanning the multi-million
row SourceImage table and matches the live max value for every project.
With the subquery gone, the dedicated SourceImage (project, -timestamp) index
is no longer needed, so drop it from the model and migration 0085. The
occurrence index stays, since recent identifications still uses a subquery.
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ami/main/migrations/0085_project_activity_sort_indexes.py`:
- Around line 23-34: The migration sets a session-wide statement_timeout to 0
but never resets it, so later non-atomic migrations inherit a disabled timeout;
add a trailing migrations.RunSQL after the AddIndexConcurrently that executes
"SET statement_timeout = DEFAULT;" (use migrations.RunSQL.noop for its
reverse_sql) so the session timeout is restored after the concurrent index build
for the model "occurrence" and index "occur_proj_updated_desc_idx".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1741adf6-efd6-405b-9ff9-13383e050aad
📒 Files selected for processing (7)
ami/main/api/views.pyami/main/migrations/0085_project_activity_sort_indexes.pyami/main/models.pyui/src/design-system/components/sort-control.tsxui/src/design-system/components/table/types.tsui/src/pages/projects/projects.tsxui/src/utils/language.ts
| operations = [ | ||
| # Runtime SET overrides the startup "-c statement_timeout" option and | ||
| # persists for the rest of this (non-atomic) migration's connection. | ||
| migrations.RunSQL( | ||
| sql="SET statement_timeout = 0;", | ||
| reverse_sql=migrations.RunSQL.noop, | ||
| ), | ||
| AddIndexConcurrently( | ||
| model_name="occurrence", | ||
| index=models.Index(fields=["project", "-updated_at"], name="occur_proj_updated_desc_idx"), | ||
| ), | ||
| ] |
There was a problem hiding this comment.
Reset statement_timeout after the concurrent index build.
SET statement_timeout = 0 is session-scoped, and this migration is non-atomic. Without a trailing reset, later migrations executed on the same connection inherit timeout=0, which silently disables that safeguard for the rest of the migrate run.
Suggested fix
operations = [
# Runtime SET overrides the startup "-c statement_timeout" option and
# persists for the rest of this (non-atomic) migration's connection.
migrations.RunSQL(
sql="SET statement_timeout = 0;",
reverse_sql=migrations.RunSQL.noop,
),
AddIndexConcurrently(
model_name="occurrence",
index=models.Index(fields=["project", "-updated_at"], name="occur_proj_updated_desc_idx"),
),
+ migrations.RunSQL(
+ sql="RESET statement_timeout;",
+ reverse_sql=migrations.RunSQL.noop,
+ ),
]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| operations = [ | |
| # Runtime SET overrides the startup "-c statement_timeout" option and | |
| # persists for the rest of this (non-atomic) migration's connection. | |
| migrations.RunSQL( | |
| sql="SET statement_timeout = 0;", | |
| reverse_sql=migrations.RunSQL.noop, | |
| ), | |
| AddIndexConcurrently( | |
| model_name="occurrence", | |
| index=models.Index(fields=["project", "-updated_at"], name="occur_proj_updated_desc_idx"), | |
| ), | |
| ] | |
| operations = [ | |
| # Runtime SET overrides the startup "-c statement_timeout" option and | |
| # persists for the rest of this (non-atomic) migration's connection. | |
| migrations.RunSQL( | |
| sql="SET statement_timeout = 0;", | |
| reverse_sql=migrations.RunSQL.noop, | |
| ), | |
| AddIndexConcurrently( | |
| model_name="occurrence", | |
| index=models.Index(fields=["project", "-updated_at"], name="occur_proj_updated_desc_idx"), | |
| ), | |
| migrations.RunSQL( | |
| sql="RESET statement_timeout;", | |
| reverse_sql=migrations.RunSQL.noop, | |
| ), | |
| ] |
🧰 Tools
🪛 Ruff (0.15.13)
[warning] 23-34: Mutable default value for class attribute
(RUF012)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ami/main/migrations/0085_project_activity_sort_indexes.py` around lines 23 -
34, The migration sets a session-wide statement_timeout to 0 but never resets
it, so later non-atomic migrations inherit a disabled timeout; add a trailing
migrations.RunSQL after the AddIndexConcurrently that executes "SET
statement_timeout = DEFAULT;" (use migrations.RunSQL.noop for its reverse_sql)
so the session timeout is restored after the concurrent index build for the
model "occurrence" and index "occur_proj_updated_desc_idx".
|
@annavik thanks for expanding the projects view! I responded to the feedback from co-pilot and I added some more sort options!
Still exploring names for the sort fields. I want to be able to see which projects had any recent occurrence activity (human or machined, created or updated). "Recent identifications" is all I can think of. |


The more projects we add, the more difficult the project page is to navigate. For most users, the main view will be a more compact list of projects they are a member of, but for super admins with access to all projects it can be a bit tricky. Also, in some cases, users might want to explore public projects they are not a member of. Also that list can get pretty long!
In this PR, we make some simple UI updates, to improve the situation a bit.
Summary of changes
Thoughts for future
A search feature would be nice, but it requires a few more backend updates and maybe not our highest prio. Also a more compact table view for projects could be interesting to explore...
Deploy notes
Sort projects by name required a tiny BE update.
Screenshots
Before

After

Summary by CodeRabbit
New Features
UI Improvements
Bug Fixes
Chores