Skip to content

Update layout for projects#1305

Open
annavik wants to merge 11 commits into
mainfrom
layout/projects
Open

Update layout for projects#1305
annavik wants to merge 11 commits into
mainfrom
layout/projects

Conversation

@annavik
Copy link
Copy Markdown
Member

@annavik annavik commented May 14, 2026

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

  • Use a more compact gallery layout
  • Update page size from 20 -> 40 (I tested and it seems to still be quick to load)
  • Add a sort control, to make it easier to find relevant projects, for example the ones with recent activity
  • Make it possible to change sort order from the sort control (this update will affect other views as well)

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
Screenshot 2026-05-14 at 16 06 52

After
Screenshot 2026-05-14 at 16 06 00

Summary by CodeRabbit

  • New Features

    • Projects listing gains client-side sorting by name and recent-activity fields (recent captures, identifications, jobs).
  • UI Improvements

    • Enhanced sort control with clearer icons, default sort orders per column, and wired project sort UI.
    • Projects gallery and page header tweaks; Jobs header order adjusted; Team page default sort changed; dialog text simplified.
    • New UI labels for sorting and create actions.
  • Bug Fixes

    • Removed trailing space from Export button label.
  • Chores

    • Updated UI dependency versions.

Review Change Stack

@netlify
Copy link
Copy Markdown

netlify Bot commented May 14, 2026

Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit 8721f93
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6a0fda23a6cf83000894eefb
😎 Deploy Preview https://deploy-preview-1305--antenna-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 60 (🔴 down 5 from production)
Accessibility: 81 (🔴 down 8 from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 92 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 14, 2026

Deploy Preview for antenna-ssec ready!

Name Link
🔨 Latest commit 8721f93
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/6a0fda2345071800088da217
😎 Deploy Preview https://deploy-preview-1305--antenna-ssec.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@annavik annavik requested a review from mihow May 14, 2026 14:18
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Sorting Feature and UI Cleanup

Layer / File(s) Summary
Translation strings for sorting
ui/src/utils/language.ts
Added CHANGE_SORT_ORDER, CREATE_NEW, SORT_RECENT_IDENTIFICATIONS, SORT_RECENT_JOBS, and SORT_RECENT_OBSERVATIONS enum members and English translations.
SortControl component and table types
ui/src/design-system/components/sort-control.tsx, ui/src/design-system/components/table/types.ts
Refactored SortControl with changeSortField/changeSortOrder, updated icon imports, conditional trigger/order UI, and added optional defaultSortOrder to column types.
Projects page sorting integration
ui/src/pages/projects/projects.tsx
Imported and rendered SortControl, added SORT_FIELDS and useSort usage, passed sort into useProjects, and fixed pagination to perPage: 40.
Backend API sorting support
ami/main/api/views.py
Extended ProjectViewSet.ordering_fields to include name and recent-activity keys; conditionally annotate queryset with Max and Subquery results for last_capture_timestamp, last_occurrence_updated_at, and last_job_updated_at.
DB index and migration for recent-activity sorting
ami/main/migrations/0085_project_activity_sort_indexes.py, ami/main/models.py
Added non-atomic migration that creates occur_proj_updated_desc_idx concurrently and added matching index entry to Occurrence.Meta.indexes.
Team, Jobs, Gallery, Project dialog, and misc UI changes
ui/package.json, ui/src/pages/jobs/jobs.tsx, ui/src/pages/occurrences/occurrences.tsx, ui/src/pages/project-details/new-project-dialog.tsx, ui/src/pages/project/team/team-columns.tsx, ui/src/pages/project/team/team.tsx, ui/src/pages/projects/project-gallery.tsx
Bumped nova-ui-kit; moved NewJobDialog before SortControl; trimmed export label spacing; inlined NewProjectDialog translation; removed sortField: 'name' from team column and changed Team default sort to created_at; removed explicit Gallery layout props.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • RolnickLab/antenna#1099: Both PRs modify the Team page's sorting configuration in team.tsx and adjust sort field handling in team-columns.tsx.

Poem

🐰 I hopped through code with eager paws,
Sorting fields and fixing flaws,
Names and dates now lead the line,
Projects, teams — arranged just fine,
A little hop, a tidy UI applause ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Update layout for projects' accurately summarizes the main change: UI/layout improvements to the projects page, including gallery layout, pagination, and sort control features.
Description check ✅ Passed The PR description covers all major template sections: a clear summary, bulleted list of changes, deploy notes, and before/after screenshots. All essential information is present.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch layout/projects

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between aeb57c1 and 0ac2147.

⛔ Files ignored due to path filters (1)
  • ui/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (11)
  • ami/main/api/views.py
  • ui/package.json
  • ui/src/design-system/components/sort-control.tsx
  • ui/src/pages/jobs/jobs.tsx
  • ui/src/pages/occurrences/occurrences.tsx
  • ui/src/pages/project-details/new-project-dialog.tsx
  • ui/src/pages/project/team/team-columns.tsx
  • ui/src/pages/project/team/team.tsx
  • ui/src/pages/projects/project-gallery.tsx
  • ui/src/pages/projects/projects.tsx
  • ui/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

Comment thread ami/main/api/views.py Outdated
serializer_class = ProjectSerializer
pagination_class = ProjectPagination
permission_classes = [ObjectPermission]
ordering_fields = ["name", "created_at", "updated_at"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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 -B10

Repository: RolnickLab/antenna

Length of output: 44


🏁 Script executed:

# Find Project model definition
rg -n 'class Project' --type=py

Repository: RolnickLab/antenna

Length of output: 1625


🏁 Script executed:

# Check if models files exist in ami/main
find ami/main -name '*models*' -type f

Repository: 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 -50

Repository: 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 -20

Repository: 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 -30

Repository: 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 -5

Repository: 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 -20

Repository: 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.

mihow and others added 5 commits May 20, 2026 19:00
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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac2147 and 8721f93.

📒 Files selected for processing (7)
  • ami/main/api/views.py
  • ami/main/migrations/0085_project_activity_sort_indexes.py
  • ami/main/models.py
  • ui/src/design-system/components/sort-control.tsx
  • ui/src/design-system/components/table/types.ts
  • ui/src/pages/projects/projects.tsx
  • ui/src/utils/language.ts

Comment on lines +23 to +34
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"),
),
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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".

@mihow
Copy link
Copy Markdown
Collaborator

mihow commented May 22, 2026

@annavik thanks for expanding the projects view! I responded to the feedback from co-pilot and I added some more sort options!

image

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants