fix jobqueue widget and enhance it with internationalization support#512
fix jobqueue widget and enhance it with internationalization support#512Felipedino wants to merge 1 commit intodevelopfrom
Conversation
…prove job status handling
There was a problem hiding this comment.
Pull request overview
This PR updates the job queue UI to be fully localizable via react-i18next and refines the widget’s presentation/interaction, while also adjusting the job polling hook to better handle incremental job updates.
Changes:
- Added
jobQueuetranslation keys tocommon.json(EN/ES) and replaced hardcoded strings in job queue components with i18n keys. - Refactored
JobQueueWidgetUI (ListItemButton usage, styling tweaks, clearer empty/loading/error states, show-completed toggle copy). - Updated
useJobManagerto sort jobs bylast_updateand merge incremental updates from the poller.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| DashAI/front/src/utils/i18n/locales/es/common.json | Adds Spanish translation keys for job queue UI text/status labels. |
| DashAI/front/src/utils/i18n/locales/en/common.json | Adds English translation keys for job queue UI text/status labels. |
| DashAI/front/src/hooks/useJobPolling.js | Adds incremental-merge behavior and sorting for job manager updates. |
| DashAI/front/src/components/jobs/JobQueueWidget.jsx | Replaces UI strings with i18n keys; updates list interaction and styling. |
| DashAI/front/src/components/jobs/JobDetailsDialog.jsx | Replaces UI strings with i18n keys; adds translated status label mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const getStatusText = (status, t) => { | ||
| switch (status) { | ||
| case "not_started": | ||
| return t("common:jobQueue.status.queued"); | ||
| case "started": | ||
| return t("common:jobQueue.status.running"); | ||
| case "finished": | ||
| return t("common:jobQueue.status.completed"); | ||
| case "error": | ||
| return t("common:jobQueue.status.failed"); | ||
| case "deleted": | ||
| return t("common:jobQueue.status.deleted"); | ||
| default: | ||
| return status || t("common:unknown"); | ||
| } |
There was a problem hiding this comment.
getStatusText() is duplicated here and in JobQueueWidget.jsx. To avoid future divergence (e.g., adding a new status or changing wording in one place only), consider extracting a shared helper (e.g., utils/jobStatusText) that both components import.
| const getStatusText = (status, t) => { | |
| switch (status) { | |
| case "not_started": | |
| return t("common:jobQueue.status.queued"); | |
| case "started": | |
| return t("common:jobQueue.status.running"); | |
| case "finished": | |
| return t("common:jobQueue.status.completed"); | |
| case "error": | |
| return t("common:jobQueue.status.failed"); | |
| case "deleted": | |
| return t("common:jobQueue.status.deleted"); | |
| default: | |
| return status || t("common:unknown"); | |
| } | |
| const JOB_STATUS_TRANSLATION_KEYS = { | |
| not_started: "common:jobQueue.status.queued", | |
| started: "common:jobQueue.status.running", | |
| finished: "common:jobQueue.status.completed", | |
| error: "common:jobQueue.status.failed", | |
| deleted: "common:jobQueue.status.deleted", | |
| }; | |
| const getStatusText = (status, t) => { | |
| const key = status && JOB_STATUS_TRANSLATION_KEYS[status]; | |
| if (key) { | |
| return t(key); | |
| } | |
| return status || t("common:unknown"); |
| "activeCount": "{{count}} activos", | ||
| "failedCount": "{{count}} fallidos", |
There was a problem hiding this comment.
activeCount/failedCount are used with count, but the Spanish strings are always plural (e.g., 1 activos, 1 fallidos). Add proper i18next plural forms (e.g., activeCount_one/activeCount_other, failedCount_one/failedCount_other) or rephrase to avoid incorrect singular grammar.
| "activeCount": "{{count}} activos", | |
| "failedCount": "{{count}} fallidos", | |
| "activeCount_one": "{{count}} activo", | |
| "activeCount_other": "{{count}} activos", | |
| "failedCount_one": "{{count}} fallido", | |
| "failedCount_other": "{{count}} fallidos", |
| const unsubscribe = subscribeJobs((updatedJobs) => { | ||
| if (Array.isArray(updatedJobs)) { | ||
| setJobs(updatedJobs); | ||
| // `updatedJobs` is incremental (changes since cursor), so merge it. | ||
| setJobs((previousJobs) => mergeJobsById(previousJobs, updatedJobs)); | ||
| setLoading(false); |
There was a problem hiding this comment.
subscribeJobs receives incremental changes (see job poller using /v1/job/changes). Merging updates means jobs deleted server-side (e.g., via deleteJob/deleteAllJobs, which remove rows from task_copy) will never be removed from jobs, so the UI can keep showing deleted items forever. Consider making refresh() re-fetch the full list (getJobs) and replace state (or have the poller/subscription provide a full snapshot / explicit deletions / queue_empty flag so the manager can reconcile removals).
| return Array.from(byId.values()).sort( | ||
| (a, b) => new Date(b.last_update) - new Date(a.last_update), | ||
| ); |
There was a problem hiding this comment.
Sorting by new Date(job.last_update) is risky because the backend timestamps are 'YYYY-MM-DD HH:MM:SS.ffffff' (not ISO8601). Date parsing is inconsistent across browsers (notably Safari) and can yield Invalid Date, breaking ordering. Reuse the widget’s normalization (replace(' ', 'T') + 'Z') or add a shared timestamp parser before sorting.
| const getJobsToShow = () => { | ||
| if (showFinished) { | ||
| return jobs; | ||
| } else { | ||
| return [...activeJobs] | ||
| return [...jobs] | ||
| .sort((a, b) => new Date(b.last_update) - new Date(a.last_update)) | ||
| .slice(0, 10); | ||
| .slice(0, 25); |
There was a problem hiding this comment.
getJobsToShow() sorts using new Date(last_update), but last_update comes from SQLite-style timestamps (space-separated, no timezone). This can parse inconsistently across browsers and lead to incorrect ordering. Consider normalizing last_update the same way getRelativeTime() does (or centralize into a shared parse helper) before sorting in both branches.
| {loading && jobs.length === 0 && ( | ||
| <Box display="flex" justifyContent="center" p={2}> | ||
| <Typography variant="body2" color="text.secondary"> | ||
| Loading jobs... | ||
| {t("common:loading")} | ||
| </Typography> |
There was a problem hiding this comment.
PR description says all user-facing strings in the job queue are now translatable, but the relative-time strings shown in the list (e.g., “just now”, “Xs ago”, “in Xm”, “time unknown”) are still hardcoded in getRelativeTime(). Please move these to i18n keys and use t(...) with interpolation so they’re localizable as well.
This pull request enhances the internationalization and user experience of the job queue components by replacing hardcoded UI strings with translation keys and improving styling and interaction patterns. The changes ensure that all user-facing text in the job queue and job details dialogs are translatable and that the UI is more consistent with the application's theme.
Internationalization improvements:
JobDetailsDialog.jsxandJobQueueWidget.jsxwith translation keys usingreact-i18next, including job status, button labels, error messages, and tooltips. Added helper functions likegetStatusTextto support translated status labels. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21]UI/UX and code improvements:
ListItemButtonfor better accessibility and interaction, improved sorting and limiting of displayed jobs, and refined the handling of empty, loading, and error states. [1] [2]Code structure:
statusTextobject and replaced it with thegetStatusTextfunction to centralize status translation logic.These changes make the job queue features fully localizable and provide a more polished and accessible user interface.