Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@Jayant2908
Copy link
Contributor

@Jayant2908 Jayant2908 commented Dec 18, 2025

Closes #2473
This PR adds end-to-end user activity tracking for organizations and exposes it on the Organization Analytics Dashboard as a new “User Behavior Analytics” section.

What’s included:-

1.)Core data model

  • Introduces a UserActivity model to store organization-scoped user actions (type, timestamp, optional metadata, etc.).

  • Adds the migration to create the table (and any required indexes/constraints as defined in the model).

2.)Activity capture infrastructure

  • Adds automatic activity logging through:

  • Middleware to track key page visits (e.g., organization dashboard visits).

  • Signal handlers to record model-driven events such as bug report creation and issue comment activity.

  • Registers the middleware and ensures signals are loaded via app initialization.

3.)Analytics + dashboard integration
Adds backend analytics aggregation for the last 30 days:

  • Active users count

  • Total activities

  • Engagement rate (activities per active user)

  • Activity breakdown by type

  • Weekly trend (daily counts for the last 7 days)

  • Top active users

-Peak Activity hours

  • Updates the dashboard template to render the new section and wires up a Chart.js weekly activity trend using json_script data injection.

Tests added

  1. Model tests: validates creating UserActivity, choice handling, and string representation.
  2. Signals tests: verifies that creating an Issue / adding a Comment generates corresponding UserActivity records with expected metadata.
  3. Middleware tests: confirms dashboard routes generate activity events while unrelated routes do not.
  4. Analytics tests: validates the analytics helper returns correct structure and non-empty computed metrics when activities exist.

Manual verification:- Created sample activity data in Django shell and confirmed:

  1. Weekly trend chart renders correctly
  2. Summary cards reflect expected counts
  3. No console errors after chart initialization

Screenshot of the Dashboard:-

Screenshot 2025-12-20 011612 Screenshot 2025-12-20 011625

Summary by CodeRabbit

  • New Features

    • User Behavior Analytics on organization dashboard (active users, engagement, breakdown, top contributors, weekly trends, peak hours; charts added).
  • Tracking

    • Dashboard visits plus bug-report and bug-comment events are now recorded as activity entries.
  • Privacy

    • IP addresses are anonymized before storage.
  • Administration

    • Read-only admin view for activity records.
  • Chores

    • Command to purge activity records older than 90 days.
  • Tests

    • Comprehensive tests for model, middleware, signals, analytics view, and cleanup command.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 18, 2025 19:00
@github-actions github-actions bot added files-changed: 14 PR changes 14 files migrations PR contains database migration files labels Dec 18, 2025
@github-actions
Copy link
Contributor

👋 Hi @Jayant2908!

This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:

  • The PR author
  • DonnieBLT
  • coderabbitai
  • copilot

Once a valid peer review is submitted, this check will pass automatically. Thank you!

@github-actions github-actions bot added the needs-peer-review PR needs peer review label Dec 18, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds user activity analytics: new UserActivity model + migration, read-only admin, activity-tracking middleware with IP anonymization and deduplication, signal handlers for issues/comments, analytics view/template additions, a 90-day cleanup management command, and corresponding tests and app init import.

Changes

Cohort / File(s) Change Summary
Data model & migration
website/models.py, website/migrations/0264_useractivity.py
Add UserActivity model (user FK, nullable organization FK, activity_type choices, timestamp, ip_address, user_agent, metadata JSON), verbose names, str, ordering by -timestamp, and indexes on (user,timestamp), (organization,activity_type), (activity_type,timestamp), (organization,timestamp).
Settings
blt/settings.py
Append "website.middleware.ActivityTrackingMiddleware" to Django MIDDLEWARE.
Request tracking middleware
website/middleware.py
New ActivityTrackingMiddleware and anonymize_ip() util; detects org/company dashboard paths, resolves organization from URL/session, deduplicates events (cache + DB window), anonymizes IP, truncates user-agent, and creates dashboard_visit UserActivity.
Signal handlers & app init
website/activity_signals.py, website/apps.py
New log_bug_report and log_bug_comment post_save receivers creating bug_report/bug_comment UserActivity; activity_signals imported in WebsiteConfig.ready().
Admin
website/admin.py
Register UserActivity via UserActivityAdmin (list_display, filters, search, readonly fields, date_hierarchy, ordering) and override has_add_permission / has_change_permission to prevent additions/edits.
Analytics view & template
website/views/company.py, website/templates/organization/dashboard/organization_analytics.html
Add get_user_behavior_analytics(self, organization) and set context["user_behavior"]; compute 30-day aggregates, top users, breakdown, weekly trend, peak hours; template adds User Behavior Analytics UI, JSON data injection and two new charts (weekly trend, peak hours).
Management command
website/management/commands/aggregate_user_analytics.py
New command deleting UserActivity records older than 90 days; reports deleted count and raises CommandError on DB errors.
Model, middleware & signals tests
website/tests/test_user_activity_model.py, website/tests/test_activity_middleware.py, website/tests/test_activity_signals.py
Tests for UserActivity fields/choices/str, middleware tracking/deduplication/IP anonymization, and signals creating activities for issues/comments.
View & command tests
website/tests/test_analytics_view.py, website/tests/test_management_commands.py
Tests for analytics view outputs/edge cases and management command cleanup behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Django
    participant Middleware as ActivityTrackingMiddleware
    participant View as OrgAnalyticsView
    participant Signals as ActivitySignalHandlers
    participant DB

    Note over Client,Middleware: Dashboard visit flow
    Client->>Django: HTTP GET /organization/{id}/dashboard
    Django->>Middleware: process_request(request)
    Middleware->>DB: query recent UserActivity (dedupe window)
    alt not duplicate & authenticated non-superuser
        Middleware->>DB: create UserActivity (dashboard_visit)
        DB-->>Middleware: created
    end
    Django->>View: view.get(request)
    View->>DB: aggregate UserActivity (30d / 7d queries)
    DB-->>View: aggregated metrics
    View-->>Client: render dashboard with analytics

    Note over Client,Signals: Issue/Comment creation flow
    Client->>Django: POST create Issue or Comment
    Django->>DB: save Issue/Comment
    DB-->>Django: saved instance
    Django->>Signals: post_save triggers
    Signals->>DB: create UserActivity (bug_report / bug_comment)
    DB-->>Signals: created
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.16% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main changes: user activity tracking infrastructure and a new organization analytics dashboard feature for user behavior.
Linked Issues check ✅ Passed The PR implements all core coding requirements from issue #2473: UserActivity model with migration and indexes [0264], middleware tracking [middleware.py, activity_signals.py], backend analytics aggregation [views/company.py], analytics template section [organization_analytics.html], management cleanup command [aggregate_user_analytics.py], and comprehensive test coverage with model/signal/middleware/analytics tests.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #2473 objectives: user activity tracking, organization-scoped analytics, IP anonymization, deduplication, dashboard visualization, and retention management. Settings middleware registration and app signal import are necessary supporting infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@github-actions
Copy link
Contributor

📊 Monthly Leaderboard

Hi @Jayant2908! Here's how you rank for December 2025:

Rank User PRs Reviews Comments Total
🥉 #3 @Pritz395 6 21 50 244
#4 @Jayant2908 4 36 13 210
#5 @sidd190 4 31 8 180

Leaderboard based on contributions in December 2025. Keep up the great work! 🚀

@Jayant2908 Jayant2908 marked this pull request as draft December 18, 2025 19:01
@github-actions github-actions bot added the pre-commit: passed Pre-commit checks passed label Dec 18, 2025
@github-actions github-actions bot added the tests: failed Django tests failed label Dec 18, 2025
Copy link
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 (8)
website/tests/test_user_activity_model.py (1)

7-39: Consider expanding test coverage for completeness.

The existing tests validate core functionality well. Consider adding tests for:

  • Timestamp auto-generation behavior
  • Default values for optional fields (ip_address, user_agent, metadata)
  • Organization-scoped string representation
  • Edge cases like activities without organization
website/tests/test_analytics_view.py (1)

10-42: Consider adding edge case and integration tests.

The current test validates the happy path well. Consider adding:

  • Test for organizations with no activities (verify empty states)
  • Test for activities older than 30 days (verify they're excluded)
  • Integration test that verifies context injection in the full view
  • Assertions on specific calculated values (e.g., engagement_rate formula)
website/management/commands/aggregate_user_analytics.py (1)

16-17: Consider making retention period configurable.

The 90-day retention period is hardcoded. Consider making it configurable via settings (e.g., settings.USER_ACTIVITY_RETENTION_DAYS) or a command argument to allow flexibility without code changes.

🔎 Example implementation:
from django.conf import settings

def handle(self, *args, **options):
    """Clean up old UserActivity records."""
    try:
        # Use settings value with 90 as default
        retention_days = getattr(settings, 'USER_ACTIVITY_RETENTION_DAYS', 90)
        cutoff_date = timezone.now() - timedelta(days=retention_days)
website/tests/test_management_commands.py (1)

29-29: Rename command to reflect cleanup-only functionality.

The command aggregate_user_analytics only performs cleanup (deleting records older than 90 days), not aggregation. The name is misleading about its actual purpose. Consider renaming to cleanup_user_activities or similar to improve clarity.

website/views/company.py (1)

40-41: User behavior analytics helper is solid; consider minor query reuse/index tuning.

The get_user_behavior_analytics logic is correct and aligns with the dashboard requirements (30‑day aggregates, 7‑day trend, top users, breakdown, peak hours, and engagement rate with a zero guard).

Two optional improvements you might consider, especially if UserActivity grows large:

  • Reuse the 30‑day activities queryset for weekly data, e.g. weekly_activities = activities.filter(timestamp__gte=seven_days_ago), to avoid re-hitting the base table.
  • If analytics queries begin to dominate, a composite index on (organization, timestamp) (in addition to the existing ones) would better support the common organization=..., timestamp__gte=... filters.

Also applies to: 791-793, 795-862

website/middleware.py (1)

4-13: Clarify logger usage and remove misleading org_ref fallback; tracking logic itself looks good.

  • You assign logger twice: first with __name__, then with "slack_bolt" and set it to WARNING. As a result, ActivityTrackingMiddleware’s logger.debug(...) messages are effectively lost and share the Slack logger. If you want separate logging, consider a dedicated logger (e.g., activity_logger = logging.getLogger(__name__)) instead of reusing the Slack one.
  • _get_organization_from_request falls back to request.session["org_ref"], but elsewhere org_ref is a referral code string, not an organization id. That fallback is at best a no‑op and at worst confusing for future readers; you can safely drop it unless you plan to store an explicit organization id in the session.

The rest of the middleware (URL pattern detection, IP extraction, organization resolution from path, and safe best‑effort UserActivity creation) looks correct and appropriately guarded with try/except.

Possible cleanups
-import logging
-import re
+import logging
+import re
@@
-from website.models import Organization, UserActivity
-
-logger = logging.getLogger(__name__)
+from website.models import Organization, UserActivity
+
+activity_logger = logging.getLogger(__name__)
@@
-logger = logging.getLogger("slack_bolt")
-logger.setLevel(logging.WARNING)
+logger = logging.getLogger("slack_bolt")
+logger.setLevel(logging.WARNING)
@@
-            except Exception as e:
+            except Exception as e:
                 # Silent failure - don't break the request
-                logger.debug("Failed to track dashboard visit: %s", type(e).__name__)
+                activity_logger.debug("Failed to track dashboard visit: %s", type(e).__name__)
@@
-            # Try to get from session
-            org_ref = request.session.get("org_ref")
-            if org_ref:
-                return Organization.objects.filter(id=org_ref).first()
+            # (Optional) If you later store an explicit organization id in session,
+            # handle it here with a clearly named key (e.g., "organization_id").

Also applies to: 45-46, 59-130

website/models.py (1)

3650-3684: UserActivity model is well-structured; consider index tuning for org/time queries.

The UserActivity definition (fields, choices, indexes, __str__) is coherent and matches its usage in middleware, signals, and analytics.

Two small follow‑ups to keep in mind:

  • Because user is non‑nullable, all writers (signals, middleware, future utilities) should guard against None before creating records — you’ve already got one such case in Issue.user (see log_bug_report).
  • If you find yourself frequently querying by organization and timestamp (as in get_user_behavior_analytics), a composite index on (organization, timestamp) could be more directly supportive than (organization, activity_type) for time‑bounded org analytics.
website/tests/test_activity_middleware.py (1)

32-43: Consider adding test coverage for unauthenticated users.

The test correctly validates that non-dashboard pages aren't tracked. To further strengthen coverage, consider adding a test case for unauthenticated users visiting the dashboard (e.g., request.user = AnonymousUser()) to ensure they're handled appropriately.

🔎 Optional test addition:
def test_unauthenticated_dashboard_not_tracked(self):
    """Test that unauthenticated dashboard visits are not tracked"""
    from django.contrib.auth.models import AnonymousUser
    
    initial_count = UserActivity.objects.count()
    
    request = self.factory.get(f"/organization/{self.org.id}/dashboard/")
    request.user = AnonymousUser()
    request.session = {}
    
    self.middleware(request)
    
    # No activity should be created for unauthenticated users
    self.assertEqual(UserActivity.objects.count(), initial_count)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between cd7312d and 9d8358a.

📒 Files selected for processing (14)
  • blt/settings.py (1 hunks)
  • website/activity_signals.py (1 hunks)
  • website/apps.py (1 hunks)
  • website/management/commands/aggregate_user_analytics.py (1 hunks)
  • website/middleware.py (2 hunks)
  • website/migrations/0264_useractivity.py (1 hunks)
  • website/models.py (1 hunks)
  • website/templates/organization/dashboard/organization_analytics.html (3 hunks)
  • website/tests/test_activity_middleware.py (1 hunks)
  • website/tests/test_activity_signals.py (1 hunks)
  • website/tests/test_analytics_view.py (1 hunks)
  • website/tests/test_management_commands.py (1 hunks)
  • website/tests/test_user_activity_model.py (1 hunks)
  • website/views/company.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
website/tests/test_analytics_view.py (2)
website/models.py (2)
  • Organization (181-289)
  • UserActivity (3650-3684)
website/views/company.py (1)
  • get_user_behavior_analytics (795-862)
website/tests/test_management_commands.py (1)
website/models.py (1)
  • UserActivity (3650-3684)
website/views/company.py (1)
website/models.py (1)
  • UserActivity (3650-3684)
website/tests/test_user_activity_model.py (1)
website/models.py (2)
  • Organization (181-289)
  • UserActivity (3650-3684)
website/tests/test_activity_signals.py (1)
website/models.py (5)
  • Domain (386-484)
  • Issue (590-739)
  • Organization (181-289)
  • UserActivity (3650-3684)
  • UserProfile (896-1109)
website/management/commands/aggregate_user_analytics.py (1)
website/models.py (1)
  • UserActivity (3650-3684)
website/activity_signals.py (1)
website/models.py (2)
  • Issue (590-739)
  • UserActivity (3650-3684)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Agent
  • GitHub Check: docker-test
🔇 Additional comments (8)
website/apps.py (1)

9-9: LGTM! Signal registration pattern is consistent.

The import follows the established pattern for registering signal handlers at app startup, matching the style of existing signal imports.

blt/settings.py (1)

123-123: LGTM! Middleware placement is correct.

The ActivityTrackingMiddleware is correctly positioned after AuthenticationMiddleware (line 119), ensuring request.user is available for activity tracking.

website/templates/organization/dashboard/organization_analytics.html (1)

778-838: LGTM! Chart initialization handles errors gracefully.

The weekly activity chart implementation includes proper error handling, empty state messages, and safe JSON parsing using json_script. The dark mode support is well-implemented.

website/tests/test_activity_signals.py (1)

34-59: The signal handler already includes issue_id in metadata.

The review comment is incorrect. The actual implementation at lines 73–76 of website/activity_signals.py already stores both comment_id and issue_id in the UserActivity metadata. The issue_id is retrieved from the Comment's object_id field after confirming the comment is attached to an Issue. No signal handler changes are needed.

Likely an incorrect or invalid review comment.

website/activity_signals.py (1)

39-79: Comment activity signal looks robust and well-scoped.

The log_bug_comment receiver cleanly restricts to Issue comments via ContentType, safely resolves author_fk.user and the related Issue, and bails out on missing data instead of raising. The organization resolution from issue.domain and the minimal metadata payload (comment_id, issue_id) are appropriate for analytics.

website/migrations/0264_useractivity.py (1)

1-68: Migration matches UserActivity model definition.

The UserActivity schema (fields, choices, FKs, indexes, verbose options) is consistent with the model in website/models.py. No issues spotted.

website/tests/test_activity_middleware.py (2)

1-6: LGTM!

The imports are appropriate for testing middleware functionality.


8-13: LGTM!

The test setup correctly initializes fixtures for middleware testing. The get_response=lambda r: None is acceptable for unit testing since these tests focus on the middleware's side effects (UserActivity creation) rather than response handling.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 18, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a comprehensive user activity tracking system for organizations with a User Behavior Analytics dashboard. It introduces database models, middleware for automatic tracking, signal handlers for event-driven logging, and a fully-featured analytics dashboard with visualizations.

Key Changes

  • Added UserActivity model to track organization-scoped user actions with metadata, IP addresses, and user agents
  • Implemented automatic activity tracking via middleware (dashboard visits) and Django signals (bug reports, comments)
  • Created analytics dashboard displaying active users, engagement metrics, activity breakdowns, top users, and weekly trends with Chart.js visualizations

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
website/models.py Defines UserActivity model with activity types, timestamps, metadata, and database indexes
website/migrations/0264_useractivity.py Migration to create UserActivity table with proper fields and indexes
website/middleware.py Adds ActivityTrackingMiddleware to track dashboard visits automatically
website/activity_signals.py Signal handlers to log bug report and comment activities
website/apps.py Registers activity_signals module for automatic loading
website/views/company.py Implements get_user_behavior_analytics method to aggregate 30-day metrics
website/templates/organization/dashboard/organization_analytics.html Adds User Behavior Analytics section with summary cards and Chart.js visualizations
website/management/commands/aggregate_user_analytics.py Management command to delete UserActivity records older than 90 days
website/tests/test_user_activity_model.py Unit tests for UserActivity model creation and validation
website/tests/test_activity_signals.py Tests verifying signal handlers create activity records for bugs and comments
website/tests/test_activity_middleware.py Tests confirming middleware tracks dashboard visits correctly
website/tests/test_analytics_view.py Tests validating analytics calculation returns correct structure
website/tests/test_management_commands.py Tests for cleanup command deleting old activities
blt/settings.py Registers ActivityTrackingMiddleware in MIDDLEWARE configuration
Comments suppressed due to low confidence (1)

website/middleware.py:89

  • Unnecessary 'pass' statement.
                pass

@Jayant2908
Copy link
Contributor Author

Jayant2908 commented Dec 18, 2025

@coderabbitai See the copilots issues which are not resolved and check if they are in fixed, in scope of the PR and tell how to fix!?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details and comments to identify Copilot issues
gh pr view 5345 --json comments,reviews,author,title --repo OWASP-BLT/BLT

Length of output: 49833


🏁 Script executed:

#!/bin/bash
# Get detailed comment thread including Copilot comments
gh api repos/OWASP-BLT/BLT/pulls/5345/comments --jq '.[] | {id: .id, user: .user.login, body: .body, path: .path, line: .line, created_at: .created_at}'

Length of output: 30479


🏁 Script executed:

#!/bin/bash
# Get review comments specifically
gh api repos/OWASP-BLT/BLT/pulls/5345/reviews --jq '.[] | {id: .id, user: .user.login, state: .state, body: .body, submitted_at: .submitted_at}'

Length of output: 19516


🏁 Script executed:

#!/bin/bash
# Fetch the key files mentioned in Copilot's comments to check current state
cat website/management/commands/aggregate_user_analytics.py

Length of output: 1471


🏁 Script executed:

#!/bin/bash
# Check the middleware implementation
cat website/middleware.py

Length of output: 4767


🏁 Script executed:

#!/bin/bash
# Check the activity signals
cat website/activity_signals.py

Length of output: 2649


🏁 Script executed:

#!/bin/bash
# Check the views analytics method
cat website/views/company.py | grep -A 100 "def get_user_behavior_analytics"

Length of output: 4127


🏁 Script executed:

#!/bin/bash
# Check the UserActivity model definition
cat website/models.py | grep -A 40 "class UserActivity"

Length of output: 1553


🏁 Script executed:

#!/bin/bash
# Check the template for inline JS
cat website/templates/organization/dashboard/organization_analytics.html | tail -200

Length of output: 8487


@Jayant2908, I've analyzed all the Copilot issues on PR #5345. Here's a comprehensive breakdown of what needs to be fixed:

🔴 Critical Issues (Must Fix)

1. Null User in Bug Report Signal (activity_signals.py:13-37)

Status: ❌ NOT FIXED
Priority: HIGH - Can cause IntegrityError

Issue: Issue.user can be None, but UserActivity.user is non-nullable. This will crash silently.

Fix:

@receiver(post_save, sender=Issue)
def log_bug_report(sender, instance, created, **kwargs):
    """Log bug report activity when a new issue is created."""
    if not created or not instance.user:  # Add user check
        return

    try:
        organization = None
        if instance.domain and hasattr(instance.domain, "organization"):
            organization = instance.domain.organization

        UserActivity.objects.create(
            user=instance.user,
            organization=organization,
            activity_type="bug_report",
            metadata={
                "issue_id": instance.id,
                "label": instance.get_label_display(),  # Remove "if instance.label" to capture "General" (0)
            },
        )
    except Exception as e:
        logger.error("Failed to log bug report activity: %s", str(e), exc_info=True)  # Better logging

2. user_agent Field Length Vulnerability (models.py:3671)

Status: ❌ NOT FIXED
Priority: HIGH - Security/DoS risk

Issue: TextField with no max_length allows malicious actors to store megabytes of data.

Fix:

user_agent = models.TextField(blank=True, default="", max_length=512)  # Add max_length

Then create a migration to apply this change.

3. IP Spoofing Vulnerability (middleware.py:108-115)

Status: ❌ NOT FIXED
Priority: HIGH - Security concern

Issue: HTTP_X_FORWARDED_FOR can be manipulated by attackers if not behind trusted proxy.

Fix:

def _get_client_ip(self, request):
    """Extract client IP address from request (trusted proxy only)."""
    # Only use X-Forwarded-For if behind trusted proxy
    # Otherwise use REMOTE_ADDR directly
    return request.META.get("REMOTE_ADDR")

Or configure Django's SECURE_PROXY_SSL_HEADER properly in settings if you have a trusted proxy.


🟡 Important Issues (Should Fix)

4. Broad Exception Handling (activity_signals.py:36, 79)

Status: ❌ NOT FIXED
Scope: IN SCOPE

Issue: Catching all exceptions hides errors. Only exception type name is logged.

Fix:

except Exception as e:
    logger.error("Failed to log bug report activity: %s", str(e), exc_info=True)

5. JavaScript in HTML Template (organization_analytics.html)

Status: ❌ NOT FIXED
Scope: IN SCOPE - Violates coding guidelines

Issue: initWeeklyActivityChart() is embedded in template. Should be in separate .js file.

Fix: Move the function to website/static/js/organization_analytics.js and load it with {% static %} tag.

6. Misleading Management Command Name

Status: ❌ NOT FIXED
Scope: IN SCOPE

Issue: Command named aggregate_user_analytics but only deletes old records.

Fix: Rename to cleanup_user_activities.py or update the help text to clarify it's cleanup-only.

7. Database Query Optimization (company.py:846)

Status: ❌ NOT FIXED
Scope: IN SCOPE - Performance

Issue: Creates separate queryset for weekly data instead of filtering existing 30-day queryset.

Fix:

# Reuse the 30-day activities queryset
weekly_trend = (
    activities.filter(timestamp__gte=seven_days_ago)  # Filter existing queryset
    .annotate(day=TruncDay("timestamp"))
    .values("day")
    .annotate(count=Count("id"))
    .order_by("day")
)

8. Unused peak_hours Calculation (company.py:833)

Status: ❌ NOT FIXED
Scope: IN SCOPE - Wastes resources

Issue: peak_hours is calculated but never used in the template.

Fix: Either remove the calculation or add UI to display it:

# Option 1: Remove it
# peak_hours = ... (delete lines 833-838)

# Option 2: Or add to template UI

9. Missing Database Index (models.py:3678)

Status: ❌ NOT FIXED
Scope: IN SCOPE - Performance

Issue: Analytics queries filter by (organization, timestamp) frequently but no composite index exists.

Fix:

indexes = [
    models.Index(fields=["user", "timestamp"], name="user_activity_user_time_idx"),
    models.Index(fields=["organization", "activity_type"], name="user_activity_org_type_idx"),
    models.Index(fields=["activity_type", "timestamp"], name="user_activity_type_time_idx"),
    models.Index(fields=["organization", "timestamp"], name="user_activity_org_time_idx"),  # Add this
]

🟢 Minor Issues (Nice to Fix)

10. Redundant pass Statement (middleware.py:89)

Status: ❌ NOT FIXED

Fix: Remove line 89: pass

11. Empty except Clause (middleware.py:128)

Status: ❌ NOT FIXED

Fix: Add logging or comment:

except Exception as e:
    logger.debug("Failed to determine organization: %s", type(e).__name__)

12. Missing Test Coverage

Status: ❌ NOT FIXED

Missing tests for:

  • Superuser exclusion (middleware)
  • Zero activities edge case (analytics view)
  • Organization None assertion (model test)

Future Enhancements (Out of Scope)

13. Rate Limiting for Dashboard Visits

Scope: OUT OF SCOPE for this PR
Recommendation: Create a follow-up issue for implementing rate limiting/deduplication to prevent creating multiple records for the same user within short time windows.


📋 Summary

  • Critical: 3 issues (null user, user_agent length, IP spoofing)
  • Important: 6 issues (exception handling, inline JS, naming, query optimization, unused code, missing index)
  • Minor: 5 issues (redundant code, missing tests, comments)
  • Future: 1 issue (rate limiting)

Total in-scope issues: 14
Immediate action required: 9 issues (Critical + Important)

Would you like me to create a GitHub issue to track these fixes?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments from GitHub Copilot on PR #5345
echo "=== Fetching Copilot review comments ==="
gh pr view 5345 --repo OWASP-BLT/BLT --json reviews,comments --jq '.reviews[] | select(.author.login == "copilot") | {state: .state, body: .body, createdAt: .createdAt}'

echo ""
echo "=== Fetching Copilot review thread comments ==="
gh api repos/OWASP-BLT/BLT/pulls/5345/comments --jq '.[] | select(.user.login == "copilot") | {path: .path, line: .line, body: .body, created_at: .created_at, in_reply_to_id: .in_reply_to_id}'

echo ""
echo "=== Fetching general PR comments from Copilot ==="
gh pr view 5345 --repo OWASP-BLT/BLT --json comments --jq '.comments[] | select(.author.login == "copilot") | {body: .body, createdAt: .createdAt}'

Length of output: 140


🏁 Script executed:

#!/bin/bash
# Fetch all reviews to see all reviewers
echo "=== All reviewers on PR #5345 ==="
gh pr view 5345 --repo OWASP-BLT/BLT --json reviews --jq '.reviews[] | {author: .author.login, state: .state, createdAt: .createdAt}' | head -20

echo ""
echo "=== Checking for GitHub Copilot variations ==="
gh api repos/OWASP-BLT/BLT/pulls/5345/comments --jq '.[] | select(.user.login | test("copilot|Copilot"; "i")) | {user: .user.login, path: .path, line: .line, body: .body}'

echo ""
echo "=== All review thread comments (first 30) ==="
gh api repos/OWASP-BLT/BLT/pulls/5345/comments --jq '.[] | {user: .user.login, path: .path, line: .line, body: .body[0:200]}' | head -60

echo ""
echo "=== Check for any bot reviewers ==="
gh pr view 5345 --repo OWASP-BLT/BLT --json reviews --jq '.reviews[] | select(.author.login | contains("bot")) | {author: .author.login, state: .state}'

Length of output: 14716


🏁 Script executed:

#!/bin/bash
# Check the current state of key files mentioned in Copilot's comments

echo "=== Check management command name and help text ==="
head -15 website/management/commands/aggregate_user_analytics.py

echo ""
echo "=== Check user_agent field definition in models.py ==="
grep -A 2 "user_agent.*=" website/models.py | head -5

echo ""
echo "=== Check signal exception handling in activity_signals.py ==="
grep -A 5 "except Exception" website/activity_signals.py

echo ""
echo "=== Check peak_hours in company.py views ==="
grep -n "peak_hours" website/views/company.py

echo ""
echo "=== Check middleware pass statement and IP extraction ==="
grep -B 2 -A 2 "pass" website/middleware.py
echo ""
grep -A 5 "_get_client_ip" website/middleware.py | head -10

echo ""
echo "=== Check indexes in UserActivity model ==="
grep -A 10 "class Meta:" website/models.py | grep -A 10 "UserActivity" | tail -15

Length of output: 2871


🏁 Script executed:

#!/bin/bash
# Check more details on the remaining issues

echo "=== Check if JavaScript is in template or separate file ==="
grep -n "initWeeklyActivityChart" website/templates/organization/dashboard/organization_analytics.html | head -5

echo ""
echo "=== Check weekly trend query in company.py ==="
sed -n '840,865p' website/views/company.py

echo ""
echo "=== Check test_user_activity_model.py for organization assertion ==="
sed -n '30,45p' website/tests/test_user_activity_model.py

echo ""
echo "=== Check if template uses peak_hours ==="
grep -n "peak_hours" website/templates/organization/dashboard/organization_analytics.html

echo ""
echo "=== Count test cases in test_activity_middleware.py ==="
grep -n "def test_" website/tests/test_activity_middleware.py

echo ""
echo "=== Check test_analytics_view.py for zero activities test ==="
grep -n "def test_" website/tests/test_analytics_view.py

Length of output: 2046


@Jayant2908: I've analyzed all Copilot's review comments on PR #5345. Here's the complete breakdown:

✅ FIXED (1 issue)

  1. ✓ user_agent field length limit (website/models.py Line 3671)
    • Status: FIXED - Changed from TextField to CharField(max_length=255)

🔴 NOT FIXED - IN SCOPE (15 issues)

High Priority

1. Misleading command name (website/management/commands/aggregate_user_analytics.py Line 11)

  • Issue: Command only deletes old records, doesn't aggregate
  • Fix: Rename file to cleanup_user_activities.py and update help text:
help = "Clean up (delete) UserActivity records older than 90 days to enforce data retention"

2. IP spoofing vulnerability (website/middleware.py Line 112)

  • Issue: HTTP_X_FORWARDED_FOR can be manipulated by attackers
  • Fix: Only use REMOTE_ADDR:
def _get_client_ip(self, request):
    """Extract client IP address from request."""
    return request.META.get("REMOTE_ADDR")

3. JavaScript in template (website/templates/organization/dashboard/organization_analytics.html Line 838)

  • Issue: Violates coding guidelines - JS should be in separate files
  • Fix: Move initWeeklyActivityChart function to a new file website/static/website/js/organization_analytics.js and load via {% static %}

4. Signal exception handling (website/activity_signals.py Lines 36 & 79)

  • Issue: Only logs exception type, loses error details
  • Fix: Log full exception with traceback:
except Exception as e:
    logger.error("Failed to log bug report activity: %s", str(e), exc_info=True)

Medium Priority

5. Database query optimization (website/views/company.py Line 862)

  • Issue: Multiple sequential queries for same data
  • Fix: Cache the base queryset and reuse it

6. Redundant weekly trend query (website/views/company.py Line 846)

  • Issue: Creates separate queryset instead of filtering existing one
  • Fix:
weekly_trend = activities.filter(timestamp__gte=seven_days_ago).annotate(
    day=TruncDay("timestamp")
).values("day").annotate(count=Count("id")).order_by("day")

7. peak_hours unused (website/views/company.py Line 833)

  • Issue: Calculated but never displayed in template
  • Fix: Either remove the calculation or add UI to display it

8. Missing index (website/models.py Line 3678)

  • Issue: Missing (organization, timestamp) index for performance
  • Fix: Add to indexes:
models.Index(fields=["organization", "timestamp"], name="user_activity_org_time_idx"),

Low Priority (Code Quality & Testing)

9. Redundant pass statements (website/middleware.py Lines 89 & 128)

  • Fix: Remove the bare pass after except blocks or add explanatory comment

10. Missing test assertion (website/tests/test_user_activity_model.py Line 39)

  • Fix: Add assertion:
self.assertIsNone(activity.organization)

11. Missing test comment (website/tests/test_management_commands.py Line 18)

  • Fix: Add explanatory comment above timestamp manipulation

12. Missing superuser test (website/tests/test_activity_middleware.py Line 43)

  • Fix: Add test case:
def test_superuser_not_tracked(self):
    self.user.is_superuser = True
    self.user.save()
    request = self.factory.get("/company/testorg/dashboard/")
    request.user = self.user
    request.session = {"organization_id": self.organization.id}
    self.middleware(request)
    self.assertEqual(UserActivity.objects.count(), 0)

13. Missing zero activities test (website/tests/test_analytics_view.py Line 42)

  • Fix: Add test case:
def test_analytics_with_no_activities(self):
    UserActivity.objects.all().delete()
    view = OrganizationDashboardAnalyticsView()
    analytics = view.get_user_behavior_analytics(self.organization)
    self.assertEqual(analytics["total_activities"], 0)
    self.assertEqual(analytics["engagement_rate"], 0)

⚠️ OUT OF SCOPE (1 issue)

14. Rate limiting for dashboard visits (website/middleware.py Line 85)

  • Issue: Every visit creates a record, high DB load
  • Why out of scope: Requires architectural changes (caching, throttling logic)
  • Recommendation: Consider as a follow-up enhancement

Summary

  • Fixed: 1/17 (6%)
  • Needs fixing: 15/17 (88%)
  • Out of scope: 1/17 (6%)

Would you like me to create an issue to track these remaining items for a follow-up PR?

@Jayant2908
Copy link
Contributor Author

@coderabbitai 1)5. JavaScript in HTML Template (organization_analytics.html) for this issue, other functions are also above the function I added still why should It be transferred to js folder!?
2)Unused peak_hours Calculation (company.py:833) give code snippet how to show it in ui

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for existing inline JavaScript functions in the template
grep -n "function " website/templates/organization/dashboard/organization_analytics.html | head -20

# Also check what other initialization functions exist
grep -n "init" website/templates/organization/dashboard/organization_analytics.html | head -30

# Get the context around existing chart initialization
sed -n '600,700p' website/templates/organization/dashboard/organization_analytics.html

Length of output: 5315


@Jayant2908, great questions! Let me address both:

1. JavaScript in Template - You're Absolutely Right! ✅

Looking at the template, I can confirm you're correct. The existing codebase already has multiple inline JavaScript functions:

  • parseChartData() (line 469)
  • showChartMessage() (line 497)
  • initPieChart() (line 615)
  • initMonthlyChart() (line 646)
  • initializeAllCharts() (line 842)

Your initWeeklyActivityChart() follows the exact same pattern as the existing initMonthlyChart() function. The Copilot suggestion was inconsistent with the established codebase conventions.

My recommendation:

  • For this PR: Keep your JavaScript inline as-is. It's consistent with the existing code.
  • Future consideration: If the team wants to refactor ALL chart initialization to separate JS files, that should be a separate PR that addresses the entire template, not just your addition.

Your code is fine! ✅


2. Code Snippet for Displaying peak_hours in UI

Add this section after the "Weekly Activity Trend" section in website/templates/organization/dashboard/organization_analytics.html:

<!-- Peak Activity Hours -->
<div class="col-md-12 mt-4">
    <div class="card shadow-sm border-0">
        <div class="card-header bg-gradient-primary text-white">
            <h5 class="mb-0">
                <i class="fas fa-clock mr-2"></i>Peak Activity Hours
            </h5>
        </div>
        <div class="card-body">
            {% if user_behavior.peak_hours %}
                <p class="text-muted mb-3">
                    <small>Top hours when users are most active (based on last 30 days)</small>
                </p>
                <div class="row">
                    {% for hour_data in user_behavior.peak_hours|slice:":5" %}
                    <div class="col-md-2 col-sm-4 col-6 mb-3">
                        <div class="text-center p-3 border rounded bg-light">
                            <div class="text-primary" style="font-size: 1.5rem;">
                                <i class="fas fa-clock"></i>
                            </div>
                            <div class="font-weight-bold mt-2" style="font-size: 1.1rem;">
                                {{ hour_data.hour }}:00
                            </div>
                            <div class="text-muted small">
                                {{ hour_data.count }} activit{{ hour_data.count|pluralize:"y,ies" }}
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            {% else %}
                <div class="text-center text-muted py-4">
                    <i class="fas fa-clock fa-3x mb-3 opacity-50"></i>
                    <p>No peak activity data available yet.</p>
                </div>
            {% endif %}
        </div>
    </div>
</div>

Alternative: Horizontal Bar Chart Visualization

If you prefer a chart visualization, add this canvas and script instead:

<!-- Peak Activity Hours Chart -->
<div class="col-md-12 mt-4">
    <div class="card shadow-sm border-0">
        <div class="card-header bg-gradient-primary text-white">
            <h5 class="mb-0">
                <i class="fas fa-clock mr-2"></i>Peak Activity Hours
            </h5>
        </div>
        <div class="card-body">
            <canvas id="peakHoursChart" height="80"></canvas>
        </div>
    </div>
</div>

<script>
// Add this inside your existing <script> tag
function initPeakHoursChart() {
    const ctx = document.getElementById('peakHoursChart');
    if (!ctx) return;
    
    const peakHoursData = {{ user_behavior.peak_hours|slice:":10"|safe }};
    
    if (!peakHoursData || peakHoursData.length === 0) {
        showChartMessage(ctx.parentElement, 'No peak hours data available', 'fas fa-clock');
        return;
    }
    
    const labels = peakHoursData.map(item => `${item.hour}:00`);
    const data = peakHoursData.map(item => item.count);
    
    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: labels,
            datasets: [{
                label: 'Activities',
                data: data,
                backgroundColor: '#3498db',
                borderColor: '#2980b9',
                borderWidth: 1,
                borderRadius: 4
            }]
        },
        options: {
            indexAxis: 'y', // Horizontal bars
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: { display: false },
                title: {
                    display: true,
                    text: 'Top 10 Most Active Hours (Last 30 Days)'
                }
            },
            scales: {
                x: {
                    beginAtZero: true,
                    ticks: { stepSize: 1 }
                }
            }
        }
    });
}

// Add to initializeAllCharts() function:
function initializeAllCharts() {
    // ... existing code ...
    initMonthlyChart();
    initWeeklyActivityChart();
    initPeakHoursChart(); // Add this line
}
</script>

Don't forget to update website/views/company.py to format peak_hours properly:

# In get_user_behavior_analytics method, change line 833-838 to:
peak_hours = (
    activities.annotate(hour=ExtractHour("timestamp"))
    .values("hour")
    .annotate(count=Count("id"))
    .order_by("-count")[:10]  # Top 10 hours
)
peak_hours = [{"hour": item["hour"], "count": item["count"]} for item in peak_hours]

Choose whichever visualization style fits better with your dashboard aesthetics! The first option (cards) is simpler and doesn't require additional Chart.js code. The second option (horizontal bars) provides better visual comparison.

@Jayant2908
Copy link
Contributor Author

@coderabbitai how to fix these!?
1)The weekly trend calculation creates a separate queryset (weekly_activities) instead of filtering the existing activities queryset. Since weekly trend is a subset of the 30-day data already fetched, you could filter the existing queryset to avoid an additional database query: weekly_trend = activities.filter(timestamp__gte=seven_days_ago).annotate(...)
2)The middleware creates a UserActivity record on every dashboard visit, which could result in significant database writes for active organizations. Consider implementing rate limiting or deduplication to prevent creating multiple activity records for the same user visiting the same dashboard within a short time window (e.g., within the same minute). This will reduce database load and storage growth.
3)The analytics test only verifies the case when activities exist. There's no test case for the edge case when an organization has zero activities, which would test the division by zero protection in the engagement rate calculation (line 852 in company.py). Add a test case with no activities to verify that the analytics function handles empty data gracefully.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the current implementation of get_user_behavior_analytics
echo "=== Current get_user_behavior_analytics implementation ==="
sed -n '/def get_user_behavior_analytics/,/^    def /p' website/views/company.py | head -n -1

echo ""
echo "=== Current ActivityTrackingMiddleware implementation ==="
cat website/middleware.py

echo ""
echo "=== Current test_analytics_view.py ==="
cat website/tests/test_analytics_view.py

Length of output: 9544


@Jayant2908, here are the specific fixes for all three issues:

1️⃣ Fix Weekly Trend Query Optimization

File: website/views/company.py

Replace lines 846-853 with this optimized version:

# Calculate weekly trend (last 7 days) - reuse existing queryset
seven_days_ago = timezone.now() - timedelta(days=7)

weekly_trend = (
    activities.filter(timestamp__gte=seven_days_ago)  # Filter existing queryset
    .annotate(day=TruncDay("timestamp"))
    .values("day")
    .annotate(count=Count("id"))
    .order_by("day")
)

weekly_trend_list = [{"date": day["day"].strftime("%Y-%m-%d"), "count": day["count"]} for day in weekly_trend]

What changed: Removed the weekly_activities queryset creation and directly filtered the existing activities queryset, avoiding an additional database hit.


2️⃣ Fix Rate Limiting in Middleware

File: website/middleware.py

Replace the __call__ method (lines 64-92) with this version that includes deduplication:

def __call__(self, request):
    # Track dashboard visits for authenticated users
    if request.user.is_authenticated and not request.user.is_superuser:
        try:
            # Check if this is an organization dashboard visit
            if self._is_dashboard_visit(request.path):
                organization = self._get_organization_from_request(request)
                
                # Deduplication: Check if user visited this dashboard in the last minute
                one_minute_ago = timezone.now() - timedelta(minutes=1)
                recent_visit = UserActivity.objects.filter(
                    user=request.user,
                    organization=organization,
                    activity_type="dashboard_visit",
                    timestamp__gte=one_minute_ago,
                    metadata__path=request.path,
                ).exists()
                
                # Only create activity if no recent visit exists
                if not recent_visit:
                    # Extract IP address
                    ip_address = self._get_client_ip(request)

                    # Extract user agent
                    user_agent = request.META.get("HTTP_USER_AGENT", "")

                    # Create activity record
                    UserActivity.objects.create(
                        user=request.user,
                        organization=organization,
                        activity_type="dashboard_visit",
                        ip_address=ip_address,
                        user_agent=user_agent,
                        metadata={"path": request.path},
                    )
        except Exception as e:
            # Silent failure - don't break the request
            logger.debug("Failed to track dashboard visit: %s", type(e).__name__)

    response = self.get_response(request)
    return response

Also add the import at the top of the file:

from datetime import timedelta

from django.utils import timezone

What changed: Added a check to see if the same user visited the same dashboard in the last minute. Only creates a new activity record if no recent visit exists, reducing database writes by ~99% for active users.


3️⃣ Add Zero Activities Test Case

File: website/tests/test_analytics_view.py

Add this new test method to the UserBehaviorAnalyticsTest class:

def test_analytics_with_zero_activities(self):
    """Test analytics gracefully handles organizations with no activities"""
    from website.views.company import OrganizationDashboardAnalyticsView

    # Create a new organization with no activities
    empty_org = Organization.objects.create(name="Empty Org", url="https://empty.com")
    
    view = OrganizationDashboardAnalyticsView()
    analytics = view.get_user_behavior_analytics(empty_org)

    # Verify structure still exists
    self.assertIn("active_users_count", analytics)
    self.assertIn("total_activities", analytics)
    self.assertIn("engagement_rate", analytics)
    self.assertIn("top_users", analytics)
    self.assertIn("activity_breakdown", analytics)
    self.assertIn("weekly_trend", analytics)

    # Verify zero values
    self.assertEqual(analytics["active_users_count"], 0)
    self.assertEqual(analytics["total_activities"], 0)
    self.assertEqual(analytics["engagement_rate"], 0)  # Tests division by zero protection
    self.assertEqual(len(analytics["top_users"]), 0)
    self.assertEqual(len(analytics["activity_breakdown"]), 0)
    self.assertEqual(len(analytics["weekly_trend"]), 0)

What this tests: Verifies that when an organization has zero activities, the analytics function returns proper zero values instead of crashing (especially testing the division by zero protection in the engagement rate calculation on line 859).


Summary of Changes

Issue File Lines Change
1. Query optimization website/views/company.py 846-853 Reuse existing queryset
2. Rate limiting website/middleware.py 64-92, add imports Add 1-minute deduplication
3. Zero activities test website/tests/test_analytics_view.py Add new method Test edge case

All three fixes are in scope for this PR and address legitimate performance and testing concerns! 🚀

@github-actions github-actions bot added files-changed: 16 PR changes 16 files tests: passed Django tests passed and removed files-changed: 14 PR changes 14 files tests: failed Django tests failed labels Dec 18, 2025
@Jayant2908
Copy link
Contributor Author

@coderabbitai Can you check if all the copilot issues are fixed? Also review the changes again

@github-actions github-actions bot added last-active: 1d PR last updated 1 day ago and removed last-active: 0d PR last updated 0 days ago labels Dec 31, 2025
Copilot AI review requested due to automatic review settings December 31, 2025 10:00
@github-actions github-actions bot removed the last-active: 1d PR last updated 1 day ago label Dec 31, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Copy link
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: 0

♻️ Duplicate comments (1)
website/activity_signals.py (1)

36-37: Fix the log message: says "bug comment" but should say "bug report".

This is in the log_bug_report handler, but the error message incorrectly references "bug comment activity".

Proposed fix
     except Exception as e:
-        logger.error("Failed to log bug comment activity: %s", str(e), exc_info=True)
+        logger.error("Failed to log bug report activity: %s", str(e), exc_info=True)
🧹 Nitpick comments (6)
website/admin.py (1)

1277-1292: Consider adding has_delete_permission override for full immutability.

The admin correctly prevents adding and changing records, but deletion is still allowed. For complete audit log immutability, consider adding:

Proposed fix
     def has_change_permission(self, request, obj=None):
         # Activity records should be immutable
         return False
+
+    def has_delete_permission(self, request, obj=None):
+        # Activity records should not be deleted via admin
+        return False
website/management/commands/aggregate_user_analytics.py (1)

11-43: Implementation looks good; consider making retention period configurable.

The command properly handles the zero-records case, uses specific DatabaseError handling, and extends LoggedBaseCommand for tracking. The 90-day retention is hardcoded; consider accepting it as a command argument for flexibility.

Optional: Configurable retention period
 class Command(LoggedBaseCommand):
     help = "Clean up (delete) UserActivity records older than 90 days to enforce data retention"

+    def add_arguments(self, parser):
+        parser.add_argument(
+            '--days',
+            type=int,
+            default=90,
+            help='Number of days to retain (default: 90)',
+        )
+
     def handle(self, *args, **options):
         """Clean up old UserActivity records."""
         try:
-            # Calculate cutoff date (90 days ago)
-            cutoff_date = timezone.now() - timedelta(days=90)
+            # Calculate cutoff date
+            retention_days = options.get('days', 90)
+            cutoff_date = timezone.now() - timedelta(days=retention_days)
website/views/company.py (1)

795-861: Well-optimized analytics method with good queryset reuse.

The implementation efficiently:

  • Reuses the base 30-day queryset for all aggregations
  • Combines total_activities and active_users_count into a single aggregate call
  • Properly guards against division by zero for engagement rate

One consideration: weekly_trend_list will only contain entries for days with activity. Days with zero activities won't appear in the list. If the frontend expects all 7 days, you may need to fill in missing dates:

Optional: Fill missing dates in weekly trend
# After building weekly_trend_list, fill gaps:
from datetime import date, timedelta

date_counts = {d["date"]: d["count"] for d in weekly_trend_list}
filled_trend = []
for i in range(7):
    day = (now - timedelta(days=6-i)).date().isoformat()
    filled_trend.append({"date": day, "count": date_counts.get(day, 0)})
weekly_trend_list = filled_trend
website/models.py (1)

3650-3686: UserActivity schema and indexing look good; consider a standalone timestamp index later

Model fields and composite indexes align well with the analytics and middleware queries (user/org/type + recent window), and the 512‑char user_agent plus JSON metadata are appropriate for this use case. As a future optimization, if the retention cleanup command ever becomes slow on large tables, you might add a standalone index on timestamp (or db_index=True) rather than relying only on composite indexes; similarly, bumping activity_type.max_length slightly (e.g., 32–50) would give more headroom for new activity types without another migration, but that’s optional.

website/templates/organization/dashboard/organization_analytics.html (1)

324-450: User Behavior Analytics section is well-integrated; optional layout tweak

The UBA block is correctly guarded by {% if user_behavior %} and matches the rest of the dashboard styling. If you want a denser layout on wider screens, you could change the breakdown/top‑users container from grid-cols-1 to md:grid-cols-2 so those panels sit side‑by‑side on desktop, but that’s purely cosmetic.

website/middleware.py (1)

91-196: ActivityTrackingMiddleware meets the analytics + privacy objectives; only minor hardening possible

The middleware cleanly implements the intended behavior: it tracks only authenticated non‑superusers on org/company dashboard URLs, uses a cache+DB hybrid dedupe window, stores org/user/metadata path, and anonymizes IPs while truncating user agents to fit the 512‑char field. That’s a good balance of accuracy, performance, and privacy for analytics.

Two small, non‑blocking tweaks you might consider:

  • In _get_organization_from_request, guard request.session with hasattr(request, "session") so the helper is safe even if used in contexts without SessionMiddleware.
  • Adjust the debug log in the except of _get_organization_from_request to something like "Failed to extract organization from request" to distinguish org‑resolution issues from actual tracking failures.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between aa877b3 and eec2bd0.

📒 Files selected for processing (15)
  • blt/settings.py
  • website/activity_signals.py
  • website/admin.py
  • website/apps.py
  • website/management/commands/aggregate_user_analytics.py
  • website/middleware.py
  • website/migrations/0264_useractivity.py
  • website/models.py
  • website/templates/organization/dashboard/organization_analytics.html
  • website/tests/test_activity_middleware.py
  • website/tests/test_activity_signals.py
  • website/tests/test_analytics_view.py
  • website/tests/test_management_commands.py
  • website/tests/test_user_activity_model.py
  • website/views/company.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • website/tests/test_analytics_view.py
  • website/tests/test_management_commands.py
  • website/migrations/0264_useractivity.py
  • website/tests/test_activity_signals.py
  • blt/settings.py
  • website/tests/test_user_activity_model.py
  • website/apps.py
🧰 Additional context used
🧬 Code graph analysis (5)
website/middleware.py (1)
website/models.py (2)
  • Organization (181-289)
  • UserActivity (3650-3686)
website/activity_signals.py (1)
website/models.py (2)
  • Issue (590-739)
  • UserActivity (3650-3686)
website/tests/test_activity_middleware.py (2)
website/middleware.py (1)
  • ActivityTrackingMiddleware (91-196)
website/models.py (2)
  • Organization (181-289)
  • UserActivity (3650-3686)
website/management/commands/aggregate_user_analytics.py (1)
website/models.py (1)
  • UserActivity (3650-3686)
website/admin.py (1)
website/models.py (1)
  • UserActivity (3650-3686)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Tests
  • GitHub Check: Agent
  • GitHub Check: docker-test
🔇 Additional comments (5)
website/activity_signals.py (1)

40-81: Well-structured signal handler with proper guards.

The log_bug_comment handler correctly:

  • Filters for Issue content type only
  • Safely extracts user from author_fk.user
  • Excludes superusers consistently with middleware
  • Handles missing Issue gracefully
  • Logs exceptions with full traceback
website/templates/organization/dashboard/organization_analytics.html (2)

455-458: json_script placement correctly guarded by user_behavior

Moving the weekly_trend and peak_hours json_script tags inside the {% if user_behavior %} block avoids template/runtime errors when analytics data isn’t present while still letting the JS safely no‑op when canvases or data are absent.


635-666: Chart initialization/destruction logic is now robust across all charts

The Chart.js usage looks solid now: pie and monthly initializers fetch the canvas before calling Chart.getChart, destroy any existing instance, and then create a new chart, fixing the earlier canvas/ctx ordering bug and preventing leaks. The new weekly and peak‑hours chart initializers follow the same pattern, short‑circuit cleanly when canvases or data are missing, and apply dark‑mode‑aware tooltip/axis styling, which keeps behavior consistent across the dashboard.

Also applies to: 669-675, 804-944, 963-964

website/middleware.py (1)

18-45: IP anonymization helper correctly handles IPv4 and IPv6

anonymize_ip cleanly masks the last octet for IPv4 and the last 80 bits for IPv6 using a bounded (2**48 - 1) << 80 mask, returning None on bad input. This is a good balance between utility and privacy for the analytics use case.

website/tests/test_activity_middleware.py (1)

9-173: Comprehensive tests for ActivityTrackingMiddleware behavior

The test suite does a good job exercising the middleware: it covers dashboard vs non‑dashboard, superuser exclusion, cache‑based dedupe, DB‑fallback dedupe beyond the 1‑minute window, cache key scoping by organization, and IPv4 anonymization. This gives strong confidence in the tracking logic. If you ever extend anonymization behavior further (e.g., more nuanced IPv6 cases), you could add a dedicated IPv6 test, but what’s here is already solid.

@github-actions github-actions bot added the last-active: 0d PR last updated 0 days ago label Jan 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested PR has requested changes from a reviewer files-changed: 15 PR changes 15 files has-peer-review PR has received peer review last-active: 0d PR last updated 0 days ago migrations PR contains database migration files pre-commit: passed Pre-commit checks passed quality: high tests: passed Django tests passed

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

Add to company cyber dashboard: User behavior analytics

3 participants