Skip to content

[MISC] Add --active-only filter to backfill_metrics command#1824

Merged
Deepak-Kesavan merged 6 commits intomainfrom
misc/backfill-metrics-active-filter
Mar 5, 2026
Merged

[MISC] Add --active-only filter to backfill_metrics command#1824
Deepak-Kesavan merged 6 commits intomainfrom
misc/backfill-metrics-active-filter

Conversation

@athul-rs
Copy link
Copy Markdown
Contributor

@athul-rs athul-rs commented Mar 4, 2026

What

  • Add --active-only flag to backfill_metrics management command to filter organizations during backfill
  • Extract org resolution logic into _resolve_org_ids() method
  • Add progress counter [1/N] to org processing output
  • Fix logger calls to use lazy %s formatting instead of f-strings

Why

  • Running backfill across all orgs in production is wasteful when most orgs are inactive
  • --active-only reduces the set to only active orgs, significantly cutting backfill time
  • In OSS mode where the filtering module is not available, the flag is silently ignored with a warning

How

  • Added --active-only CLI argument with graceful import-based fallback for OSS compatibility
  • Extracted _resolve_org_ids() to cleanly handle single-org, all-org, and filtered-org modes
  • Added [i/N] progress output for each org processed

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. Default behavior (without --active-only) is unchanged. The flag is additive and the import fallback ensures OSS mode works as before.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

  • N/A

Related Issues or PRs

  • N/A

Dependencies Versions

  • No new dependencies

Notes on Testing

  • Run backfill_metrics --days=7 --dry-run — verify all orgs are listed
  • Run backfill_metrics --days=7 --active-only --dry-run — verify filtered org count
  • Run without --active-only — verify backward-compatible behavior
  • Verify OSS mode gracefully ignores --active-only with warning message

Checklist

I have read and understood the Contribution Guidelines.

Allow filtering organizations during backfill to only process
those with active status. Adds graceful fallback for OSS mode.
Also improves progress output with [n/N] counter, extracts org
resolution into _resolve_org_ids(), and fixes logger formatting
to use lazy %s instead of f-strings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 4, 2026

Summary by CodeRabbit

  • New Features

    • Added an --active-only flag to filter organizations by active subscriptions during metrics backfill.
  • Improvements

    • Per-organization processing with progress indicators and clearer per-org logging.
    • Graceful fallback when subscription-based filtering isn't available.
    • Dry-run and processing messages updated to reflect per-org flow.
  • Bug Fixes

    • Improved error and result logging to include metric name and organization context.

Walkthrough

Adds an --active-only CLI flag and a _resolve_org_ids() helper to the backfill metrics management command, adds conditional Subscription import detection (HAS_SUBSCRIPTION), refactors to iterate resolved org IDs with per-org progress, and improves per-org error/log context.

Changes

Cohort / File(s) Summary
Backfill Metrics Command
backend/dashboard_metrics/management/commands/backfill_metrics.py
Added --active-only argument and `def _resolve_org_ids(self, org_id: str
sequenceDiagram
    participant CLI as CLI
    participant Cmd as BackfillCommand
    participant Sub as SubscriptionModel
    participant Org as OrganizationModel
    participant Collector as MetricsCollector
    participant Log as Logger

    CLI->>Cmd: run backfill_metrics (--active-only?, org_id?)
    Cmd->>Cmd: parse args
    Cmd->>Sub: conditional import check (HAS_SUBSCRIPTION)
    Cmd->>Cmd: _resolve_org_ids(org_id, active_only)
    alt HAS_SUBSCRIPTION and active_only
        Cmd->>Sub: query active subscriptions -> org ids
    else fallback / no subscription support
        Cmd->>Org: query all org ids (or validate single org)
    end
    Cmd->>Cmd: iterate resolved org ids [i/total]
    loop per org
        Cmd->>Collector: collect metrics for current_org_id
        Collector-->>Cmd: success / errors
        alt error
            Cmd->>Log: log metric name + current_org_id + error
        end
    end
    Cmd->>Log: final summary (errors/counts)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 PR title clearly summarizes the main change: adding a --active-only filter to the backfill_metrics command. It is concise, specific, and directly related to the primary feature introduced in the changeset.
Description check ✅ Passed The PR description comprehensively addresses all required template sections including What, Why, How, breaking changes assessment, migrations, config, testing notes, and checklist. All critical sections are filled with sufficient detail.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch misc/backfill-metrics-active-filter

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.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@backend/dashboard_metrics/management/commands/backfill_metrics.py`:
- Around line 202-207: The single-org branch bypasses the --active-only filter,
so when --org-id is provided inactive orgs can be processed; update the logic in
the function that returns org IDs (the branch using
Organization.objects.filter(id=org_id).exists()) to honor the --active-only
flag: either make --org-id and --active-only mutually exclusive in argument
parsing, or (preferred) when active_only is true, check
Organization.objects.filter(id=org_id, is_active=True).exists() and error/return
if not active; reference Organization and the code path that currently returns
[org_id] for single-org mode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5bf7e7e-0729-48ff-bc0a-a5698a69f465

📥 Commits

Reviewing files that changed from the base of the PR and between e0c2af0 and 59d9969.

📒 Files selected for processing (1)
  • backend/dashboard_metrics/management/commands/backfill_metrics.py

Comment thread backend/dashboard_metrics/management/commands/backfill_metrics.py Outdated
athul-rs and others added 2 commits March 5, 2026 00:10
Organization.id is the auto-increment int PK, but
Subscription.organization_id and all service query methods use
Organization.organization_id (the Auth0 org identifier string).
The set intersection was always empty due to this type mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@muhammad-ali-e muhammad-ali-e left a comment

Choose a reason for hiding this comment

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

left one comment

Comment thread backend/dashboard_metrics/management/commands/backfill_metrics.py Outdated
Subscription.organization_id stores Auth0 org strings (org_*) while
all downstream service queries and bulk upserts use Organization.id
(int PK) via FK references. Map subscription org strings back to
Organization PKs via organization_id__in lookup to ensure correct
downstream behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@muhammad-ali-e muhammad-ali-e left a comment

Choose a reason for hiding this comment

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

Code Review

Summary

PR adds --active-only flag to backfill_metrics management command, extracts org resolution into _resolve_org_ids(), adds progress counters, and fixes logger formatting. However, the refactoring introduces a critical data type mismatch: the new code passes Organization.organization_id (Auth0 string identifier) where the downstream code expects Organization.id (integer PK).

Automated Checks

  • ruff check: PASS
  • ruff format: PASS

Findings (confidence >= 80 only)

Critical [must fix]

# File:Line Category Confidence Finding Suggestion
1 backfill_metrics.py (_resolve_org_ids) Bug 98 _resolve_org_ids() returns Organization.organization_id (Auth0 string like "org_abc123") but _collect_metrics() passes it to MetricsQueryService methods that expect Organization.id (integer PK). For example, get_pages_processed() does Organization.objects.get(id=organization_id) at services.py:141 — this will raise DoesNotExist or ValueError when given a string identifier instead of an integer PK. Return Organization PKs (values_list("id", flat=True)) from _resolve_org_ids(), not organization_id CharField values. Or update all MetricsQueryService methods to accept the Auth0 identifier — but that's a much larger change.
2 backfill_metrics.py (_resolve_org_ids) Bug 96 Bulk upsert methods do EventMetricsHourly(organization_id=org_id, ...) where organization_id is the FK column (stores Organization PK). Passing Auth0 string identifiers will cause integrity errors or create orphaned records. Same fix as #1 — ensure _resolve_org_ids() returns Organization PKs.
3 backfill_metrics.py (_resolve_org_ids single-org) Bug 92 Single-org mode: Organization.objects.filter(organization_id=org_id) validates using the Auth0 identifier, but returns it to be passed to _collect_metrics() which expects PK. The usage doc was changed from --org-id=12 to --org-id=org_abc123 but downstream code still expects PKs. This is a breaking change to the --org-id CLI semantics without updating the consumer. Either keep --org-id as PK and do Organization.objects.filter(id=org_id), or convert the Auth0 identifier back to PK before returning: Organization.objects.filter(organization_id=org_id).values_list("id", flat=True).

Important [should fix]

# File:Line Category Confidence Finding Suggestion
4 backfill_metrics.py (handle loop) Code Quality 82 Progress output only shows current_org_id (a raw string identifier) without the org display name. The old code showed org.display_name ({org_id_str}) which was more useful for operators. Fetch the Organization object (or at minimum the display_name) and include it in the progress message.

Suggested Fix

Change _resolve_org_ids() to return PKs:

# All org mode
all_org_ids = set(Organization.objects.values_list("id", flat=True))

# Active filter - need to map Subscription.organization_id → Organization.id
if active_only and HAS_SUBSCRIPTION:
    active_auth0_ids = set(
        Subscription.objects.filter(is_active=True)
        .values_list("organization_id", flat=True)
    )
    all_org_ids = set(
        Organization.objects.filter(organization_id__in=active_auth0_ids)
        .values_list("id", flat=True)
    )

# Single org mode - decide: is --org-id a PK or Auth0 identifier? Document clearly.

Confluence Checklist

  • ✅ Appropriate PR title and description
  • ✅ Code follows style guidelines (ruff passes)
  • ⚠️ Self-review — the PK vs organization_id mismatch suggests the interaction with MetricsQueryService wasn't verified end-to-end
  • ❌ Tests — no test files added or modified
  • ✅ No database migrations needed
  • ✅ No new dependencies
  • ✅ Backward compatible (when bug is fixed)

Verdict: REQUEST CHANGES

The core refactoring has a critical data type mismatch. _resolve_org_ids() returns Organization.organization_id (Auth0 string identifiers) but all downstream consumers — MetricsQueryService methods and bulk upsert calls — expect Organization.id (integer PK). This will cause runtime failures for every org processed.

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)
backend/dashboard_metrics/management/commands/backfill_metrics.py (1)

216-226: Push dedup/filter work to the database to reduce Python memory churn.

set(...) materialization here works, but this can be done with DB-level distinct() and subquery composition, which scales better for larger org/subscription tables.

Refactor sketch
-            active_org_id_strings = set(
-                Subscription.objects.filter(is_active=True).values_list(
-                    "organization_id", flat=True
-                )
-            )
-            # Map org_* strings back to Organization PKs
-            all_org_ids = set(
-                Organization.objects.filter(
-                    organization_id__in=active_org_id_strings
-                ).values_list("id", flat=True)
-            )
+            active_org_id_strings = (
+                Subscription.objects.filter(is_active=True)
+                .values_list("organization_id", flat=True)
+                .distinct()
+            )
+            # Map org_* strings back to Organization PKs
+            all_org_ids = (
+                Organization.objects.filter(organization_id__in=active_org_id_strings)
+                .values_list("id", flat=True)
+                .distinct()
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/dashboard_metrics/management/commands/backfill_metrics.py` around
lines 216 - 226, The current code materializes Subscription and Organization IDs
into Python sets (active_org_id_strings and all_org_ids) which causes
unnecessary memory churn; instead push deduping to the DB by using
QuerySet.distinct() and subquery composition: get the distinct subscription
organization_id via
Subscription.objects.filter(is_active=True).values_list("organization_id",
flat=True).distinct() (or wrap as a Subquery), then use that QuerySet/Subquery
in Organization.objects.filter(organization_id__in=...) to fetch Organization
IDs (and call .values_list("id", flat=True).distinct()) so no large set(...)
materialization is done in Python and the database handles deduplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/dashboard_metrics/management/commands/backfill_metrics.py`:
- Around line 206-210: The org lookup can raise on malformed --org-id; wrap the
Organization.objects.filter(id=org_id).first() call in a try/except that catches
ValueError and django.core.exceptions.ValidationError (and optionally
TypeError), write a friendly error to self.stderr via self.style.ERROR (e.g.,
"Invalid organization id: {org_id}") and return [] on exception, then proceed to
the existing "not found" path if lookup returns None; update imports if needed
to include ValidationError.
- Around line 32-37: The import try/except around "from
pluggable_apps.subscription_v2.models import Subscription" should only swallow
module-not-found for the pluggable_apps package path and re-raise other import
errors; change the except to catch ModuleNotFoundError (not ImportError) and
only set HAS_SUBSCRIPTION = False when exc.name is "pluggable_apps" or
startswith "pluggable_apps."; otherwise re-raise the exception so import-time
errors inside subscription_v2 aren't silently ignored (update the block that
defines Subscription and HAS_SUBSCRIPTION).

---

Nitpick comments:
In `@backend/dashboard_metrics/management/commands/backfill_metrics.py`:
- Around line 216-226: The current code materializes Subscription and
Organization IDs into Python sets (active_org_id_strings and all_org_ids) which
causes unnecessary memory churn; instead push deduping to the DB by using
QuerySet.distinct() and subquery composition: get the distinct subscription
organization_id via
Subscription.objects.filter(is_active=True).values_list("organization_id",
flat=True).distinct() (or wrap as a Subquery), then use that QuerySet/Subquery
in Organization.objects.filter(organization_id__in=...) to fetch Organization
IDs (and call .values_list("id", flat=True).distinct()) so no large set(...)
materialization is done in Python and the database handles deduplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1cbb1d23-21b8-4e3c-8d41-81d38f6c0169

📥 Commits

Reviewing files that changed from the base of the PR and between b4e0dfd and 812de00.

📒 Files selected for processing (1)
  • backend/dashboard_metrics/management/commands/backfill_metrics.py

Comment thread backend/dashboard_metrics/management/commands/backfill_metrics.py
Comment thread backend/dashboard_metrics/management/commands/backfill_metrics.py
@athul-rs
Copy link
Copy Markdown
Contributor Author

athul-rs commented Mar 5, 2026

Code Review

Summary

PR adds --active-only flag to backfill_metrics management command, extracts org resolution into _resolve_org_ids(), adds progress counters, and fixes logger formatting. However, the refactoring introduces a critical data type mismatch: the new code passes Organization.organization_id (Auth0 string identifier) where the downstream code expects Organization.id (integer PK).

Automated Checks

  • ruff check: PASS
  • ruff format: PASS

Findings (confidence >= 80 only)

Critical [must fix]

File:Line Category Confidence Finding Suggestion

1 backfill_metrics.py (_resolve_org_ids) Bug 98 _resolve_org_ids() returns Organization.organization_id (Auth0 string like "org_abc123") but _collect_metrics() passes it to MetricsQueryService methods that expect Organization.id (integer PK). For example, get_pages_processed() does Organization.objects.get(id=organization_id) at services.py:141 — this will raise DoesNotExist or ValueError when given a string identifier instead of an integer PK. Return Organization PKs (values_list("id", flat=True)) from _resolve_org_ids(), not organization_id CharField values. Or update all MetricsQueryService methods to accept the Auth0 identifier — but that's a much larger change.
2 backfill_metrics.py (_resolve_org_ids) Bug 96 Bulk upsert methods do EventMetricsHourly(organization_id=org_id, ...) where organization_id is the FK column (stores Organization PK). Passing Auth0 string identifiers will cause integrity errors or create orphaned records. Same fix as #1 — ensure _resolve_org_ids() returns Organization PKs.
3 backfill_metrics.py (_resolve_org_ids single-org) Bug 92 Single-org mode: Organization.objects.filter(organization_id=org_id) validates using the Auth0 identifier, but returns it to be passed to _collect_metrics() which expects PK. The usage doc was changed from --org-id=12 to --org-id=org_abc123 but downstream code still expects PKs. This is a breaking change to the --org-id CLI semantics without updating the consumer. Either keep --org-id as PK and do Organization.objects.filter(id=org_id), or convert the Auth0 identifier back to PK before returning: Organization.objects.filter(organization_id=org_id).values_list("id", flat=True).

Important [should fix]

File:Line Category Confidence Finding Suggestion

4 backfill_metrics.py (handle loop) Code Quality 82 Progress output only shows current_org_id (a raw string identifier) without the org display name. The old code showed org.display_name ({org_id_str}) which was more useful for operators. Fetch the Organization object (or at minimum the display_name) and include it in the progress message.

Suggested Fix

Change _resolve_org_ids() to return PKs:

# All org mode
all_org_ids = set(Organization.objects.values_list("id", flat=True))

# Active filter - need to map Subscription.organization_id → Organization.id
if active_only and HAS_SUBSCRIPTION:
    active_auth0_ids = set(
        Subscription.objects.filter(is_active=True)
        .values_list("organization_id", flat=True)
    )
    all_org_ids = set(
        Organization.objects.filter(organization_id__in=active_auth0_ids)
        .values_list("id", flat=True)
    )

# Single org mode - decide: is --org-id a PK or Auth0 identifier? Document clearly.

Confluence Checklist

  • ✅ Appropriate PR title and description
  • ✅ Code follows style guidelines (ruff passes)
  • ⚠️ Self-review — the PK vs organization_id mismatch suggests the interaction with MetricsQueryService wasn't verified end-to-end
  • ❌ Tests — no test files added or modified
  • ✅ No database migrations needed
  • ✅ No new dependencies
  • ✅ Backward compatible (when bug is fixed)

Verdict: REQUEST CHANGES

The core refactoring has a critical data type mismatch. _resolve_org_ids() returns Organization.organization_id (Auth0 string identifiers) but all downstream consumers — MetricsQueryService methods and bulk upsert calls — expect Organization.id (integer PK). This will cause runtime failures for every org processed.

This was addressed in earlier commit

Wrap Organization PK lookup in try/except so non-integer values
(e.g. --org-id=abc) show a friendly "not found" error instead of
crashing with an unhandled ValueError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 5, 2026

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 63 passed, 0 failed (63 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{63}}$$ $$\textcolor{#23d18b}{\tt{63}}$$

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Mar 5, 2026

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.

♻️ Duplicate comments (1)
backend/dashboard_metrics/management/commands/backfill_metrics.py (1)

207-210: ⚠️ Potential issue | 🟡 Minor

Catch ValidationError for malformed --org-id.

This lookup can still raise django.core.exceptions.ValidationError (commonly with malformed UUID input), so the command may abort before reaching the friendly “not found” path.

Patch suggestion
+from django.core.exceptions import ValidationError
...
         if org_id:
             try:
                 org = Organization.objects.filter(id=org_id).first()
-            except (ValueError, TypeError):
+            except (ValueError, TypeError, ValidationError):
                 org = None
#!/bin/bash
set -euo pipefail

# Verify Organization PK type and current exception handling
fd "models.py" | xargs rg -n --type=py -C3 "class Organization\\(|id\\s*=\\s*models\\.(UUIDField|AutoField|BigAutoField|IntegerField)"
rg -n --type=py -C3 "_resolve_org_ids|except \\(ValueError, TypeError\\)|ValidationError" backend/dashboard_metrics/management/commands/backfill_metrics.py

Expected result: if Organization.id is a UUIDField, ValidationError should be handled in this branch.

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

In `@backend/dashboard_metrics/management/commands/backfill_metrics.py` around
lines 207 - 210, The current try/except around
Organization.objects.filter(id=org_id).first() only catches ValueError and
TypeError but a malformed UUID can raise django.core.exceptions.ValidationError;
update the exception handling in the block that resolves org_id (the call
performing Organization.objects.filter(id=org_id).first()) to also catch
ValidationError (imported as from django.core.exceptions import ValidationError)
so malformed UUID inputs fall back to org = None and proceed to the existing
“not found” handling.
🧹 Nitpick comments (2)
backend/dashboard_metrics/management/commands/backfill_metrics.py (2)

71-75: Clarify --org-id help text to indicate PK semantics.

The current text is ambiguous with Organization.organization_id also in play. Explicitly stating “Organization.id (PK)” will reduce operator confusion.

Patch suggestion
         parser.add_argument(
             "--org-id",
             type=str,
             default=None,
-            help="Specific organization ID to backfill (default: all orgs)",
+            help="Specific organization PK (Organization.id) to backfill (default: all orgs)",
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/dashboard_metrics/management/commands/backfill_metrics.py` around
lines 71 - 75, Update the help string for the "--org-id" argument in the
management command (inside add_arguments of Command in backfill_metrics.py) to
explicitly state that it expects the Organization primary key; replace the
ambiguous text "Specific organization ID to backfill (default: all orgs)" with
something like "Specific Organization.id (primary key) to backfill; if omitted,
backfills all organizations" so operators know to supply the PK rather than
Organization.organization_id.

142-145: Optional: include org display name in progress output.

For long backfills, printing <org_id> (<name>) makes operator monitoring much easier.

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

In `@backend/dashboard_metrics/management/commands/backfill_metrics.py` around
lines 142 - 145, Update the progress print in the backfill loop to include the
org display name alongside the org id: inside the loop over org_ids (variables
org_ids, current_org_id, i) fetch the Organization display name (e.g., via the
Organization model or existing lookup used elsewhere in backfill_metrics.py) and
change the self.stdout.write call to print "{org_id} ({display_name})" with a
safe fallback to the id if the name is missing; ensure you reference
org_ids/current_org_id and self.stdout.write when making the change so it works
for long-running backfills.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@backend/dashboard_metrics/management/commands/backfill_metrics.py`:
- Around line 207-210: The current try/except around
Organization.objects.filter(id=org_id).first() only catches ValueError and
TypeError but a malformed UUID can raise django.core.exceptions.ValidationError;
update the exception handling in the block that resolves org_id (the call
performing Organization.objects.filter(id=org_id).first()) to also catch
ValidationError (imported as from django.core.exceptions import ValidationError)
so malformed UUID inputs fall back to org = None and proceed to the existing
“not found” handling.

---

Nitpick comments:
In `@backend/dashboard_metrics/management/commands/backfill_metrics.py`:
- Around line 71-75: Update the help string for the "--org-id" argument in the
management command (inside add_arguments of Command in backfill_metrics.py) to
explicitly state that it expects the Organization primary key; replace the
ambiguous text "Specific organization ID to backfill (default: all orgs)" with
something like "Specific Organization.id (primary key) to backfill; if omitted,
backfills all organizations" so operators know to supply the PK rather than
Organization.organization_id.
- Around line 142-145: Update the progress print in the backfill loop to include
the org display name alongside the org id: inside the loop over org_ids
(variables org_ids, current_org_id, i) fetch the Organization display name
(e.g., via the Organization model or existing lookup used elsewhere in
backfill_metrics.py) and change the self.stdout.write call to print "{org_id}
({display_name})" with a safe fallback to the id if the name is missing; ensure
you reference org_ids/current_org_id and self.stdout.write when making the
change so it works for long-running backfills.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec1c5954-0894-473d-ab0d-5e883cbba909

📥 Commits

Reviewing files that changed from the base of the PR and between 812de00 and 0d43511.

📒 Files selected for processing (1)
  • backend/dashboard_metrics/management/commands/backfill_metrics.py

@athul-rs athul-rs requested a review from Deepak-Kesavan March 5, 2026 04:47
@Deepak-Kesavan Deepak-Kesavan merged commit 8202c28 into main Mar 5, 2026
7 checks passed
@Deepak-Kesavan Deepak-Kesavan deleted the misc/backfill-metrics-active-filter branch March 5, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants