diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 75b620e039..c06af21b3f 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -260,6 +260,9 @@ "dependencies": "Dependencies", "tasks": "Task Manager" }, + "taskState": { + "NoTask": "No Task" + }, "terms": { "accepted": "Accepted", "acceptedAppsAndDeps": "Accepted applications and dependencies", diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index 635aa59886..6ccad4e4a9 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -304,7 +304,8 @@ export type TaskState = | "QuotaBlocked" | "Ready" | "Pending" - | "Postponed"; + | "Postponed" + | "SucceededWithErrors"; // synthetic state for ease-of-use in UI; export interface Task { id: number; diff --git a/client/src/app/components/Icons/TaskStateIcon.tsx b/client/src/app/components/Icons/TaskStateIcon.tsx index e78e30f343..4a481f5b7f 100644 --- a/client/src/app/components/Icons/TaskStateIcon.tsx +++ b/client/src/app/components/Icons/TaskStateIcon.tsx @@ -6,6 +6,7 @@ import { TimesCircleIcon, InProgressIcon, ExclamationCircleIcon, + ExclamationTriangleIcon, UnknownIcon, PendingIcon, TaskIcon, @@ -29,6 +30,12 @@ export const TaskStateIcon: FC<{ state?: TaskState }> = ({ state }) => { ); + case "SucceededWithErrors": + return ( + + + + ); case "Failed": return ( diff --git a/client/src/app/pages/tasks/tasks-page.tsx b/client/src/app/pages/tasks/tasks-page.tsx index 915c5f6ad3..e0d95bec6c 100644 --- a/client/src/app/pages/tasks/tasks-page.tsx +++ b/client/src/app/pages/tasks/tasks-page.tsx @@ -42,7 +42,7 @@ import { TablePersistenceKeyPrefix } from "@app/Constants"; import { useSelectionState } from "@migtools/lib-ui"; import { useServerTasks } from "@app/queries/tasks"; -import { Task } from "@app/api/models"; +import { Task, TaskState } from "@app/api/models"; import { IconWithLabel, TaskStateIcon } from "@app/components/Icons"; import { ManageColumnsToolbar } from "../applications/applications-table/components/manage-columns-toolbar"; import dayjs from "dayjs"; @@ -50,6 +50,21 @@ import { formatPath } from "@app/utils/utils"; import { Paths } from "@app/Paths"; import { TaskActionColumn } from "./TaskActionColumn"; +const taskStateToLabel: Record = { + "No task": "taskState.NoTask", + "not supported": "", + Canceled: "Canceled", + Created: "Created", + Succeeded: "Succeeded", + Failed: "Failed", + Running: "Running", + QuotaBlocked: "Quota Blocked", + Ready: "Ready", + Pending: "Pending", + Postponed: "Postponed", + SucceededWithErrors: "Succeeded with Errors", +}; + export const TasksPage: React.FC = () => { const { t } = useTranslation(); const history = useHistory(); @@ -235,7 +250,7 @@ export const TasksPage: React.FC = () => { taskId: id, })} > - {state ?? "No task"} + {t(taskStateToLabel[state ?? "No task"])} } /> diff --git a/client/src/app/queries/tasks.ts b/client/src/app/queries/tasks.ts index 512e13fc3d..84967f174a 100644 --- a/client/src/app/queries/tasks.ts +++ b/client/src/app/queries/tasks.ts @@ -25,27 +25,36 @@ export const TaskStates = { Failed: ["Failed"], Queued: ["Ready", "Postponed", "Pending", "Running"], // "Created", "QuotaBlocked" ?? Running: ["Running"], - Success: ["Succeeded"], + Success: ["Succeeded", "SucceededWithErrors"], }; export const TasksQueryKey = "tasks"; -export const TasksQueueKey = "TasksQueue"; +export const TasksPagedQueryKey = "tasksPaged"; +export const TasksQueueKey = "tasksQueue"; export const TaskByIDQueryKey = "taskByID"; export const TaskAttachmentByIDQueryKey = "taskAttachmentByID"; +/** + * Rebuild the __state__ of a Task to include the UI synthetic "SucceededWithErrors" + */ +const calculateSyntheticState = (task: Task): Task => { + if (task.state === "Succeeded" && (task.errors?.length ?? 0) > 0) { + task.state = "SucceededWithErrors"; + } + + return task; +}; + export const useFetchTasks = (refetchDisabled: boolean = false) => { const { isLoading, error, refetch, data } = useQuery({ queryKey: [TasksQueryKey], queryFn: getTasks, - refetchInterval: !refetchDisabled ? 5000 : false, - select: (allTasks) => { - // sort by createTime (newest to oldest) - allTasks.sort( - (a, b) => -1 * universalComparator(a.createTime, b.createTime) - ); - return allTasks; - }, + select: (tasks) => + tasks + .map(calculateSyntheticState) + .sort((a, b) => -1 * universalComparator(a.createTime, b.createTime)), onError: (err) => console.log(err), + refetchInterval: !refetchDisabled ? 5000 : false, }); const hasActiveTasks = @@ -61,13 +70,19 @@ export const useFetchTasks = (refetchDisabled: boolean = false) => { }; export const useServerTasks = ( - params: HubRequestParams = {}, + params: HubRequestParams, refetchInterval?: number ) => { const { data, isLoading, error, refetch } = useQuery({ - queryKey: [TasksQueryKey, params], + queryKey: [TasksPagedQueryKey, params], queryFn: async () => await getServerTasks(params), - onError: (error) => console.log("error, ", error), + select: (data) => { + if (data?.data?.length > 0) { + data.data = data.data.map(calculateSyntheticState); + } + return data; + }, + onError: (error: Error) => console.log("error, ", error), keepPreviousData: true, refetchInterval: refetchInterval ?? false, }); @@ -184,6 +199,8 @@ export const useFetchTaskByID = (taskId?: number) => { const { isLoading, error, data, refetch } = useQuery({ queryKey: [TaskByIDQueryKey, taskId], queryFn: () => (taskId ? getTaskById(taskId) : null), + select: (task: Task | null) => + task === null ? null : calculateSyntheticState(task), enabled: !!taskId, });