Skip to content

Add live data to project summary#1296

Open
annavik wants to merge 10 commits intomainfrom
feat/project-overview
Open

Add live data to project summary#1296
annavik wants to merge 10 commits intomainfrom
feat/project-overview

Conversation

@annavik
Copy link
Copy Markdown
Member

@annavik annavik commented May 7, 2026

Summary

In this PR we include some live data in the project summary. This is to make the project dashboard feel a bit more alive and interactive. The information about top identifiers is new and not something we have been able to see before. Maybe this could be motivating for team members to see?

List of Changes

  • Setup new backend endpoint for top identifiers
  • Add overview section to project summary
  • Present latest occurrences in overview section
  • Present top identifiers in overview section
  • Present top taxa in overview section

Detailed Description

How to Test the Changes

@mihow could you help test and review the backend changes I included, since I'm no experts here?

Screenshots

Screenshot 2026-05-07 at 13 26 56

Next Steps

Include a stats section in the occurrence view where we can see total number of identifications, percentage of occurrences verified and maybe even human-model agreement rate?

Summary by CodeRabbit

Release Notes

  • New Features

    • Added project overview section displaying top user identifiers, latest occurrences, and most observed species
    • Integrated charts display within project summary view
    • Added "View All" links for navigation to detailed views
  • Style

    • Updated sidebar and error message styling for improved appearance
  • Chores

    • Added translation strings to support new features

@annavik annavik requested a review from mihow May 7, 2026 11:35
@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for antenna-preview ready!

Name Link
🔨 Latest commit 0f9f1c0
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/69fc810b9064910008cee8c3
😎 Deploy Preview https://deploy-preview-1296--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: 62 (🔴 down 3 from production)
Accessibility: 89 (no change 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 7, 2026

Deploy Preview for antenna-ssec ready!

Name Link
🔨 Latest commit 0f9f1c0
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/69fc810b7e04e700080a6f5a
😎 Deploy Preview https://deploy-preview-1296--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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Warning

Rate limit exceeded

@annavik has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 53 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ffc8318-24a3-4b7d-9ace-4ad4bf406c54

📥 Commits

Reviewing files that changed from the base of the PR and between fe1862e and 0f9f1c0.

📒 Files selected for processing (2)
  • ui/src/pages/project/summary/list-item.tsx
  • ui/src/pages/project/summary/summary.tsx
📝 Walkthrough

Walkthrough

This PR adds a project summary dashboard displaying top identifiers, latest occurrences, and most observed taxa. The backend provides a new REST endpoint for user identification rankings, while the frontend introduces three data hooks and a refactored Summary component with nested overview sections, a new ListItem component for consistent item rendering, and internationalization support.

Changes

Project Summary Dashboard

Layer / File(s) Summary
Backend API Contract
ami/main/api/serializers.py, ami/main/api/views.py, config/api_router.py
New UserIdentificationCountSerializer and UserIdentificationCountsView expose a GET endpoint at /users/identifications/top/ returning top 5 users ranked by identification count, optionally scoped to active project.
Frontend Data Hooks
ui/src/data-services/hooks/identifications/useTopIdentifiers.ts, ui/src/data-services/hooks/occurrences/useLatestOccurrences.ts, ui/src/data-services/hooks/species/useTopSpecies.ts
New hooks useTopIdentifiers, useLatestOccurrences, and useTopSpecies fetch respective ranked/paginated data with optional project filtering.
Summary UI Components
ui/src/pages/project/summary/list-item.tsx, ui/src/pages/project/summary/summary.tsx
New ListItem component renders items with image variant, title, text, and count. Summary refactored to include overview sections for latest occurrences, top identifiers, top taxa, and charts with loading/error/empty state handling.
Styling and Translations
ui/src/components/error-state/error-state.tsx, ui/src/pages/project/sidebar/sidebar.tsx, ui/src/utils/language.ts, ui/src/pages/species-details/species-details.tsx
ErrorState compact mode drops body-small class. Sidebar adds shrink-0, p-0, rounded-md. Translation enum STRING adds 15 new keys (VIEW_ALL, CHARTS, OVERVIEW, MOST_IDENTIFICATIONS, MOST_OBSERVED_TAXA, LATEST_OCCURRENCES, LOADING_DATA, etc.) with corresponding English strings. species-details uses translated VIEW_ALL.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

backend, next up!

Suggested reviewers

  • mihow

Poem

🐰 A dashboard sprouts with data so neat,
Top identifiers and species greet—
Occurrences bloom in sorted rows,
Translations flourish as the interface grows,
Dashboard dreams, now alive with glee!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add live data to project summary' directly and clearly summarizes the main objective: adding live data to the project summary/dashboard to make it more interactive.
Description check ✅ Passed The PR description includes Summary, List of Changes, Detailed Description, How to Test, and Screenshots. However, it lacks Related Issues, complete Deployment Notes, and an unchecked Checklist section as specified in the template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/project-overview

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

🧹 Nitpick comments (1)
ami/main/api/views.py (1)

1869-1898: ⚡ Quick win

Recommended: use Count(filter=Q(...)) and align serializer_class with the actual response shape.

Two adjacent improvements:

  • The aggregation works today because Django reuses the identifications join, but the canonical and safer pattern is Count("identifications", filter=Q(...)). It removes the asymmetry between the two branches (one uses distinct=True, the other doesn't), drops the redundant trailing .distinct(), and is robust against future query changes.
  • serializer_class = UserIdentificationCountSerializer is registered but the response is a hand-rolled {"project_id": ..., "top_identifiers": [...]} dict. drf-spectacular will document the inner item shape and miss the wrapper. Either build the response through a top-level serializer or declare it via @extend_schema(responses=...).
♻️ Proposed simplification of the queryset
-        # Start with user queryset
-        user_queryset = User.objects.all()
-
-        # Filter by project if provided, then annotate with count
-        if project:
-            user_queryset = (
-                user_queryset.filter(identifications__occurrence__project=project)
-                .annotate(identification_count=Count("identifications", distinct=True))
-                .distinct()
-            )
-        else:
-            user_queryset = user_queryset.annotate(identification_count=Count("identifications"))
-
-        # Get top 5 users, ordered by identification count (descending)
-        top_identifiers = user_queryset.filter(identification_count__gt=0).order_by("-identification_count")[:5]
+        count_filter = (
+            Q(identifications__occurrence__project=project) if project else Q()
+        )
+        top_identifiers = (
+            User.objects.annotate(
+                identification_count=Count("identifications", filter=count_filter, distinct=True)
+            )
+            .filter(identification_count__gt=0)
+            .order_by("-identification_count")[:5]
+        )
🤖 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` around lines 1869 - 1898, Refactor the aggregation in
the top user queryset to use Count with a filter argument
(Count("identifications", filter=Q(...))) for consistent and safer filtering
instead of the current conditional annotations and distinct calls. Additionally,
align the response with the declared serializer_class by either creating a
top-level serializer that reflects the full response shape including
"project_id" and "top_identifiers", or annotate the view with `@extend_schema` to
explicitly declare the custom response structure to keep documentation in sync.
🤖 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/serializers.py`:
- Around line 1709-1718: The UserIdentificationCountSerializer currently exposes
email (email = serializers.CharField()) which leaks PII; remove the email field
from UserIdentificationCountSerializer so it matches UserNestedSerializer (id,
name, image, details) and update any UI/data consumers to use name/image only,
or alternatively keep the field but gate its inclusion in the view logic (e.g.,
only add email to the serialized output when request.user has project manager
permissions) by returning a different serializer or conditionally adding the
email in the view that constructs the response.

In `@ami/main/api/views.py`:
- Around line 1855-1905: The UserIdentificationCountsView exposes user PII such
as emails to anonymous users due to the broad IsActiveStaffOrReadOnly permission
and also lacks the require_project=True guard, allowing costly global
aggregations without project filtering. To fix this, set require_project = True
on the view to enforce project filtering, adjust permission_classes to a
stricter class like IsAuthenticated or IsProjectMember to prevent anonymous
access, and remove the email field from the response or restrict it so only
staff users receive it. These changes should be applied within the
UserIdentificationCountsView class.

In `@ui/src/pages/project/summary/list-item.tsx`:
- Around line 36-54: The img elements in the Image and UserImage components
currently use className="object-cover" but lack sizing so object-fit has no
effect; update the <img> in both Image and UserImage (functions/components named
Image and UserImage) to include full-size classes (e.g., add w-full and h-full
alongside object-cover) so the image fills the 12x12 container and object-cover
works as intended.

In `@ui/src/pages/project/summary/summary.tsx`:
- Around line 107-136: The "Latest occurrences" links are using an ascending
ordering query param; update both Link usages that build URLs via
APP_ROUTES.OCCURRENCE_DETAILS (inside occurrences.map) and
APP_ROUTES.OCCURRENCES (the "View all" link) to request descending order by
prefixing the field with a minus (e.g. ordering=-first_appearance_timestamp) so
the pages show most-recent-first to match useLatestOccurrences and the section
title.
- Around line 179-180: The Link pointing to APP_ROUTES.OCCURRENCES({ projectId
}) incorrectly appends "?verified=-true" (a malformed query) — update that Link
to use a proper boolean query (e.g., "?verified=false") so the URL is correct
and not brittle; locate the Link component referencing APP_ROUTES.OCCURRENCES
and projectId in summary.tsx and replace the "?verified=-true" fragment with a
valid query string such as "?verified=false" (or generate the query via
URLSearchParams) to fix the bug.

---

Nitpick comments:
In `@ami/main/api/views.py`:
- Around line 1869-1898: Refactor the aggregation in the top user queryset to
use Count with a filter argument (Count("identifications", filter=Q(...))) for
consistent and safer filtering instead of the current conditional annotations
and distinct calls. Additionally, align the response with the declared
serializer_class by either creating a top-level serializer that reflects the
full response shape including "project_id" and "top_identifiers", or annotate
the view with `@extend_schema` to explicitly declare the custom response structure
to keep documentation in sync.
🪄 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: e06b3030-6423-436b-9cfb-81575abff194

📥 Commits

Reviewing files that changed from the base of the PR and between 183f487 and fe1862e.

📒 Files selected for processing (12)
  • ami/main/api/serializers.py
  • ami/main/api/views.py
  • config/api_router.py
  • ui/src/components/error-state/error-state.tsx
  • ui/src/data-services/hooks/identifications/useTopIdentifiers.ts
  • ui/src/data-services/hooks/occurrences/useLatestOccurrences.ts
  • ui/src/data-services/hooks/species/useTopSpecies.ts
  • ui/src/pages/project/sidebar/sidebar.tsx
  • ui/src/pages/project/summary/list-item.tsx
  • ui/src/pages/project/summary/summary.tsx
  • ui/src/pages/species-details/species-details.tsx
  • ui/src/utils/language.ts

Comment on lines +1709 to +1718
class UserIdentificationCountSerializer(serializers.Serializer):
"""
Serializer for user identification counts.
"""

id = serializers.IntegerField()
name = serializers.CharField()
email = serializers.CharField()
image = serializers.CharField(required=False, allow_null=True)
identification_count = serializers.IntegerField()
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 7, 2026

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

email exposed to all project members — PII/compliance risk.

The existing UserNestedSerializer (line 62) deliberately omits email, exposing only id, name, image, and details. The new UserIdentificationCountSerializer breaks this pattern by adding email = serializers.CharField(), which the frontend renders directly for any user who can view the project summary (PR screenshot confirms the email is displayed in the "Most identifications" panel).

Exposing another user's email address to co-members without their consent is a GDPR/CCPA concern. Consider removing email from the serializer and relying on name/image for the UI card, or at minimum restricting the field to project managers in the view.

🤖 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/serializers.py` around lines 1709 - 1718, The
UserIdentificationCountSerializer currently exposes email (email =
serializers.CharField()) which leaks PII; remove the email field from
UserIdentificationCountSerializer so it matches UserNestedSerializer (id, name,
image, details) and update any UI/data consumers to use name/image only, or
alternatively keep the field but gate its inclusion in the view logic (e.g.,
only add email to the serialized output when request.user has project manager
permissions) by returning a different serializer or conditionally adding the
email in the view that constructs the response.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes this is a good point, how do you think we should do with the main @mihow ...?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread ami/main/api/views.py
Comment on lines +1855 to +1905
class UserIdentificationCountsView(GenericAPIView, ProjectMixin):
"""
API endpoint that returns identification counts for top 5 users.
Optionally restricted to a specific project via project_id query parameter.
"""

permission_classes = [IsActiveStaffOrReadOnly]
serializer_class = UserIdentificationCountSerializer

@extend_schema(parameters=[project_id_doc_param])
def get(self, request):
"""
Return top 5 users by identification count, optionally filtered by project.
"""
project = self.get_active_project()

# Start with user queryset
user_queryset = User.objects.all()

# Filter by project if provided, then annotate with count
if project:
user_queryset = (
user_queryset.filter(identifications__occurrence__project=project)
.annotate(identification_count=Count("identifications", distinct=True))
.distinct()
)
else:
user_queryset = user_queryset.annotate(identification_count=Count("identifications"))

# Get top 5 users, ordered by identification count (descending)
top_identifiers = user_queryset.filter(identification_count__gt=0).order_by("-identification_count")[:5]

# Prepare serialized data
data = []
for user in top_identifiers:
data.append(
{
"id": user.id,
"name": user.name if user.name else None,
"email": user.email,
"image": user.image.url if user.image else None,
"identification_count": user.identification_count,
}
)

return Response(
{
"project_id": project.id if project else None,
"top_identifiers": data,
}
)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 7, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify what IsActiveStaffOrReadOnly grants to anonymous users.
ast-grep --pattern $'class IsActiveStaffOrReadOnly($_) {
  $$$
}'
rg -nP --type=py -C5 'class IsActiveStaffOrReadOnly\b'
# Also check whether other endpoints returning user PII apply stricter permissions:
rg -nP --type=py -C2 '\buser\.email\b|"email":\s*user\.email'

Repository: RolnickLab/antenna

Length of output: 9674


🏁 Script executed:

#!/bin/bash
# Get full IsActiveStaffOrReadOnly implementation
rg -nA20 'class IsActiveStaffOrReadOnly' ami/base/permissions.py

Repository: RolnickLab/antenna

Length of output: 876


🏁 Script executed:

#!/bin/bash
# Find SummaryView and check require_project setting
rg -nA30 'class SummaryView' ami/main/api/views.py | head -50

Repository: RolnickLab/antenna

Length of output: 1800


🏁 Script executed:

#!/bin/bash
# Show the full UserIdentificationCountsView implementation focusing on Count logic
sed -n '1855,1905p' ami/main/api/views.py | cat -n

Repository: RolnickLab/antenna

Length of output: 2312


🏁 Script executed:

#!/bin/bash
# Check if there are tests for UserIdentificationCountsView
rg -n 'UserIdentificationCountsView|user.*identification.*count' ami/main/tests.py ami/main/api/tests/ --type=py 2>/dev/null | head -20

Repository: RolnickLab/antenna

Length of output: 44


Major: PII exposure risk and missing require_project guard.

Two confirmed concerns on this endpoint:

  1. PII exposure (emails) under IsActiveStaffOrReadOnly. This permission allows read access to anonymous users (anyone can GET). The view returns email and name for the top 5 users, exposing personally identifiable information to unauthenticated callers. The neighbouring SummaryView uses the same permission but returns only aggregated counts—no user details.

  2. No require_project guard; unbounded global aggregation. SummaryView sets require_project = True with a comment: "Unfiltered summary aggregates are too expensive". This view's else branch runs User.objects.annotate(identification_count=Count("identifications")) without a project filter, triggering a full table scan on the Identification table. An anonymous caller can trigger this expensive operation.

Add require_project = True and consider tightening the permission class (e.g., IsAuthenticated or IsProjectMember) and/or omitting email from the response for non-staff callers.

🧰 Tools
🪛 Ruff (0.15.12)

[warning] 1861-1861: 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` around lines 1855 - 1905, The
UserIdentificationCountsView exposes user PII such as emails to anonymous users
due to the broad IsActiveStaffOrReadOnly permission and also lacks the
require_project=True guard, allowing costly global aggregations without project
filtering. To fix this, set require_project = True on the view to enforce
project filtering, adjust permission_classes to a stricter class like
IsAuthenticated or IsProjectMember to prevent anonymous access, and remove the
email field from the response or restrict it so only staff users receive it.
These changes should be applied within the UserIdentificationCountsView class.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For the optional project, I setup the endpoint so we could also use it for top identifiers across all projects in future, however this is not used yet. Do you think it will be too heavy @mihow ?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread ui/src/pages/project/summary/list-item.tsx
Comment thread ui/src/pages/project/summary/summary.tsx
Comment thread ui/src/pages/project/summary/summary.tsx Outdated
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.

1 participant