From efd55a009ece7fb6cbc9dad1dfeba51749bcb137 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Tue, 11 Jun 2024 21:29:55 +0200 Subject: [PATCH] Task Manager Signed-off-by: Radoslaw Szwajkowski --- client/public/locales/en/translation.json | 6 +- client/src/app/Constants.ts | 1 + client/src/app/Paths.ts | 1 + client/src/app/Routes.tsx | 8 + client/src/app/api/models.ts | 2 +- .../src/app/layout/SidebarApp/SidebarApp.tsx | 5 + client/src/app/pages/tasks/tasks-page.tsx | 242 ++++++++++++++++++ 7 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 client/src/app/pages/tasks/tasks-page.tsx diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index bbce9e047..70729f01b 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -249,7 +249,8 @@ "reports": "Reports", "migrationWaves": "Migration waves", "issues": "Issues", - "dependencies": "Dependencies" + "dependencies": "Dependencies", + "tasks": "Task Manager" }, "terms": { "accepted": "Accepted", @@ -467,7 +468,8 @@ "YAMLTemplate": "YAML template" }, "titles": { - "archetypeDrawer": "Archetype details" + "archetypeDrawer": "Archetype details", + "taskManager": "Task Manager" }, "toastr": { "success": { diff --git a/client/src/app/Constants.ts b/client/src/app/Constants.ts index 2633f1e29..80a622ee9 100644 --- a/client/src/app/Constants.ts +++ b/client/src/app/Constants.ts @@ -244,4 +244,5 @@ export enum TablePersistenceKeyPrefix { issuesRemainingIncidents = "ii", dependencyApplications = "da", archetypes = "ar", + tasks = "t", } diff --git a/client/src/app/Paths.ts b/client/src/app/Paths.ts index 57708283a..5e10daa1d 100644 --- a/client/src/app/Paths.ts +++ b/client/src/app/Paths.ts @@ -38,6 +38,7 @@ export const DevPaths = { issuesSingleAppSelected: "/issues/single-app/:applicationId", dependencies: "/dependencies", + tasks: "/tasks", } as const; export type DevPathValues = (typeof DevPaths)[keyof typeof DevPaths]; diff --git a/client/src/app/Routes.tsx b/client/src/app/Routes.tsx index 46cf40bd0..101f551f2 100644 --- a/client/src/app/Routes.tsx +++ b/client/src/app/Routes.tsx @@ -63,6 +63,9 @@ const AssessmentSummary = lazy( "./pages/assessment/components/assessment-summary/assessment-summary-page" ) ); + +const TaskManager = lazy(() => import("./pages/tasks/tasks-page")); + export interface IRoute { path: T; comp: React.ComponentType; @@ -184,6 +187,11 @@ export const devRoutes: IRoute[] = [ comp: Archetypes, exact: false, }, + { + path: Paths.tasks, + comp: TaskManager, + exact: false, + }, ]; export const adminRoutes: IRoute[] = [ diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index c106243c3..749c18584 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -308,7 +308,7 @@ export type TaskState = export interface Task { id?: number; createTime?: string; - application: { id: number }; + application: Ref; name: string; addon: string; data: TaskData; diff --git a/client/src/app/layout/SidebarApp/SidebarApp.tsx b/client/src/app/layout/SidebarApp/SidebarApp.tsx index 0fff82fe1..2b2c6d7c9 100644 --- a/client/src/app/layout/SidebarApp/SidebarApp.tsx +++ b/client/src/app/layout/SidebarApp/SidebarApp.tsx @@ -149,6 +149,11 @@ export const MigrationSidebar = () => { ) : null} + + + {t("sidebar.tasks")} + + ); diff --git a/client/src/app/pages/tasks/tasks-page.tsx b/client/src/app/pages/tasks/tasks-page.tsx new file mode 100644 index 000000000..06ca1c1a7 --- /dev/null +++ b/client/src/app/pages/tasks/tasks-page.tsx @@ -0,0 +1,242 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; +import { + EmptyState, + EmptyStateHeader, + EmptyStateIcon, + PageSection, + PageSectionVariants, + Text, + TextContent, + Toolbar, + ToolbarContent, + ToolbarItem, +} from "@patternfly/react-core"; +import { Table, Tbody, Th, Thead, Tr, Td } from "@patternfly/react-table"; +import { CubesIcon } from "@patternfly/react-icons"; + +import { FilterToolbar, FilterType } from "@app/components/FilterToolbar"; +import { + ConditionalTableBody, + TableHeaderContentWithControls, + TableRowContentWithControls, +} from "@app/components/TableControls"; +import { + deserializeFilterUrlParams, + getHubRequestParams, + useTableControlProps, + useTableControlState, +} from "@app/hooks/table-controls"; + +import { SimplePagination } from "@app/components/SimplePagination"; +import { TablePersistenceKeyPrefix } from "@app/Constants"; + +import { useSelectionState } from "@migtools/lib-ui"; +import { useServerTasks } from "@app/queries/tasks"; + +export const TasksPage: React.FC = () => { + const { t } = useTranslation(); + const history = useHistory(); + + const urlParams = new URLSearchParams(window.location.search); + const filters = urlParams.get("filters") ?? ""; + + const deserializedFilterValues = deserializeFilterUrlParams({ filters }); + + const tableControlState = useTableControlState({ + tableName: "tasks-table", + persistTo: { filter: "urlParams" }, + persistenceKeyPrefix: TablePersistenceKeyPrefix.tasks, + columnNames: { + id: "ID", + state: "State", + application: "Application", + kind: "Kind", + }, + initialFilterValues: deserializedFilterValues, + isFilterEnabled: true, + isSortEnabled: true, + isPaginationEnabled: true, + isActiveItemEnabled: true, + sortableColumns: ["id", "state", "application", "kind"], + initialSort: { columnKey: "id", direction: "desc" }, + filterCategories: [ + { + categoryKey: "id", + title: "ID", + type: FilterType.search, + placeholderText: t("actions.filterBy", { + what: "ID...", + }), + getServerFilterValue: (value) => (value ? value : []), + }, + { + categoryKey: "state", + title: "State", + type: FilterType.search, + placeholderText: t("actions.filterBy", { + what: "State...", + }), + getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []), + }, + { + categoryKey: "application", + title: "Application", + type: FilterType.search, + placeholderText: t("actions.filterBy", { + what: "Application...", + }), + serverFilterField: "application.id", + getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []), + }, + { + categoryKey: "kind", + title: "Kind", + type: FilterType.search, + placeholderText: t("actions.filterBy", { + what: "Kind...", + }), + getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []), + }, + ], + initialItemsPerPage: 10, + }); + + const { + result: { data: currentPageItems = [], total: totalItemCount }, + isFetching, + fetchError, + } = useServerTasks( + getHubRequestParams({ + ...tableControlState, + hubSortFieldKeys: { + id: "id", + state: "state", + application: "application.id", + kind: "kind", + }, + }) + ); + + const tableControls = useTableControlProps({ + ...tableControlState, + // task.id is defined as optional + idProperty: "name", + currentPageItems, + totalItemCount, + isLoading: isFetching, + selectionState: useSelectionState({ + items: currentPageItems, + isEqual: (a, b) => a.name === b.name, + }), + }); + + const { + numRenderedColumns, + propHelpers: { + toolbarProps, + filterToolbarProps, + paginationToolbarItemProps, + paginationProps, + tableProps, + getThProps, + getTrProps, + getTdProps, + }, + } = tableControls; + + const clearFilters = () => { + const currentPath = history.location.pathname; + const newSearch = new URLSearchParams(history.location.search); + newSearch.delete("filters"); + history.push(`${currentPath}`); + filterToolbarProps.setFilterValues({}); + }; + + return ( + <> + + + {t("titles.taskManager")} + + + +
+ + + + + + + + + + + + + + + + + } + /> + + } + numRenderedColumns={numRenderedColumns} + > + + {currentPageItems?.map((task, rowIndex) => ( + + + + + + + + + ))} + + +
+ + + + +
{task.id} + {task.state} + + {task.application.name ?? task.application.id} + kind?
+ +
+
+ + ); +}; + +export default TasksPage;