Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions badges/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,30 @@ def _iter_library_authoring():
yield user, library


def _iter_library_maintenance():
"""Yield (user, library) once per library the user maintains.

Maintainers are recorded per ``LibraryVersion``, but the badge counts
*libraries* maintained (thresholds 1/2/5/...), so pairs are deduplicated
across versions and the ``Library`` is the achievement source. Only parent
libraries count: see the module docstring.
"""
from libraries.models import LibraryVersion

seen = set()
versions = (
LibraryVersion.objects.exclude(library__key__in=SUB_LIBRARIES)
.select_related("library")
.prefetch_related("maintainers")
)
for version in versions.iterator(chunk_size=500):
for user in version.maintainers.all():
key = (user.pk, version.library_id)
if key not in seen:
seen.add(key)
yield user, version.library


def _iter_code_commits():
"""Yield (user, commit) for every attributed commit."""
from libraries.models import Commit
Expand All @@ -55,6 +79,7 @@ def _iter_code_commits():

BACKFILL_ITERATORS = {
AchievementSlug.LIBRARY_AUTHORING: _iter_library_authoring,
AchievementSlug.LIBRARY_MAINTENANCE: _iter_library_maintenance,
AchievementSlug.CODE_COMMITS: _iter_code_commits,
}

Expand Down
20 changes: 20 additions & 0 deletions badges/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ def test_backfill_library_authoring(plain_user):
).exists()


def test_backfill_library_maintenance(plain_user):
"""Backfill grants the maintenance achievement once per library maintained."""
library = baker.make("libraries.Library")
for _ in range(2):
version = baker.make("libraries.LibraryVersion", library=library)
version.maintainers.add(plain_user)

call_command("backfill_achievements", "--source", "library-maintenance")

assert (
UserAchievement.objects.filter(
user=plain_user, achievement__slug="library-maintenance"
).count()
== 1
)
assert UserBadge.objects.filter(
user=plain_user, badge__achievement__slug="library-maintenance"
).exists()


def test_backfill_fails_loudly_on_an_explicit_unseeded_source(plain_user):
"""A named source with no Achievement row is a deploy bug, not a skip."""
Achievement.objects.filter(slug=AchievementSlug.CODE_COMMITS).delete()
Expand Down
20 changes: 20 additions & 0 deletions badges/tests/test_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ def test_iter_library_authoring_skips_sub_libraries(plain_user):
assert list(sources._iter_library_authoring()) == []


def test_iter_library_maintenance_dedupes_versions(plain_user):
"""Maintaining many versions of one library yields a single pair."""
library = baker.make("libraries.Library")
for _ in range(3):
version = baker.make("libraries.LibraryVersion", library=library)
version.maintainers.add(plain_user)

pairs = list(sources._iter_library_maintenance())
assert pairs == [(plain_user, library)]
Comment on lines +69 to +77

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit suggestion: Perhaps we can update this test to add another library here, and assert that pairs == [(plain_user, library), (plain_user, another library)] to ensure we can cover that common case as well!



def test_iter_library_maintenance_skips_sub_libraries(plain_user):
"""Maintaining a sub-library is maintaining part of its parent's docs."""
sub = baker.make("libraries.Library", key="math/quaternion")
version = baker.make("libraries.LibraryVersion", library=sub)
version.maintainers.add(plain_user)

assert list(sources._iter_library_maintenance()) == []


def test_iter_code_commits_skips_unlinked(plain_user):
"""Only commits whose author has a linked user are yielded."""
linked = baker.make("libraries.CommitAuthor", user=plain_user)
Expand Down
Loading