From ecb761a0dc1153f0090fd6d8bbc7f3428b60c88d Mon Sep 17 00:00:00 2001 From: Harmit Goswami Date: Tue, 14 Jan 2025 23:55:25 -0500 Subject: [PATCH] Addressed review comments Factored out notification useEffect hook, added extra tag for badge messages --- pontoon/base/views.py | 11 +++-- pontoon/sync/core/translations_from_repo.py | 39 +++++++--------- pontoon/sync/utils.py | 4 +- translate/src/context/BadgeTooltip.tsx | 44 +++--------------- translate/src/context/Notification.tsx | 40 ++++++++-------- translate/src/hooks/useNotification.ts | 46 +++++++++++++++++++ .../batchactions/components/BatchActions.tsx | 2 +- .../editor/components/BadgeTooltip.tsx | 2 +- .../editor/hooks/useSendTranslation.ts | 3 +- .../hooks/useUpdateTranslationStatus.ts | 3 +- 10 files changed, 103 insertions(+), 91 deletions(-) create mode 100644 translate/src/hooks/useNotification.ts diff --git a/pontoon/base/views.py b/pontoon/base/views.py index 1881865b35..6ee819b380 100755 --- a/pontoon/base/views.py +++ b/pontoon/base/views.py @@ -806,15 +806,18 @@ def upload(request): upload = request.FILES["uploadfile"] try: - badge_update = import_uploaded_file( + badge_name, badge_level = import_uploaded_file( project, locale, res_path, upload, request.user ) messages.success(request, "Translations updated from uploaded file.") - if badge_update[0]: + if badge_name: message = json.dumps( - {"badgeName": badge_update[0], "badgeLevel": badge_update[1]} + { + "name": badge_name, + "level": badge_level, + } ) - messages.info(request, message) + messages.info(request, message, extra_tags="badge") except Exception as error: messages.error(request, str(error)) else: diff --git a/pontoon/sync/core/translations_from_repo.py b/pontoon/sync/core/translations_from_repo.py index 599016ffe8..d0f38f5e09 100644 --- a/pontoon/sync/core/translations_from_repo.py +++ b/pontoon/sync/core/translations_from_repo.py @@ -77,12 +77,12 @@ def sync_translations_from_repo( def write_db_updates( project: Project, updates: Updates, user: User | None, now: datetime ) -> tuple[str, int]: - badge_update, updated_translations, new_translations = update_db_translations( - project, updates, user, now + 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_update + return badge_name, badge_level def delete_removed_bilingual_resources( @@ -435,31 +435,24 @@ def update_db_translations( f"[{project.slug}] Created {str_n_translations(created)} from repo changes" ) - badge_update = ("", 0) + badge_name = "" + badge_level = 0 if actions: translation_before_level = log_user.badges_translation_level review_before_level = log_user.badges_review_level ActionLog.objects.bulk_create(actions) - - if ( - log_user.badges_translation_level > translation_before_level - and log_user.username != "pontoon-sync" - ): - send_badge_notification( - log_user, "Translation Champion", log_user.badges_translation_level - ) - badge_update = ("Translation Champion", log_user.badges_translation_level) - if ( - log_user.badges_review_level > review_before_level - and log_user.username != "pontoon-sync" - ): - send_badge_notification( - log_user, "Review Master", log_user.badges_review_level - ) - badge_update = ("Review Master", log_user.badges_review_level) - - return badge_update, created, list(suggestions.values()) + 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: diff --git a/pontoon/sync/utils.py b/pontoon/sync/utils.py index c366a1db06..a252c97bdb 100644 --- a/pontoon/sync/utils.py +++ b/pontoon/sync/utils.py @@ -96,7 +96,7 @@ def import_uploaded_file( ) if updates: now = timezone.now() - badge_update = 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,6 +105,6 @@ def import_uploaded_file( ), ignore_conflicts=True, ) - return badge_update + return badge_name, badge_level else: raise Exception("Upload failed.") diff --git a/translate/src/context/BadgeTooltip.tsx b/translate/src/context/BadgeTooltip.tsx index aaf56db37e..fec2c43cea 100644 --- a/translate/src/context/BadgeTooltip.tsx +++ b/translate/src/context/BadgeTooltip.tsx @@ -1,49 +1,17 @@ -import { createContext, useEffect, useState } from 'react'; -import { Localized } from '@fluent/react'; - -export type BadgeTooltipMessage = Readonly<{ - badgeName: string | null; - badgeLevel: number | null; -}>; - -export const BadgeTooltipMessage = createContext( - null, -); - -export const ShowBadgeTooltip = createContext< - (tooltip: BadgeTooltipMessage | null) => void ->(() => {}); +import { createContext, useContext } from 'react'; +import { BadgeTooltipMessage, ShowBadgeTooltip } from './Notification'; export function BadgeTooltipProvider({ children, }: { children: React.ReactElement; }) { - const [message, setMessage] = useState(null); - - useEffect(() => { - const rootElt = document.getElementById('root'); - if (rootElt?.dataset.notifications) { - const notifications = JSON.parse(rootElt.dataset.notifications); - if (notifications.length > 0) { - const parsed = notifications.map( - (notification: { content: string; type: string }) => { - if (notification.type === 'info') { - // Badge update information - return JSON.parse(notification.content); - } else { - return { content: notification.content, type: notification.type }; - } - }, - ); - setMessage(parsed[1]); - } - } - }, []); + const badgeMessage = useContext(BadgeTooltipMessage); + const setBadgeMessage = useContext(ShowBadgeTooltip); return ( - - setMessage(tooltip)}> + + {children} diff --git a/translate/src/context/Notification.tsx b/translate/src/context/Notification.tsx index d515841573..7274df054d 100644 --- a/translate/src/context/Notification.tsx +++ b/translate/src/context/Notification.tsx @@ -1,4 +1,5 @@ -import { createContext, useEffect, useState } from 'react'; +import React, { createContext } from 'react'; +import { useNotifications } from '../hooks/useNotification'; type NotificationType = 'debug' | 'error' | 'info' | 'success' | 'warning'; @@ -15,32 +16,35 @@ export const ShowNotification = createContext< (message: NotificationMessage | null) => void >(() => {}); +export type BadgeTooltipMessage = Readonly<{ + badgeName: string | null; + badgeLevel: number | null; +}>; + +export const BadgeTooltipMessage = createContext( + null, +); + +export const ShowBadgeTooltip = createContext< + (tooltip: BadgeTooltipMessage | null) => void +>(() => {}); + export function NotificationProvider({ children, }: { children: React.ReactElement; }) { - const [message, setMessage] = useState(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, badgeMessage, setBadgeMessage } = + useNotifications(); return ( - {children} + + + {children} + + ); diff --git a/translate/src/hooks/useNotification.ts b/translate/src/hooks/useNotification.ts new file mode 100644 index 0000000000..afa3aa546e --- /dev/null +++ b/translate/src/hooks/useNotification.ts @@ -0,0 +1,46 @@ +import { useState, useEffect } from 'react'; +import { + NotificationMessage, + BadgeTooltipMessage, +} from '../context/Notification'; + +export function useNotifications() { + const [message, setMessage] = useState(null); + const [badgeMessage, setBadgeMessage] = useState( + null, + ); + + useEffect(() => { + const rootElt = document.getElementById('root'); + if (rootElt?.dataset.notifications) { + const notifications = JSON.parse(rootElt.dataset.notifications); + if (notifications.length > 0) { + 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 }; +} diff --git a/translate/src/modules/batchactions/components/BatchActions.tsx b/translate/src/modules/batchactions/components/BatchActions.tsx index 86bf12e292..4022c7b886 100644 --- a/translate/src/modules/batchactions/components/BatchActions.tsx +++ b/translate/src/modules/batchactions/components/BatchActions.tsx @@ -2,7 +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 { ShowBadgeTooltip } from '~/context/Notification'; import { useAppDispatch, useAppSelector } from '~/hooks'; import { performAction, resetSelection, selectAll } from '../actions'; diff --git a/translate/src/modules/editor/components/BadgeTooltip.tsx b/translate/src/modules/editor/components/BadgeTooltip.tsx index c68668c6ed..d4e94801b1 100644 --- a/translate/src/modules/editor/components/BadgeTooltip.tsx +++ b/translate/src/modules/editor/components/BadgeTooltip.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useContext, useRef } from 'react'; import { Localized } from '@fluent/react'; import Pride from 'react-canvas-confetti/dist/presets/pride'; -import { BadgeTooltipMessage, ShowBadgeTooltip } from '~/context/BadgeTooltip'; +import { BadgeTooltipMessage, ShowBadgeTooltip } from '~/context/Notification'; import { useAppSelector } from '~/hooks'; import { useOnDiscard } from '~/utils'; diff --git a/translate/src/modules/editor/hooks/useSendTranslation.ts b/translate/src/modules/editor/hooks/useSendTranslation.ts index aae03f6e55..3e5f4b9711 100644 --- a/translate/src/modules/editor/hooks/useSendTranslation.ts +++ b/translate/src/modules/editor/hooks/useSendTranslation.ts @@ -11,7 +11,6 @@ import { EntityView } from '~/context/EntityView'; import { FailedChecksData } from '~/context/FailedChecksData'; import { Locale } from '~/context/Locale'; import { Location } from '~/context/Location'; -import { ShowNotification } from '~/context/Notification'; import { UnsavedActions } from '~/context/UnsavedChanges'; import { updateEntityTranslation } from '~/modules/entities/actions'; import { usePushNextTranslatable } from '~/modules/entities/hooks'; @@ -23,7 +22,7 @@ import { updateResource } from '~/modules/resource/actions'; import { updateStats } from '~/modules/stats/actions'; import { useAppDispatch, useAppSelector } from '~/hooks'; import { serializeEntry, getPlainMessage } from '~/utils/message'; -import { ShowBadgeTooltip } from '~/context/BadgeTooltip'; +import { ShowBadgeTooltip, ShowNotification } from '~/context/Notification'; /** * Return a function to send a translation to the server. diff --git a/translate/src/modules/editor/hooks/useUpdateTranslationStatus.ts b/translate/src/modules/editor/hooks/useUpdateTranslationStatus.ts index 2a00c46ff5..ac786e59ac 100644 --- a/translate/src/modules/editor/hooks/useUpdateTranslationStatus.ts +++ b/translate/src/modules/editor/hooks/useUpdateTranslationStatus.ts @@ -7,8 +7,7 @@ import { EntityView } from '~/context/EntityView'; import { FailedChecksData } from '~/context/FailedChecksData'; import { HistoryData } from '~/context/HistoryData'; import { Location } from '~/context/Location'; -import { ShowNotification } from '~/context/Notification'; -import { ShowBadgeTooltip } from '~/context/BadgeTooltip'; +import { ShowBadgeTooltip, ShowNotification } from '~/context/Notification'; import { updateEntityTranslation } from '~/modules/entities/actions'; import { usePushNextTranslatable } from '~/modules/entities/hooks'; import {