Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality for counting file uploads #3522

Merged
merged 5 commits into from
Jan 15, 2025
Merged
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
13 changes: 12 additions & 1 deletion pontoon/base/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
import re

Expand Down Expand Up @@ -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:
Expand Down
26 changes: 21 additions & 5 deletions pontoon/sync/core/translations_from_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)
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
Expand Down Expand Up @@ -75,12 +76,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(
Expand Down Expand Up @@ -433,10 +435,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:
Expand Down
3 changes: 2 additions & 1 deletion pontoon/sync/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
(
Expand All @@ -105,5 +105,6 @@ def import_uploaded_file(
),
ignore_conflicts=True,
)
return badge_name, badge_level
else:
raise Exception("Upload failed.")
10 changes: 5 additions & 5 deletions translate/src/context/BadgeTooltip.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -19,11 +19,11 @@ export function BadgeTooltipProvider({
}: {
children: React.ReactElement;
}) {
const [message, setMessage] = useState<BadgeTooltipMessage | null>(null);
const { badgeMessage, setBadgeMessage } = useNotifications();

return (
harmitgoswami marked this conversation as resolved.
Show resolved Hide resolved
<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>
Expand Down
20 changes: 3 additions & 17 deletions translate/src/context/Notification.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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}>
Expand Down
49 changes: 49 additions & 0 deletions translate/src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
@@ -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',
harmitgoswami marked this conversation as resolved.
Show resolved Hide resolved
);
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 };
}
Loading