From 89b482425cffb95cd9c0ccd060067cbb6746a895 Mon Sep 17 00:00:00 2001 From: "Teodoro B. Mendes" Date: Fri, 31 Jul 2026 19:24:48 -0300 Subject: [PATCH 1/2] feat: backfill achievements from the weekly release pipeline --- .../management/commands/release_tasks.py | 2 + libraries/tasks.py | 15 ++++++ libraries/tests/test_tasks.py | 49 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/libraries/management/commands/release_tasks.py b/libraries/management/commands/release_tasks.py index b8f900941..4557dabd7 100644 --- a/libraries/management/commands/release_tasks.py +++ b/libraries/management/commands/release_tasks.py @@ -61,6 +61,8 @@ def set_tasks(self): Action("Updating slack activity buckets", ["fetch_slack_activity"]), Action("Updating website statistics", self.update_website_statistics), Action("Importing mailing list counts", self.import_ml_counts), + # Last, so every source it reads has already been refreshed above. + Action("Backfilling achievements", ["backfill_achievements"]), # Action("Generating report", self.generate_report), ] diff --git a/libraries/tasks.py b/libraries/tasks.py index b69652828..d40dfe9e1 100644 --- a/libraries/tasks.py +++ b/libraries/tasks.py @@ -280,6 +280,17 @@ def update_authors_and_maintainers(): call_command("update_maintainers") call_command("update_library_version_authors", "--clean") app.signature("users.tasks.recompute_displayed_profile_roles").apply_async() + # Only the sources whose upstream data just changed. A blanket backfill would + # also sweep the commit, review and news tables this task never touches. + call_command( + "backfill_achievements", + "--source", + "library-authoring", + "--source", + "library-maintenance", + "--source", + "library-versioning", + ) @app.task @@ -296,6 +307,10 @@ def update_commits(token=None, clean=False, min_version=""): ) logger.info("update_commits finished.") app.signature("users.tasks.recompute_displayed_profile_roles").apply_async() + # No achievement backfill here on purpose: this runs as a step of the + # release_tasks command, which sweeps every source once at the end. Calling + # it here too would walk the whole Commit table twice per release. Ad hoc + # runs use the "Backfill achievements" button in the badges admin. return commits_handled diff --git a/libraries/tests/test_tasks.py b/libraries/tests/test_tasks.py index cffa59341..a05b2bfd8 100644 --- a/libraries/tests/test_tasks.py +++ b/libraries/tests/test_tasks.py @@ -164,3 +164,52 @@ def test_update_library_version_website_adoc_no_stable_release(): with patch("libraries.tasks.store_library_version_website_adoc") as mock_store: update_library_version_website_adoc() mock_store.assert_not_called() + + +@patch("libraries.tasks.call_command") +def test_update_authors_and_maintainers_backfills_only_library_sources(mock_call): + """A blanket backfill here would sweep the commit, review and news tables.""" + from libraries.tasks import update_authors_and_maintainers + + update_authors_and_maintainers() + + backfills = [ + c for c in mock_call.call_args_list if c.args[0] == "backfill_achievements" + ] + assert len(backfills) == 1 + assert set(backfills[0].args[1:]) == { + "--source", + "library-authoring", + "library-maintenance", + "library-versioning", + } + + +@patch("libraries.tasks.LibraryUpdater") +@patch("libraries.tasks.call_command") +def test_update_commits_does_not_backfill(mock_call, _mock_updater, db): + """release_tasks sweeps every source once; a call here would double it.""" + from libraries.tasks import update_commits + + update_commits() + + assert not [ + c for c in mock_call.call_args_list if c.args[0] == "backfill_achievements" + ] + + +@patch("libraries.tasks.call_command") +def test_release_tasks_delegates_the_backfill_to_the_command(mock_call): + """The sweep is an Action inside release_tasks, not a trailing extra call.""" + from libraries.management.commands.release_tasks import ReleaseTasksManager + from libraries.tasks import release_tasks + + release_tasks("https://example.com") + + assert [c.args[0] for c in mock_call.call_args_list] == ["release_tasks"] + manager = ReleaseTasksManager(base_uri="https://example.com", user_id=None) + assert [ + task.description + for task in manager.tasks + if task.handler == ["backfill_achievements"] + ] == ["Backfilling achievements"] From 28227fed22b57d888adedf176633ba7d77b10840 Mon Sep 17 00:00:00 2001 From: "Teodoro B. Mendes" Date: Mon, 3 Aug 2026 17:53:22 -0300 Subject: [PATCH 2/2] feat: tag the release pipeline backfill in the sync log --- libraries/management/commands/release_tasks.py | 5 ++++- libraries/tasks.py | 2 +- libraries/tests/test_tasks.py | 14 +++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libraries/management/commands/release_tasks.py b/libraries/management/commands/release_tasks.py index 4557dabd7..0bceb1ec9 100644 --- a/libraries/management/commands/release_tasks.py +++ b/libraries/management/commands/release_tasks.py @@ -62,7 +62,10 @@ def set_tasks(self): Action("Updating website statistics", self.update_website_statistics), Action("Importing mailing list counts", self.import_ml_counts), # Last, so every source it reads has already been refreshed above. - Action("Backfilling achievements", ["backfill_achievements"]), + Action( + "Backfilling achievements", + ["backfill_achievements", "--trigger", "pipeline"], + ), # Action("Generating report", self.generate_report), ] diff --git a/libraries/tasks.py b/libraries/tasks.py index d40dfe9e1..51dace9e8 100644 --- a/libraries/tasks.py +++ b/libraries/tasks.py @@ -281,7 +281,7 @@ def update_authors_and_maintainers(): call_command("update_library_version_authors", "--clean") app.signature("users.tasks.recompute_displayed_profile_roles").apply_async() # Only the sources whose upstream data just changed. A blanket backfill would - # also sweep the commit, review and news tables this task never touches. + # also sweep the commit and review tables this task never touches. call_command( "backfill_achievements", "--source", diff --git a/libraries/tests/test_tasks.py b/libraries/tests/test_tasks.py index a05b2bfd8..510ea8b45 100644 --- a/libraries/tests/test_tasks.py +++ b/libraries/tests/test_tasks.py @@ -168,7 +168,7 @@ def test_update_library_version_website_adoc_no_stable_release(): @patch("libraries.tasks.call_command") def test_update_authors_and_maintainers_backfills_only_library_sources(mock_call): - """A blanket backfill here would sweep the commit, review and news tables.""" + """A blanket backfill here would sweep the commit and review tables too.""" from libraries.tasks import update_authors_and_maintainers update_authors_and_maintainers() @@ -208,8 +208,12 @@ def test_release_tasks_delegates_the_backfill_to_the_command(mock_call): assert [c.args[0] for c in mock_call.call_args_list] == ["release_tasks"] manager = ReleaseTasksManager(base_uri="https://example.com", user_id=None) - assert [ - task.description + sweeps = [ + task for task in manager.tasks - if task.handler == ["backfill_achievements"] - ] == ["Backfilling achievements"] + if isinstance(task.handler, list) and task.handler[0] == "backfill_achievements" + ] + assert [task.description for task in sweeps] == ["Backfilling achievements"] + # Tagged, so the sync log can tell the weekly job from a person pressing a + # button when support asks what moved a member's count. + assert sweeps[0].handler == ["backfill_achievements", "--trigger", "pipeline"]