-
-
Notifications
You must be signed in to change notification settings - Fork 319
Add User Activity Tracking & Organization User Behavior Analytics Dashboard #5345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
👋 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:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📊 Monthly LeaderboardHi @Jayant2908! Here's how you rank for December 2025:
Leaderboard based on contributions in December 2025. Keep up the great work! 🚀 |
There was a problem hiding this 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_analyticsonly performs cleanup (deleting records older than 90 days), not aggregation. The name is misleading about its actual purpose. Consider renaming tocleanup_user_activitiesor 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_analyticslogic 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
UserActivitygrows large:
- Reuse the 30‑day
activitiesqueryset 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 commonorganization=..., timestamp__gte=...filters.Also applies to: 791-793, 795-862
website/middleware.py (1)
4-13: Clarify logger usage and remove misleadingorg_reffallback; tracking logic itself looks good.
- You assign
loggertwice: first with__name__, then with"slack_bolt"and set it to WARNING. As a result,ActivityTrackingMiddleware’slogger.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_requestfalls back torequest.session["org_ref"], but elsewhereorg_refis 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
UserActivitycreation) 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:UserActivitymodel is well-structured; consider index tuning for org/time queries.The
UserActivitydefinition (fields, choices, indexes,__str__) is coherent and matches its usage in middleware, signals, and analytics.Two small follow‑ups to keep in mind:
- Because
useris non‑nullable, all writers (signals, middleware, future utilities) should guard againstNonebefore creating records — you’ve already got one such case inIssue.user(seelog_bug_report).- If you find yourself frequently querying by
organizationandtimestamp(as inget_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
📒 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
ActivityTrackingMiddlewareis correctly positioned afterAuthenticationMiddleware(line 119), ensuringrequest.useris 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.pyalready stores bothcomment_idandissue_idin 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_commentreceiver cleanly restricts to Issue comments viaContentType, safely resolvesauthor_fk.userand the relatedIssue, and bails out on missing data instead of raising. The organization resolution fromissue.domainand the minimal metadata payload (comment_id,issue_id) are appropriate for analytics.website/migrations/0264_useractivity.py (1)
1-68: Migration matchesUserActivitymodel definition.The
UserActivityschema (fields, choices, FKs, indexes, verbose options) is consistent with the model inwebsite/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: Noneis acceptable for unit testing since these tests focus on the middleware's side effects (UserActivity creation) rather than response handling.
website/templates/organization/dashboard/organization_analytics.html
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
UserActivitymodel 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
|
@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!? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 49833 🏁 Script executed: Length of output: 30479 🏁 Script executed: Length of output: 19516 🏁 Script executed: Length of output: 1471 🏁 Script executed: Length of output: 4767 🏁 Script executed: Length of output: 2649 🏁 Script executed: Length of output: 4127 🏁 Script executed: Length of output: 1553 🏁 Script executed: 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 Issue: 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 logging2. user_agent Field Length Vulnerability (models.py:3671)Status: ❌ NOT FIXED 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_lengthThen create a migration to apply this change. 3. IP Spoofing Vulnerability (middleware.py:108-115)Status: ❌ NOT FIXED Issue: 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 🟡 Important Issues (Should Fix)4. Broad Exception Handling (activity_signals.py:36, 79)Status: ❌ NOT FIXED 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 Issue: Fix: Move the function to 6. Misleading Management Command NameStatus: ❌ NOT FIXED Issue: Command named Fix: Rename to 7. Database Query Optimization (company.py:846)Status: ❌ NOT FIXED 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 Issue: 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 UI9. Missing Database Index (models.py:3678)Status: ❌ NOT FIXED Issue: Analytics queries filter by 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: 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 CoverageStatus: ❌ NOT FIXED Missing tests for:
⚪ Future Enhancements (Out of Scope)13. Rate Limiting for Dashboard VisitsScope: OUT OF SCOPE for this PR 📋 Summary
Total in-scope issues: 14 Would you like me to create a GitHub issue to track these fixes? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 140 🏁 Script executed: Length of output: 14716 🏁 Script executed: Length of output: 2871 🏁 Script executed: Length of output: 2046 @Jayant2908: I've analyzed all Copilot's review comments on PR #5345. Here's the complete breakdown: ✅ FIXED (1 issue)
🔴 NOT FIXED - IN SCOPE (15 issues)High Priority1. Misleading command name (
help = "Clean up (delete) UserActivity records older than 90 days to enforce data retention"2. IP spoofing vulnerability (
def _get_client_ip(self, request):
"""Extract client IP address from request."""
return request.META.get("REMOTE_ADDR")3. JavaScript in template (
4. Signal exception handling (
except Exception as e:
logger.error("Failed to log bug report activity: %s", str(e), exc_info=True)Medium Priority5. Database query optimization (
6. Redundant weekly trend query (
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 (
8. Missing index (
models.Index(fields=["organization", "timestamp"], name="user_activity_org_time_idx"),Low Priority (Code Quality & Testing)9. Redundant pass statements (
10. Missing test assertion (
self.assertIsNone(activity.organization)11. Missing test comment (
12. Missing superuser test (
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 (
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)
|
|
@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!? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: 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:
Your My recommendation:
Your code is fine! ✅ 2. Code Snippet for Displaying peak_hours in UIAdd this section after the "Weekly Activity Trend" section in <!-- 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 # 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. |
|
@coderabbitai how to fix these!? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 9544 @Jayant2908, here are the specific fixes for all three issues: 1️⃣ Fix Weekly Trend Query OptimizationFile: 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 2️⃣ Fix Rate Limiting in MiddlewareFile: Replace the 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 responseAlso add the import at the top of the file: from datetime import timedelta
from django.utils import timezoneWhat 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 CaseFile: Add this new test method to the 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
All three fixes are in scope for this PR and address legitimate performance and testing concerns! 🚀 |
|
@coderabbitai Can you check if all the copilot issues are fixed? Also review the changes again |
…so added more checks on user
aa877b3 to
eec2bd0
Compare
There was a problem hiding this 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.
There was a problem hiding this 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_reporthandler, 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 addinghas_delete_permissionoverride 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 Falsewebsite/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
DatabaseErrorhandling, and extendsLoggedBaseCommandfor 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_activitiesandactive_users_countinto a single aggregate call- Properly guards against division by zero for engagement rate
One consideration:
weekly_trend_listwill 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_trendwebsite/models.py (1)
3650-3686: UserActivity schema and indexing look good; consider a standalone timestamp index laterModel fields and composite indexes align well with the analytics and middleware queries (user/org/type + recent window), and the 512‑char
user_agentplus JSONmetadataare 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 ontimestamp(ordb_index=True) rather than relying only on composite indexes; similarly, bumpingactivity_type.max_lengthslightly (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 tweakThe 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 fromgrid-cols-1tomd:grid-cols-2so 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 possibleThe 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, guardrequest.sessionwithhasattr(request, "session")so the helper is safe even if used in contexts without SessionMiddleware.- Adjust the debug log in the
exceptof_get_organization_from_requestto 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
📒 Files selected for processing (15)
blt/settings.pywebsite/activity_signals.pywebsite/admin.pywebsite/apps.pywebsite/management/commands/aggregate_user_analytics.pywebsite/middleware.pywebsite/migrations/0264_useractivity.pywebsite/models.pywebsite/templates/organization/dashboard/organization_analytics.htmlwebsite/tests/test_activity_middleware.pywebsite/tests/test_activity_signals.pywebsite/tests/test_analytics_view.pywebsite/tests/test_management_commands.pywebsite/tests/test_user_activity_model.pywebsite/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_commenthandler 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 byuser_behaviorMoving the
weekly_trendandpeak_hoursjson_scripttags 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 chartsThe 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 earliercanvas/ctxordering 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_ipcleanly masks the last octet for IPv4 and the last 80 bits for IPv6 using a bounded(2**48 - 1) << 80mask, returningNoneon 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 behaviorThe 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.
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
Tests added
Manual verification:- Created sample activity data in Django shell and confirmed:
Screenshot of the Dashboard:-
Summary by CodeRabbit
New Features
Tracking
Privacy
Administration
Chores
Tests
✏️ Tip: You can customize this high-level summary in your review settings.