Skip to content

feat(workspaces): add statistics and metrics#20784

Open
nijel wants to merge 1 commit into
WeblateOrg:mainfrom
nijel:stats-workspace
Open

feat(workspaces): add statistics and metrics#20784
nijel wants to merge 1 commit into
WeblateOrg:mainfrom
nijel:stats-workspace

Conversation

@nijel

@nijel nijel commented Jul 20, 2026

Copy link
Copy Markdown
Member

Expose aggregate translation health and historical trends at workspace scope. Keep metric lookups integer-indexed and batch the management listing to avoid per-workspace queries.

@nijel nijel added this to the 2026.8 milestone Jul 20, 2026
@nijel
nijel requested a review from amCap1712 July 20, 2026 18:54
@nijel nijel self-assigned this Jul 20, 2026
@nijel

nijel commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

@amCap1712 I'd like to hear your opinion, especially on linking Metric to Workspace. I want to keep UUID as the primary key on Workspace and Metric uses int to reference the related object. I've chosen using sequence-based metric_id on Workspace, but there are definitely other options like converting the reference on Metric to char and storing UUID directly there.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

https://github.com/WeblateOrg/weblate/blob/e3e9a36b63c5e5879f229f91e88dd0e5a897b863/templates/workspace.html#L119-L120
P1 Badge Restrict workspace statistics to visible projects

When a visitor can access any public/protected project in a workspace but not another private project, detail() permits the workspace page based on request.user.allowed_projects while the information tab renders the unfiltered workspace.stats. WorkspaceStats.get_child_objects() aggregates workspace.projects without the request user's access filter, so this newly exposed tab reveals private projects' aggregate string/word/check/progress data (and historical contributor totals) to users who cannot view those projects; the existing project list correctly omits them.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@nijel
nijel force-pushed the stats-workspace branch from e3e9a36 to 997afca Compare July 21, 2026 05:31
Copilot AI review requested due to automatic review settings July 21, 2026 05:31

Copilot AI left a comment

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.

Pull request overview

Adds workspace-scoped aggregate translation statistics and historical metrics, integrating workspaces into the existing stats/metrics infrastructure while optimizing the management listing to avoid per-workspace queries.

Changes:

  • Introduces WorkspaceStats aggregation and schedules workspace stats refreshes when projects move or component topology changes.
  • Adds Workspace.metric_id (integer sequence) to support integer-indexed workspace metrics, plus workspace metric collection and lifecycle hooks.
  • Updates workspace UI and management pages to display workspace info/metrics and to support sortable listings with preserved search parameters.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
weblate/workspaces/tests.py Adds tests for metric_id behavior and workspace stats aggregation/refresh triggers.
weblate/workspaces/models.py Adds metric_id, WorkspaceStats wiring, and billing_or_none helper.
weblate/workspaces/migrations/0004_workspace_metric_id.py Creates the metric ID sequence and adds the metric_id field.
weblate/wladmin/views.py Batches workspace listing annotations/prefetching and prepares language counts for uncached workspace stats.
weblate/wladmin/tests.py Extends management UI tests for stats sorting, search preservation, and query batching.
weblate/utils/tasks.py Adds update_workspace_stats task and ensures topology updates include workspace aggregates.
weblate/utils/stats.py Adds cache helper has_cached_data, workspace aggregation class, and workspace update ordering.
weblate/trans/templatetags/translations.py Extends show_info context to accept a workspace.
weblate/trans/models/project.py Schedules workspace stats refresh when a project’s workspace changes.
weblate/trans/mixins.py Updates cache key typing protocol to allow UUID IDs.
weblate/templates/workspace.html Adds “Information” tab and renders workspace info + metrics.
weblate/templates/snippets/list-objects.html Reuses listing table for workspaces, adds columns, and preserves query_string across sorts.
weblate/templates/snippets/info.html Adds workspace-specific quick metrics blocks and disables contributor link for workspace context.
weblate/templates/manage/workspaces.html Switches workspace management page to the shared list-objects snippet.
weblate/metrics/tests.py Adds workspace metric collection and deletion lifecycle tests.
weblate/metrics/templatetags/metrics.py Enables `
weblate/metrics/tasks.py Extends periodic metrics collection to include workspaces.
weblate/metrics/models.py Adds workspace metric scope, collection, and create/delete hooks.
weblate/billing/views.py Triggers workspace stats refresh when billing merge moves projects between workspaces.
weblate/billing/models.py Triggers workspace stats refresh when billing changes a project’s workspace.
docs/changes.rst Adds changelog entry announcing workspace statistics/metrics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread weblate/workspaces/models.py Outdated
@property
def billing_or_none(self) -> Billing | None:
"""Associated billing, or none for unbilled workspaces."""
with suppress(ObjectDoesNotExist):
Copilot AI review requested due to automatic review settings July 21, 2026 07:01
@nijel
nijel force-pushed the stats-workspace branch from 997afca to f535dea Compare July 21, 2026 07:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f535dea499

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread weblate/metrics/models.py
Comment on lines +424 to +429
"components": Component.objects.filter(
project__workspace=workspace
).count(),
"translations": Translation.objects.filter(
component__project__workspace=workspace
).count(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include linked components in workspace metrics

For a workspace containing a project that consumes a component through ComponentLink, these filters only count components owned by a project in the workspace, excluding the linked component and its translations (and the analogous screenshots filter below). This disagrees with WorkspaceStats, whose project aggregates include shared components, so the new workspace Overview can show hosted strings from the linked component but zero/understated Components and Translations counts. Build these metrics from the workspace's effective project components, including component__links__workspace, as is done for category metrics.

Useful? React with 👍 / 👎.

Comment on lines +119 to +120
<div class="tab-pane" id="information">
{% show_info workspace=workspace stats=workspace.stats metrics=workspace|metrics show_source=True %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict workspace statistics to visible projects

When a workspace contains both an accessible public/protected project and a private project, this renders the unfiltered workspace.stats and workspace metrics to a visitor who is admitted solely through the accessible project. The detail view deliberately filters the project listing through request.user.allowed_projects, but WorkspaceStats aggregates every workspace.projects entry; consequently the Overview exposes private-project string, translation, check, and contributor totals to users who cannot access that project. Compute the displayed stats from the permitted projects or require workspace-level access before rendering this tab.

Useful? React with 👍 / 👎.

Comment thread weblate/wladmin/views.py
Comment on lines +1116 to +1119
sort_by = self.request.GET.get("sort_by")
if sort_by and sort_by.lstrip("-") != "name":
queryset = prefetch_stats(queryset)
self.prepare_workspace_stats(queryset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat reverse name sorting as stat-free

When an administrator clicks the Name header to request sort_by=-name, this guard skips the explicit prefetch, but get_paginator(..., stats=True) treats every value other than exactly name as requiring statistics and calls prefetch_stats across the entire workspace queryset before sorting. Reverse alphabetical sorting therefore performs unnecessary cache/stat work for every workspace, which can make this otherwise simple management operation slow on large installations. Normalize the leading - before deciding whether stats are needed.

Useful? React with 👍 / 👎.

Copilot AI left a comment

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.

Pull request overview

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

Comments suppressed due to low confidence (1)

weblate/workspaces/models.py:310

  • billing_or_none only suppresses ObjectDoesNotExist, but when the billing app is disabled the reverse accessor (Workspace.billing) does not exist and this property will raise AttributeError if accessed. It should be safe to call regardless of INSTALLED_APPS.
    @property
    def billing_or_none(self) -> Billing | None:
        """Associated billing, or none for unbilled workspaces."""
        with suppress(ObjectDoesNotExist):
            return self.billing
        return None

Comment thread weblate/wladmin/views.py
Comment on lines +1115 to +1126
queryset = result["object_list"]
sort_by = self.request.GET.get("sort_by")
if sort_by and sort_by.lstrip("-") != "name":
queryset = prefetch_stats(queryset)
self.prepare_workspace_stats(queryset)
workspaces = get_paginator(
self.request,
queryset,
page_limit=50,
stats=True,
sort_by=sort_by,
)
Comment thread weblate/wladmin/views.py
Comment on lines +1144 to +1148
result["show_review_columns"] = (
Project.objects.filter(workspace__in=queryset)
.filter(Q(source_review=True) | Q(translation_review=True))
.exists()
)
Expose aggregate translation health and historical trends at workspace scope. Keep metric lookups integer-indexed and batch the management listing to avoid per-workspace queries.
Copilot AI review requested due to automatic review settings July 21, 2026 07:09
@nijel
nijel force-pushed the stats-workspace branch from f535dea to 0d3a8ae Compare July 21, 2026 07:09

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

weblate/wladmin/views.py:1094

  • prepare_workspace_stats() can run the (potentially expensive) translation-language union query twice when sorting by non-name columns: it’s called once before pagination (lines 1117–1120) and again after pagination (line 1127). Because missing only checks has_cached_data, the second call repeats the work even though stats_languages was already populated.

Consider skipping workspaces that already have stats_languages set so the second call becomes a no-op for those objects.

        """Batch language counts needed to initialize uncached workspace stats."""
        missing = [
            workspace for workspace in workspaces if not workspace.stats.has_cached_data
        ]

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.

2 participants