diff --git a/libraries/management/commands/release_tasks.py b/libraries/management/commands/release_tasks.py index b8f900941..0bceb1ec9 100644 --- a/libraries/management/commands/release_tasks.py +++ b/libraries/management/commands/release_tasks.py @@ -61,6 +61,11 @@ 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", "--trigger", "pipeline"], + ), # Action("Generating report", self.generate_report), ] diff --git a/libraries/tasks.py b/libraries/tasks.py index b69652828..51dace9e8 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 and review 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..510ea8b45 100644 --- a/libraries/tests/test_tasks.py +++ b/libraries/tests/test_tasks.py @@ -164,3 +164,56 @@ 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 and review tables too.""" + 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) + sweeps = [ + task + for task in manager.tasks + 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"]