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,
});