Skip to content

Trending Courses/Professors Feature#1231

Closed
ArislanT wants to merge 17 commits into
devfrom
feature/trending-analytics
Closed

Trending Courses/Professors Feature#1231
ArislanT wants to merge 17 commits into
devfrom
feature/trending-analytics

Conversation

@ArislanT
Copy link
Copy Markdown
Collaborator

@ArislanT ArislanT commented Mar 23, 2026

GitHub Issues addressed

  • This PR closes

What I did

Changes:

  1. CREATED tcf_website/analytics_utils.py : This serves as the engine for the feature. It implements a non-blocking analytics pipeline using a global ThreadPoolExecutor (with bounded workers and backlog management, all detailed in the code) and DynamoDB atomic counters to ensure collection doesn't impact user-facing latency.
  2. Updated tcf_website/views/browse.py: Integrated tracking calls course_view, instructor_view, and course_instructor. These calls are strategically placed to record page renders asynchronously, ensuring collection does not delay the user.
  3. Updated .env.example: Added necessary DynamoDB configuration keys to ensure environment consistency.

Infrastructure:

  • DynamoDB: Configured a new table "trending_analytics" in AWS with a Composite Key (pk as Partition Key, sk as Sort Key)
  • Optimizations: Set to On-Demand Capacity Mode to handle traffic bursts without provisioning costs and enabled Time-to-Live (TTL) on the expires_at attribute to automate the cleanup of old data, keeping the table lean and costs lower.

Screenshots

  • Before
  • After

Testing

I utilized Locust to verify the stability of the asynchronous pipeline under high-concurrency conditions within the local Docker environment.

  1. Stability Test
  • Users: 100 concurrent users
  • Spawn Rate: 40 users/second
  • Duration: ~90 seconds
  • Total Requests: 3,351
  • Failure Rate: 0%
  • Peak Throughput: 44.4 RPS
    The system maintained total stability and was reliable. Latency remained consistent until local hardware limits were reached, confirming the Django application logic is not an issue.
  1. Stress Test
  • Users: 200 concurrent users
  • Spawn Rate: 20 users/second
  • Duration: ~90 seconds
  • Total Requests: ~9,900
  • Failure Rate: 0.13%
    The small failure rate, which was 7 failures out of 9,900, confirms that the ThreadPoolExecutor backlog limits were reached. This validates the system's ability to prioritize site stability over analytic precision during extreme traffic spikes by dropping excess events rather than blocking the main request cycle.
    (The seven views were successfully rendered for users but just not counted in analytics)

Refinements Following Automated Review & Testing
Primary Refinement:

  • Introduced _ANALYTICS_ENABLED flag to differentiate between Analytics being disabled and error, which is a missing credentials and connections failure issue respectively.
  • Apart of this, I also added early exits in _fire_and_forget to prevent unnecessary thread pool overhead and logging when analytics are not configured, using _ANALYTICS_ENABLED.

Other Changes:

  1. Pylint Compliance: Fixed docstrings
  2. Dead Code Removal: Removed unused import time from analytics_utils.py
  3. Documentation Accuracy: Corrected the hints in get_analytics_health
  4. Optimization: Reclassified missing credentials log from ERROR to INFO for cleaner local development.

Questions/Discussions/Notes

Summary by CodeRabbit

Release Notes

  • New Features
    • Added optional page view analytics for courses and instructors.
    • Metrics are recorded in the background without impacting user experience and automatically expire over time.
    • Enable through optional configuration settings; inactive by default.

@ArislanT ArislanT requested a review from brandonistfan March 23, 2026 22:15
@ArislanT ArislanT self-assigned this Mar 23, 2026
@ArislanT ArislanT added Feature New feature Performance Performance improvements, refactoring labels Mar 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces analytics tracking functionality to the application. Environment variables for DynamoDB-based analytics are added to the configuration template, and a new module implements asynchronous view recording for courses and instructors using a thread pool executor with event queueing and backpressure management.

Changes

Cohort / File(s) Summary
Configuration
.env.example
Added optional DynamoDB analytics configuration block with AWS credentials, region (defaults to us-east-1), and table name (defaults to trending_analytics). Fixed missing trailing newline.
Analytics Module
tcf_website/analytics_utils.py
New module implementing asynchronous view recording to DynamoDB. Provides record_course_view() and record_instructor_view() entrypoints that queue updates via ThreadPoolExecutor. Tracks in-flight work with backpressure (drops events when queue exceeds threshold), atomically increments counters, sets TTL expiration, and handles exceptions gracefully.

Sequence Diagram(s)

sequenceDiagram
    actor App as Application
    participant Queue as ThreadPoolExecutor
    participant Executor as Worker Thread
    participant DDB as DynamoDB

    App->>Queue: record_course_view(course_id)<br/>or record_instructor_view(instructor_id)
    alt Pending < MAX_BACKLOG
        Queue->>Queue: Queue task (_send_to_dynamo)
        Queue->>Executor: Execute task
        Executor->>DDB: Update item (PK: entity_type:id,<br/>SK: YYYY-MM-DD)
        Executor->>DDB: Increment view_count, set expires_at
        alt Success
            DDB-->>Executor: Success
        else Exception
            Executor-->>Executor: Log warning, continue
        end
    else Pending >= MAX_BACKLOG
        Queue-->>App: Drop event (backpressure)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop along to trends so fine,
With DynamoDB in a line,
Threading queues with graceful care,
Analytics everywhere!
No views are lost, no workers blocked—
Just async magic nicely locked!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Trending Courses/Professors Feature' directly summarizes the main change—adding trending analytics functionality to track and identify trending courses and professors.
Description check ✅ Passed The description covers all required template sections: GitHub Issues (though empty), What I Did (comprehensive with 3 file changes and infrastructure details), Screenshots (included), Testing (detailed with Locust results), and Notes. The content is complete and thorough.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/trending-analytics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tcf_website/analytics_utils.py`:
- Around line 51-62: The analytics init should detect an explicit "disabled"
fast path instead of treating missing env vars as an error: check the relevant
env flags/keys before creating resources and if analytics are disabled set a
clear disabled state (e.g., _SESSION=None, _DYNAMODB=None, and a new
_ANALYTICS_DISABLED=True) and return early so you never touch or start _EXECUTOR
or enqueue background tasks; update get_analytics_health() to report "disabled"
when _ANALYTICS_DISABLED is True and guard the code paths around _EXECUTOR, the
background enqueue logic (the block around lines 109-145) and the subsequent
task submission (lines 165-189) to no-op when disabled instead of logging
failures.
- Around line 14-17: The project is missing the boto3 runtime dependency
referenced by the import boto3 in tcf_website/analytics_utils.py (which causes
tcf_website/views/browse.py to fail at import/time); add "boto3" to the
top-level dependencies list in pyproject.toml (same section where other packages
are listed) so the package is installed for runtime and imports succeed.

In `@tcf_website/views/browse.py`:
- Around line 294-295: The analytics calls (record_course_view(course.id) and
the course_instructor call site) are currently executed on every request method
in instructor_view and other handlers, skewing metrics; update those sites to
only run for successful GET page renders by either wrapping them with a guard
that checks request.method == "GET" before calling record_course_view(course.id)
and course_instructor, or move the calls to after the successful render(...)
return so they only execute on successful GET responses; reference the
instructor_view function and the call sites where record_course_view(course.id)
and course_instructor are invoked and apply the GET-only guard or relocation
consistently at all three locations mentioned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3121c38a-84d5-4fa7-9a2a-510d54d0748b

📥 Commits

Reviewing files that changed from the base of the PR and between c1052bc and 0c6f3a9.

📒 Files selected for processing (3)
  • .env.example
  • tcf_website/analytics_utils.py
  • tcf_website/views/browse.py

Comment thread tcf_website/analytics_utils.py Outdated
Comment thread tcf_website/analytics_utils.py Outdated
Comment thread tcf_website/views/browse.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tcf_website/analytics_utils.py (1)

87-89: Avoid repeated env/table resolution in the hot path.

Resolving DYNAMODB_TABLE_NAME and Table(...) on every event adds avoidable overhead under load; cache once at init and reuse.

Refactor sketch
+# Configuration
+_TABLE_NAME = env("DYNAMODB_TABLE_NAME", default="trending_analytics")
+_TABLE = None
@@
     if access_key and secret_key:
         _DYNAMODB = boto3.Session(
@@
         ).resource("dynamodb", config=_BOTO_CONFIG)
+        _TABLE = _DYNAMODB.Table(_TABLE_NAME)
         _ANALYTICS_ENABLED = True
@@
 def _send_to_dynamo(entity_type: str, entity_id: int) -> None:
@@
-    if _DYNAMODB is None:
+    if _TABLE is None:
         return
@@
-        table_name = env("DYNAMODB_TABLE_NAME", default="trending_analytics")
-        table = _DYNAMODB.Table(table_name)
-
@@
-        table.update_item(
+        _TABLE.update_item(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/analytics_utils.py` around lines 87 - 89, The code repeatedly
calls env("DYNAMODB_TABLE_NAME") and _DYNAMODB.Table(table_name) on each event,
which is expensive; move the resolution and Table creation to module/init time
and reuse a cached value. Create a module-level constant (e.g.,
DYNAMODB_TABLE_NAME or CACHED_TABLE_NAME) and a single cached table object
(e.g., CACHED_DDB_TABLE = _DYNAMODB.Table(DYNAMODB_TABLE_NAME)) and update code
that currently computes table_name/table to use CACHED_DDB_TABLE instead. Ensure
any tests or code that overwrite env in test runs can still patch the cached
objects as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tcf_website/analytics_utils.py`:
- Line 31: The TTL configuration _TTL_DAYS (env key ANALYTICS_TTL_DAYS) can be
non-positive which makes computed expires_at immediate/past; validate and clamp
it at load time: when reading _TTL_DAYS, if value is None or <= 0, set it to a
safe minimum (e.g. 1) and optionally log a warning; ensure any use sites that
compute expires_at rely on this validated _TTL_DAYS so fresh analytics rows are
not purged immediately (refer to the module-level _TTL_DAYS constant and the
code that computes expires_at).
- Around line 181-182: The health docstring in analytics_utils.py is out of
sync: it mentions status values "healthy/session_failed" but the code actually
returns "healthy/disabled"; update the docstring(s) that describe the health
contract to list the correct status values ("healthy" and "disabled") and any
other returned fields (e.g., "pending_tasks") so they match the implementation;
ensure both occurrences (the docstring around line ~181 and the duplicate at
~200) are changed to reflect the actual return values and wording used by the
health-related function.

---

Nitpick comments:
In `@tcf_website/analytics_utils.py`:
- Around line 87-89: The code repeatedly calls env("DYNAMODB_TABLE_NAME") and
_DYNAMODB.Table(table_name) on each event, which is expensive; move the
resolution and Table creation to module/init time and reuse a cached value.
Create a module-level constant (e.g., DYNAMODB_TABLE_NAME or CACHED_TABLE_NAME)
and a single cached table object (e.g., CACHED_DDB_TABLE =
_DYNAMODB.Table(DYNAMODB_TABLE_NAME)) and update code that currently computes
table_name/table to use CACHED_DDB_TABLE instead. Ensure any tests or code that
overwrite env in test runs can still patch the cached objects as needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66f38d40-f407-4cdc-a2f3-ff25761fd8d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0c6f3a9 and f0a7a08.

📒 Files selected for processing (1)
  • tcf_website/analytics_utils.py

Comment thread tcf_website/analytics_utils.py Outdated
Comment thread tcf_website/analytics_utils.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tcf_website/analytics_utils.py`:
- Around line 57-61: The module-level _DYNAMODB resource is not thread-safe and
is being shared by worker threads via _EXECUTOR and used in _send_to_dynamo (and
where _DYNAMODB.Table() is called); replace it with a thread-safe pattern by
either creating a module-level boto3.client("dynamodb", ...) (clients are
thread-safe) and update _send_to_dynamo and any table operations to use the
client API, or instantiate a new boto3.resource(...) inside the worker (inside
_send_to_dynamo) and call resource.Table(...) there so each thread uses its own
resource; update references to _DYNAMODB and _DYNAMODB.Table() accordingly
(e.g., in _send_to_dynamo) to use the chosen approach.

In `@tcf_website/views/index.py`:
- Around line 120-124: The current logic sorts raw rows in items by individual
row view_count then picks top 5, which lets one entity with multiple daily rows
occupy multiple slots; instead aggregate view_count by entity key (pk) first,
summing int(x.get("view_count",0)) for each unique pk, then produce a list of
aggregated items and sort that aggregated list by the summed view_count
descending to pick the top 5 (then derive course_ids from the aggregated pk
values); apply the same aggregation change to the analogous helper
get_trending_instructors() so it also sums view_count per pk before ranking.
- Around line 27-32: The module currently creates a module-global boto3 resource
and Table objects (_DYNAMODB and _TABLE) which are then used by
scan_table_paginated(), causing thread-safety issues; fix this by instantiating
a fresh DynamoDB resource or a low-level boto3.client inside the request handler
(the view function that calls scan_table_paginated()) and passing the resulting
Table or client into scan_table_paginated() instead of referencing the
module-global _TABLE, or refactor scan_table_paginated() to accept a
table/client parameter and create the resource there per-request; update all
references to _TABLE and _DYNAMODB to use the per-request object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 546a00a8-1097-4fbc-a15d-170de4b02bc9

📥 Commits

Reviewing files that changed from the base of the PR and between 77e11b5 and e15ceac.

📒 Files selected for processing (2)
  • tcf_website/analytics_utils.py
  • tcf_website/views/index.py

Comment thread tcf_website/analytics_utils.py Outdated
Comment thread tcf_website/views/index.py Outdated
Comment on lines +27 to +32
_DYNAMODB = boto3.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=env("AWS_REGION", default="us-east-1"),
).resource("dynamodb")
_TABLE = _DYNAMODB.Table(table_name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Are boto3 DynamoDB resource/Table objects safe to share across concurrent Django request threads? Please cite the official boto3 docs and note the recommended thread-safe pattern.

💡 Result:

No, boto3 DynamoDB resource and Table objects are not safe to share across concurrent Django request threads. Official boto3 documentation explicitly states that resource instances are not thread safe and should not be shared across threads or processes due to containing additional metadata that cannot be shared. This applies to DynamoDB resources like dynamodb = boto3.resource('dynamodb') and table = dynamodb.Table('my-table'). The recommended thread-safe pattern is to create a new resource (or client) for each thread by first creating a new session per thread: session = boto3.session.Session dynamodb = session.resource('dynamodb') table = dynamodb.Table('my-table') In a Django context with concurrent request threads (e.g., via Gunicorn uWSGI with threads), this means creating the resource or Table object anew in each view or request handler, rather than reusing a global or singleton instance. Note that while low-level clients (boto3.client('dynamodb')) are generally thread-safe once created from a session, the question specifically concerns resource/Table objects, which are not. For efficiency in Django, consider using a middleware or context manager to create per-request sessions/resources, but avoid global sharing.

Citations:


Avoid module-global DynamoDB Table objects in request code.

_TABLE is initialized once at import time and shared across all concurrent requests via scan_table_paginated(). In threaded Django deployments, DynamoDB resource/Table objects are not thread-safe and will cause issues. Create a new resource inside the request handler or use the low-level boto3 client instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/views/index.py` around lines 27 - 32, The module currently
creates a module-global boto3 resource and Table objects (_DYNAMODB and _TABLE)
which are then used by scan_table_paginated(), causing thread-safety issues; fix
this by instantiating a fresh DynamoDB resource or a low-level boto3.client
inside the request handler (the view function that calls scan_table_paginated())
and passing the resulting Table or client into scan_table_paginated() instead of
referencing the module-global _TABLE, or refactor scan_table_paginated() to
accept a table/client parameter and create the resource there per-request;
update all references to _TABLE and _DYNAMODB to use the per-request object.

Comment thread tcf_website/views/index.py Outdated
Comment on lines +120 to +124
# Safe sort that handles missing view counts
top_items = sorted(items, key=lambda x: int(x.get("view_count", 0)), reverse=True)[
:5
]
course_ids = [int(item["pk"].split(":")[1]) for item in top_items]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Aggregate per entity before picking the top 5.

The writer stores one row per entity per day (pk + sk=date; see tcf_website/analytics_utils.py Line 98), but these helpers rank raw rows. That makes "trending" use the best single day, not the total within the TTL window, and the same entity can consume multiple slots. Sum view_count by pk first, then rank the aggregated totals.

Possible fix
+from collections import defaultdict
...
-    top_items = sorted(items, key=lambda x: int(x.get("view_count", 0)), reverse=True)[
-        :5
-    ]
-    course_ids = [int(item["pk"].split(":")[1]) for item in top_items]
+    totals = defaultdict(int)
+    for item in items:
+        try:
+            totals[item["pk"]] += int(item.get("view_count", 0))
+        except (KeyError, TypeError, ValueError):
+            continue
+    course_ids = [
+        int(pk.split(":", 1)[1])
+        for pk, _ in sorted(totals.items(), key=lambda kv: kv[1], reverse=True)[:5]
+    ]

Apply the same pattern in get_trending_instructors().

Also applies to: 153-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/views/index.py` around lines 120 - 124, The current logic sorts
raw rows in items by individual row view_count then picks top 5, which lets one
entity with multiple daily rows occupy multiple slots; instead aggregate
view_count by entity key (pk) first, summing int(x.get("view_count",0)) for each
unique pk, then produce a list of aggregated items and sort that aggregated list
by the summed view_count descending to pick the top 5 (then derive course_ids
from the aggregated pk values); apply the same aggregation change to the
analogous helper get_trending_instructors() so it also sums view_count per pk
before ranking.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
tcf_website/analytics_utils.py (2)

18-18: ⚠️ Potential issue | 🟡 Minor

Clamp ANALYTICS_TTL_DAYS to a positive value.

Line 18 accepts non-positive TTL values, which makes Line 53 compute immediate/past expiration and risks premature item eviction.

Suggested hardening
-_TTL_DAYS = env.int("ANALYTICS_TTL_DAYS", default=7)
+_ttl_days_cfg = env.int("ANALYTICS_TTL_DAYS", default=7)
+if _ttl_days_cfg <= 0:
+    logger.warning("Invalid ANALYTICS_TTL_DAYS=%s; defaulting to 1", _ttl_days_cfg)
+_TTL_DAYS = _ttl_days_cfg if _ttl_days_cfg > 0 else 1

Also applies to: 53-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/analytics_utils.py` at line 18, The _TTL_DAYS configuration can
be zero or negative which causes immediate/past expirations; update the
initialization of _TTL_DAYS to clamp to a positive minimum (e.g., use max(1,
env.int("ANALYTICS_TTL_DAYS", default=7)) or validate and raise) so any
downstream expiration computation (where _TTL_DAYS is used to compute expiry)
always receives a positive TTL.

67-77: ⚠️ Potential issue | 🟠 Major

Handle executor submission failures to avoid leaked _pending and request-path exceptions.

At Line 68 you increment _pending before submit, but Line 77 can raise (e.g., executor shutdown/broken pool). That leaves _pending stuck and can propagate an exception to page-render paths.

Suggested fix
 def _fire_and_forget(entity_type: str, entity_id: int) -> None:
     if not _ANALYTICS_ENABLED: return
     global _pending
     with _pending_lock:
         if _pending >= _MAX_BACKLOG: return
         _pending += 1
@@
-    _EXECUTOR.submit(task_wrapper)
+    try:
+        _EXECUTOR.submit(task_wrapper)
+    except RuntimeError:
+        with _pending_lock:
+            _pending -= 1
+        logger.debug("Analytics executor unavailable; dropped %s:%s", entity_type, entity_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcf_website/analytics_utils.py` around lines 67 - 77, You increment the
global _pending before calling _EXECUTOR.submit(task_wrapper) which can raise
(executor shutdown), leaking _pending and surfacing exceptions to request paths;
fix by surrounding the submit call with try/except so that if
_EXECUTOR.submit(task_wrapper) raises you immediately decrement _pending under
_pending_lock (and then re-raise or handle/log the exception), or alternatively
only increment _pending after a successful submit; reference symbols: _pending,
_pending_lock, _EXECUTOR, task_wrapper, and _send_to_dynamo.
🧹 Nitpick comments (1)
.env.example (1)

52-58: Document ANALYTICS_TTL_DAYS in the analytics env block.

analytics_utils.py reads ANALYTICS_TTL_DAYS; including it here improves operability and avoids hidden defaults.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 52 - 58, The .env.example analytics block is
missing the ANALYTICS_TTL_DAYS variable used by analytics_utils.py; add a
commented line for ANALYTICS_TTL_DAYS under the Trending Analytics section with
a short description and a recommended default (e.g., number of days to retain
analytics, e.g., 30) so operators see the setting and there are no hidden
defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Line 58: Add a single trailing newline at the end of .env.example so the file
ends with a blank line (this will satisfy dotenv-linter's EndingBlankLine rule);
simply ensure the file ends after the commented DYNAMODB_TABLE_NAME line with
one final newline character.

---

Duplicate comments:
In `@tcf_website/analytics_utils.py`:
- Line 18: The _TTL_DAYS configuration can be zero or negative which causes
immediate/past expirations; update the initialization of _TTL_DAYS to clamp to a
positive minimum (e.g., use max(1, env.int("ANALYTICS_TTL_DAYS", default=7)) or
validate and raise) so any downstream expiration computation (where _TTL_DAYS is
used to compute expiry) always receives a positive TTL.
- Around line 67-77: You increment the global _pending before calling
_EXECUTOR.submit(task_wrapper) which can raise (executor shutdown), leaking
_pending and surfacing exceptions to request paths; fix by surrounding the
submit call with try/except so that if _EXECUTOR.submit(task_wrapper) raises you
immediately decrement _pending under _pending_lock (and then re-raise or
handle/log the exception), or alternatively only increment _pending after a
successful submit; reference symbols: _pending, _pending_lock, _EXECUTOR,
task_wrapper, and _send_to_dynamo.

---

Nitpick comments:
In @.env.example:
- Around line 52-58: The .env.example analytics block is missing the
ANALYTICS_TTL_DAYS variable used by analytics_utils.py; add a commented line for
ANALYTICS_TTL_DAYS under the Trending Analytics section with a short description
and a recommended default (e.g., number of days to retain analytics, e.g., 30)
so operators see the setting and there are no hidden defaults.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3521b372-49f0-41ee-86dd-2ef94e6492a6

📥 Commits

Reviewing files that changed from the base of the PR and between e15ceac and 0e3855c.

📒 Files selected for processing (2)
  • .env.example
  • tcf_website/analytics_utils.py

Comment thread .env.example
# AWS_ANALYTICS_ACCESS_KEY_ID=
# AWS_ANALYTICS_SECRET_ACCESS_KEY=
# AWS_REGION=us-east-1
# DYNAMODB_TABLE_NAME=trending_analytics No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a trailing blank line at EOF to satisfy dotenv-linter.

Static analysis flags EndingBlankLine on Line 58.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 58-58: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example at line 58, Add a single trailing newline at the end of
.env.example so the file ends with a blank line (this will satisfy
dotenv-linter's EndingBlankLine rule); simply ensure the file ends after the
commented DYNAMODB_TABLE_NAME line with one final newline character.

@ArislanT
Copy link
Copy Markdown
Collaborator Author

Had to make changes to refactor towards the new UI changes. Should be complete towards it

@ArislanT ArislanT closed this Apr 19, 2026
@ArislanT
Copy link
Copy Markdown
Collaborator Author

Superseded by #1248 which consolidates backend and frontend into one PR

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

Labels

Feature New feature Performance Performance improvements, refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant