зеркало из https://github.com/mozilla/pontoon.git
Award badges on file uploads (#3522)
This commit is contained in:
Родитель
402bf74812
Коммит
89f390a551
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
@ -805,8 +806,18 @@ def upload(request):
|
|||
|
||||
upload = request.FILES["uploadfile"]
|
||||
try:
|
||||
import_uploaded_file(project, locale, res_path, upload, request.user)
|
||||
badge_name, badge_level = import_uploaded_file(
|
||||
project, locale, res_path, upload, request.user
|
||||
)
|
||||
messages.success(request, "Translations updated from uploaded file.")
|
||||
if badge_name:
|
||||
message = json.dumps(
|
||||
{
|
||||
"name": badge_name,
|
||||
"level": badge_level,
|
||||
}
|
||||
)
|
||||
messages.info(request, message, extra_tags="badge")
|
||||
except Exception as error:
|
||||
messages.error(request, str(error))
|
||||
else:
|
||||
|
|
|
@ -29,6 +29,7 @@ from pontoon.base.models import (
|
|||
)
|
||||
from pontoon.checks import DB_FORMATS
|
||||
from pontoon.checks.utils import bulk_run_checks
|
||||
from pontoon.messaging.notifications import send_badge_notification
|
||||
from pontoon.sync.core.checkout import Checkout, Checkouts
|
||||
from pontoon.sync.core.paths import UploadPaths
|
||||
from pontoon.sync.formats import parse
|
||||
|
@ -76,12 +77,13 @@ def sync_translations_from_repo(
|
|||
|
||||
def write_db_updates(
|
||||
project: Project, updates: Updates, user: User | None, now: datetime
|
||||
) -> None:
|
||||
updated_translations, new_translations = update_db_translations(
|
||||
project, updates, user, now
|
||||
) -> tuple[str, int]:
|
||||
badge_name, badge_level, updated_translations, new_translations = (
|
||||
update_db_translations(project, updates, user, now)
|
||||
)
|
||||
add_failed_checks(new_translations)
|
||||
add_translation_memory_entries(project, new_translations + updated_translations)
|
||||
return badge_name, badge_level
|
||||
|
||||
|
||||
def delete_removed_bilingual_resources(
|
||||
|
@ -460,10 +462,24 @@ def update_db_translations(
|
|||
f"[{project.slug}] Created {str_n_translations(created)} from repo changes"
|
||||
)
|
||||
|
||||
badge_name = ""
|
||||
badge_level = 0
|
||||
if actions:
|
||||
ActionLog.objects.bulk_create(actions)
|
||||
translation_before_level = log_user.badges_translation_level
|
||||
review_before_level = log_user.badges_review_level
|
||||
|
||||
return created, list(suggestions.values())
|
||||
ActionLog.objects.bulk_create(actions)
|
||||
if log_user.username != "pontoon-sync":
|
||||
if log_user.badges_translation_level > translation_before_level:
|
||||
badge_name = "Translation Champion"
|
||||
badge_level = log_user.badges_translation_level
|
||||
send_badge_notification(log_user, badge_name, badge_level)
|
||||
if log_user.badges_review_level > review_before_level:
|
||||
badge_name = "Review Master"
|
||||
badge_level = log_user.badges_review_level
|
||||
send_badge_notification(log_user, badge_name, badge_level)
|
||||
|
||||
return badge_name, badge_level, created, list(suggestions.values())
|
||||
|
||||
|
||||
def str_n_translations(n: int | Sized) -> str:
|
||||
|
|
|
@ -96,7 +96,7 @@ def import_uploaded_file(
|
|||
)
|
||||
if updates:
|
||||
now = timezone.now()
|
||||
write_db_updates(project, updates, user, now)
|
||||
badge_name, badge_level = write_db_updates(project, updates, user, now)
|
||||
update_stats(project)
|
||||
ChangedEntityLocale.objects.bulk_create(
|
||||
(
|
||||
|
@ -105,5 +105,6 @@ def import_uploaded_file(
|
|||
),
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
return badge_name, badge_level
|
||||
else:
|
||||
raise Exception("Upload failed.")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext, useEffect, useState } from 'react';
|
||||
import { Localized } from '@fluent/react';
|
||||
import { createContext } from 'react';
|
||||
import { useNotifications } from '~/hooks/useNotifications';
|
||||
|
||||
export type BadgeTooltipMessage = Readonly<{
|
||||
badgeName: string | null;
|
||||
|
@ -19,11 +19,11 @@ export function BadgeTooltipProvider({
|
|||
}: {
|
||||
children: React.ReactElement;
|
||||
}) {
|
||||
const [message, setMessage] = useState<BadgeTooltipMessage | null>(null);
|
||||
const { badgeMessage, setBadgeMessage } = useNotifications();
|
||||
|
||||
return (
|
||||
<BadgeTooltipMessage.Provider value={message}>
|
||||
<ShowBadgeTooltip.Provider value={(tooltip) => setMessage(tooltip)}>
|
||||
<BadgeTooltipMessage.Provider value={badgeMessage}>
|
||||
<ShowBadgeTooltip.Provider value={(tooltip) => setBadgeMessage(tooltip)}>
|
||||
{children}
|
||||
</ShowBadgeTooltip.Provider>
|
||||
</BadgeTooltipMessage.Provider>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createContext, useEffect, useState } from 'react';
|
||||
import { createContext } from 'react';
|
||||
import { useNotifications } from '~/hooks/useNotifications';
|
||||
|
||||
type NotificationType = 'debug' | 'error' | 'info' | 'success' | 'warning';
|
||||
|
||||
|
@ -20,22 +21,7 @@ export function NotificationProvider({
|
|||
}: {
|
||||
children: React.ReactElement;
|
||||
}) {
|
||||
const [message, setMessage] = useState<NotificationMessage | null>(null);
|
||||
|
||||
// If there's a notification in the DOM set by Django, show it.
|
||||
// Note that we only show it once, and only when the UI has already
|
||||
// been rendered, to make sure users do see it.
|
||||
useEffect(() => {
|
||||
const rootElt = document.getElementById('root');
|
||||
if (rootElt?.dataset.notifications) {
|
||||
const notifications = JSON.parse(rootElt.dataset.notifications);
|
||||
if (notifications.length > 0) {
|
||||
// Our notification system only supports showing one notification
|
||||
// for the moment, so we only add the first notification here.
|
||||
setMessage(notifications[0]);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
const { message, setMessage } = useNotifications();
|
||||
|
||||
return (
|
||||
<NotificationMessage.Provider value={message}>
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { NotificationMessage } from '~/context/Notification';
|
||||
import { BadgeTooltipMessage } from '~/context/BadgeTooltip';
|
||||
|
||||
export function useNotifications() {
|
||||
const [message, setMessage] = useState<NotificationMessage | null>(null);
|
||||
const [badgeMessage, setBadgeMessage] = useState<BadgeTooltipMessage | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
// If there's a notification in the DOM set by Django, show it.
|
||||
// Note that we only show it once, and only when the UI has already
|
||||
// been rendered, to make sure users do see it.
|
||||
useEffect(() => {
|
||||
const rootElt = document.getElementById('root');
|
||||
if (rootElt?.dataset.notifications) {
|
||||
const notifications = JSON.parse(rootElt.dataset.notifications);
|
||||
if (notifications.length > 0) {
|
||||
// Extra tags from the Django messages framework are combined
|
||||
// with the level into a single string as notification.type
|
||||
const generalNotification = notifications.find(
|
||||
(notification: { type: string }) =>
|
||||
notification.type !== 'badge info',
|
||||
);
|
||||
const badgeNotification = notifications.find(
|
||||
(notification: { type: string }) =>
|
||||
notification.type === 'badge info',
|
||||
);
|
||||
|
||||
if (generalNotification) {
|
||||
setMessage({
|
||||
type: generalNotification.type,
|
||||
content: generalNotification.content,
|
||||
});
|
||||
}
|
||||
|
||||
if (badgeNotification) {
|
||||
const badgeData = JSON.parse(badgeNotification.content);
|
||||
setBadgeMessage({
|
||||
badgeName: badgeData.name || null,
|
||||
badgeLevel: badgeData.level || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { message, setMessage, badgeMessage, setBadgeMessage };
|
||||
}
|
Загрузка…
Ссылка в новой задаче