Skip to content

Commit

Permalink
Award badges on batch actions (#3499)
Browse files Browse the repository at this point in the history
  • Loading branch information
harmitgoswami authored Jan 6, 2025
1 parent 42421cd commit 72b7646
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 11 deletions.
20 changes: 20 additions & 0 deletions pontoon/base/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,24 @@ def badges_promotion_count(self):
)


@property
def badges_translation_level(self):
thresholds = settings.BADGES_TRANSLATION_THRESHOLDS
for level in range(len(thresholds) - 1):
if thresholds[level] <= self.badges_translation_count < thresholds[level + 1]:
return level + 1
return 0


@property
def badges_review_level(self):
thresholds = settings.BADGES_REVIEW_THRESHOLDS
for level in range(len(thresholds) - 1):
if thresholds[level] <= self.badges_review_count < thresholds[level + 1]:
return level + 1
return 0


@property
def top_contributed_locale(self):
"""Locale the user has made the most contributions to."""
Expand Down Expand Up @@ -523,6 +541,8 @@ def latest_action(self):
User.add_to_class("badges_translation_count", badges_translation_count)
User.add_to_class("badges_review_count", badges_review_count)
User.add_to_class("badges_promotion_count", badges_promotion_count)
User.add_to_class("badges_translation_level", badges_translation_level)
User.add_to_class("badges_review_level", badges_review_level)
User.add_to_class("has_approved_translations", has_approved_translations)
User.add_to_class("top_contributed_locale", top_contributed_locale)
User.add_to_class("can_translate", can_translate)
Expand Down
34 changes: 34 additions & 0 deletions pontoon/batch/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
TranslationMemoryEntry,
)
from pontoon.batch import utils
from pontoon.messaging.notifications import send_badge_notification


def batch_action_template(form, user, translations, locale):
Expand Down Expand Up @@ -69,6 +70,8 @@ def approve_translations(form, user, translations, locale):
locale,
)

before_level = user.badges_review_level

# Log approving actions
actions_to_log = [
ActionLog(
Expand All @@ -80,6 +83,14 @@ def approve_translations(form, user, translations, locale):
]
ActionLog.objects.bulk_create(actions_to_log)

# Send Review Master Badge notification information
after_level = user.badges_review_level
badge_update = {}
if after_level > before_level:
badge_update["level"] = after_level
badge_update["name"] = "Review Master"
send_badge_notification(user, badge_update["name"], badge_update["level"])

# Approve translations.
translations.update(
approved=True,
Expand All @@ -99,6 +110,7 @@ def approve_translations(form, user, translations, locale):
"latest_translation_pk": latest_translation_pk,
"changed_translation_pks": changed_translation_pks,
"invalid_translation_pks": invalid_translation_pks,
"badge_update": badge_update,
}


Expand All @@ -124,6 +136,8 @@ def reject_translations(form, user, translations, locale):
)
TranslationMemoryEntry.objects.filter(translation__in=suggestions).delete()

before_level = user.badges_review_level

# Log rejecting actions
actions_to_log = [
ActionLog(
Expand All @@ -135,6 +149,14 @@ def reject_translations(form, user, translations, locale):
]
ActionLog.objects.bulk_create(actions_to_log)

# Send Review Master Badge notification information
after_level = user.badges_review_level
badge_update = {}
if after_level > before_level:
badge_update["level"] = after_level
badge_update["name"] = "Review Master"
send_badge_notification(user, badge_update["name"], badge_update["level"])

# Reject translations.
suggestions.update(
active=False,
Expand All @@ -155,6 +177,7 @@ def reject_translations(form, user, translations, locale):
"latest_translation_pk": None,
"changed_translation_pks": [],
"invalid_translation_pks": [],
"badge_update": badge_update,
}


Expand Down Expand Up @@ -216,6 +239,8 @@ def replace_translations(form, user, translations, locale):
translations_to_create,
)

before_level = user.badges_translation_level

# Log creating actions
actions_to_log = [
ActionLog(
Expand All @@ -227,6 +252,14 @@ def replace_translations(form, user, translations, locale):
]
ActionLog.objects.bulk_create(actions_to_log)

# Send Translation Champion Badge notification information
after_level = user.badges_translation_level
badge_update = {}
if after_level > before_level:
badge_update["level"] = after_level
badge_update["name"] = "Translation Champion"
send_badge_notification(user, badge_update["name"], badge_update["level"])

changed_translation_pks = [c.pk for c in changed_translations]

if changed_translation_pks:
Expand All @@ -239,6 +272,7 @@ def replace_translations(form, user, translations, locale):
"latest_translation_pk": latest_translation_pk,
"changed_translation_pks": changed_translation_pks,
"invalid_translation_pks": invalid_translation_pks,
"badge_update": badge_update,
}


Expand Down
4 changes: 4 additions & 0 deletions pontoon/batch/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def test_batch_approve_valid_translations(
assert response.json() == {
"count": 1,
"invalid_translation_count": 0,
"badge_update": {},
}

translation_dtd_unapproved.refresh_from_db()
Expand All @@ -170,6 +171,7 @@ def test_batch_approve_invalid_translations(
assert response.json() == {
"count": 0,
"invalid_translation_count": 1,
"badge_update": {},
}

translation_dtd_invalid_unapproved.refresh_from_db()
Expand All @@ -195,6 +197,7 @@ def test_batch_find_and_replace_valid_translations(
assert response.json() == {
"count": 1,
"invalid_translation_count": 0,
"badge_update": {},
}

translation = translation_dtd_unapproved.entity.translation_set.last()
Expand Down Expand Up @@ -224,6 +227,7 @@ def test_batch_find_and_replace_invalid_translations(
assert response.json() == {
"count": 0,
"invalid_translation_count": 1,
"badge_update": {},
}

translation = translation_dtd_unapproved.entity.translation_set.last()
Expand Down
7 changes: 6 additions & 1 deletion pontoon/batch/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ def batch_edit_translations(request):
invalid_translation_count = len(action_status.get("invalid_translation_pks", []))
if action_status["count"] == 0:
return JsonResponse(
{"count": 0, "invalid_translation_count": invalid_translation_count}
{
"count": 0,
"invalid_translation_count": invalid_translation_count,
"badge_update": action_status["badge_update"],
}
)

tr_pks = [tr.pk for tr in action_status["translated_resources"]]
Expand Down Expand Up @@ -145,5 +149,6 @@ def batch_edit_translations(request):
{
"count": action_status["count"],
"invalid_translation_count": invalid_translation_count,
"badge_update": action_status["badge_update"],
}
)
6 changes: 3 additions & 3 deletions pontoon/translations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def create_translation(request):
# Send Translation Champion Badge notification information
translation_count = user.badges_translation_count
if translation_count in settings.BADGES_TRANSLATION_THRESHOLDS:
badge_name = "Translation Champion Badge"
badge_name = "Translation Champion"
badge_level = (
settings.BADGES_TRANSLATION_THRESHOLDS.index(translation_count) + 1
)
Expand Down Expand Up @@ -323,7 +323,7 @@ def approve_translation(request):
# Send Review Master Badge notification information
review_count = user.badges_review_count
if review_count in settings.BADGES_REVIEW_THRESHOLDS:
badge_name = "Review Master Badge"
badge_name = "Review Master"
badge_level = settings.BADGES_REVIEW_THRESHOLDS.index(review_count) + 1
_add_badge_data(response_data, user, badge_name, badge_level)

Expand Down Expand Up @@ -460,7 +460,7 @@ def reject_translation(request):
# Send Review Master Badge notification information
review_count = request.user.badges_review_count
if review_count in settings.BADGES_REVIEW_THRESHOLDS:
badge_name = "Review Master Badge"
badge_name = "Review Master"
badge_level = settings.BADGES_REVIEW_THRESHOLDS.index(review_count) + 1
_add_badge_data(response_data, request.user, badge_name, badge_level)

Expand Down
7 changes: 6 additions & 1 deletion translate/src/api/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
EntityTranslation,
HistoryTranslation,
} from './translation';
import type { BatchBadgeUpdate } from '../modules/batchactions/actions';

/**
* String that needs to be translated, along with its current metadata,
Expand Down Expand Up @@ -42,7 +43,11 @@ export type EntitySiblings = {
};

type BatchEditResponse =
| { count: number; invalid_translation_count?: number }
| {
count: number;
invalid_translation_count?: number;
badge_update?: BatchBadgeUpdate;
}
| { error: true };

export async function batchEditEntities(
Expand Down
22 changes: 22 additions & 0 deletions translate/src/modules/batchactions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ export const RESET_BATCHACTIONS_RESPONSE = 'batchactions/RESET_RESPONSE';
export const TOGGLE_BATCHACTIONS = 'batchactions/TOGGLE';
export const UNCHECK_BATCHACTIONS = 'batchactions/UNCHECK';

export type BatchBadgeUpdate = {
name: string | null;
level: number | null;
};

export type ResponseType = {
action: string;
changedCount: number | null | undefined;
invalidCount: number | null | undefined;
badgeUpdate: BatchBadgeUpdate | null | undefined;
error: boolean | null | undefined;
};

Expand Down Expand Up @@ -117,6 +123,10 @@ export const performAction =
location: Location,
action: 'approve' | 'reject' | 'replace',
entityIds: number[],
showBadgeTooltip: (tooltip: {
badgeName: string | null;
badgeLevel: number | null;
}) => void,
find?: string,
replace?: string,
) =>
Expand All @@ -134,13 +144,25 @@ export const performAction =
const response: ResponseType = {
changedCount: 0,
invalidCount: 0,
badgeUpdate: {
name: '',
level: 0,
},
error: false,
action,
};

if ('count' in data) {
response.changedCount = data.count;
response.invalidCount = data.invalid_translation_count;
response.badgeUpdate = data.badge_update;

if (response.badgeUpdate?.level && response.badgeUpdate?.level > 0) {
showBadgeTooltip({
badgeName: response.badgeUpdate.name,
badgeLevel: response.badgeUpdate.level,
});
}

if (data.count > 0) {
dispatch(updateUI(location, entityIds));
Expand Down
21 changes: 19 additions & 2 deletions translate/src/modules/batchactions/components/BatchActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Localized } from '@fluent/react';
import React, { useCallback, useContext, useEffect, useRef } from 'react';

import { Location } from '~/context/Location';
import { ShowBadgeTooltip } from '~/context/BadgeTooltip';
import { useAppDispatch, useAppSelector } from '~/hooks';

import { performAction, resetSelection, selectAll } from '../actions';
Expand All @@ -18,6 +19,7 @@ import { ReplaceAll } from './ReplaceAll';
export function BatchActions(): React.ReactElement<'div'> {
const batchactions = useAppSelector((state) => state[BATCHACTIONS]);
const location = useContext(Location);
const showBadgeTooltip = useContext(ShowBadgeTooltip);
const dispatch = useAppDispatch();

const find = useRef<HTMLInputElement>(null);
Expand All @@ -43,13 +45,27 @@ export function BatchActions(): React.ReactElement<'div'> {

const approveAll = useCallback(() => {
if (!batchactions.requestInProgress) {
dispatch(performAction(location, 'approve', batchactions.entities));
dispatch(
performAction(
location,
'approve',
batchactions.entities,
showBadgeTooltip,
),
);
}
}, [location, batchactions]);

const rejectAll = useCallback(() => {
if (!batchactions.requestInProgress) {
dispatch(performAction(location, 'reject', batchactions.entities));
dispatch(
performAction(
location,
'reject',
batchactions.entities,
showBadgeTooltip,
),
);
}
}, [location, batchactions]);

Expand All @@ -67,6 +83,7 @@ export function BatchActions(): React.ReactElement<'div'> {
location,
'replace',
batchactions.entities,
showBadgeTooltip,
encodeURIComponent(fv),
encodeURIComponent(rv),
),
Expand Down
5 changes: 3 additions & 2 deletions translate/src/modules/editor/hooks/useSendTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ export function useSendTranslation(): (ignoreWarnings?: boolean) => void {
);

const badgeLevel = content.badge_update?.level;
if (badgeLevel) {
const badgeName = content.badge_update?.name;
if (badgeName && badgeLevel) {
showBadgeTooltip({
badgeName: 'Translation Champion',
badgeName: badgeName,
badgeLevel: badgeLevel,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ export function useUpdateTranslationStatus(

// Check for update in badge level
const badgeLevel = results.badge_update?.level;
if (badgeLevel) {
const badgeName = results.badge_update?.name;
if (badgeName && badgeLevel) {
showBadgeTooltip({
badgeName: 'Review Master',
badgeName: badgeName,
badgeLevel: badgeLevel,
});
}
Expand Down

0 comments on commit 72b7646

Please sign in to comment.