Skip to content

Commit

Permalink
Task Manager
Browse files Browse the repository at this point in the history
Signed-off-by: Radoslaw Szwajkowski <rszwajko@redhat.com>
  • Loading branch information
rszwajko committed Jun 12, 2024
1 parent 1dfdb51 commit ffcc13e
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 2 deletions.
3 changes: 2 additions & 1 deletion client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@
"reports": "Reports",
"migrationWaves": "Migration waves",
"issues": "Issues",
"dependencies": "Dependencies"
"dependencies": "Dependencies",
"tasks": "Task Manager"
},
"terms": {
"accepted": "Accepted",
Expand Down
1 change: 1 addition & 0 deletions client/src/app/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,5 @@ export enum TablePersistenceKeyPrefix {
issuesRemainingIncidents = "ii",
dependencyApplications = "da",
archetypes = "ar",
tasks = "t",
}
1 change: 1 addition & 0 deletions client/src/app/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
8 changes: 8 additions & 0 deletions client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
path: T;
comp: React.ComponentType<any>;
Expand Down Expand Up @@ -184,6 +187,11 @@ export const devRoutes: IRoute<DevPathValues>[] = [
comp: Archetypes,
exact: false,
},
{
path: Paths.tasks,
comp: TaskManager,
exact: false,
},
];

export const adminRoutes: IRoute<AdminPathValues>[] = [
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions client/src/app/layout/SidebarApp/SidebarApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ export const MigrationSidebar = () => {
</NavItem>
</>
) : null}
<NavItem>
<NavLink to={DevPaths.tasks} activeClassName="pf-m-current">
{t("sidebar.taskManager")}
</NavLink>
</NavItem>
</NavList>
</PersonaSidebar>
);
Expand Down
69 changes: 69 additions & 0 deletions client/src/app/pages/tasks/TaskAppsDetailDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from "react";
import { useTranslation } from "react-i18next";

import {
TextContent,
Text,
Title,
Tabs,
TabTitleText,
Tab,
} from "@patternfly/react-core";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";

import { Task } from "@app/api/models";
import { PageDrawerContent } from "@app/components/PageDrawerContext";
import { StateNoData } from "@app/components/StateNoData";

enum TabKey {
Applications = 0,
}

export const TaskAppsDetailDrawer: React.FC<{
task?: Task;
onCloseClick: () => void;
}> = ({ task, onCloseClick }) => {
const { t } = useTranslation();

const [activeTabKey, setActiveTabKey] = React.useState<TabKey>(
TabKey.Applications
);

return (
<PageDrawerContent
isExpanded={!!task}
onCloseClick={onCloseClick}
focusKey={task?.name}
pageKey="tasks-page"
drawerPanelContentProps={{ defaultSize: "600px" }}
header={
<TextContent>
<Text component="small" className={spacing.mb_0}>
Task
</Text>
<Title headingLevel="h2" size="lg" className={spacing.mtXs}>
{task?.name || ""} /{" "}
</Title>
</TextContent>
}
>
{!task ? (
<StateNoData />
) : (
<div>
<Tabs
activeKey={activeTabKey}
onSelect={(_event, tabKey) => setActiveTabKey(tabKey as TabKey)}
>
<Tab
eventKey={TabKey.Applications}
title={<TabTitleText>Applications</TabTitleText>}
>
{t("terms.tasks")}
</Tab>
</Tabs>
</div>
)}
</PageDrawerContent>
);
};
235 changes: 235 additions & 0 deletions client/src/app/pages/tasks/tasks-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
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";
import { TaskAppsDetailDrawer } from "./TaskAppsDetailDrawer";

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", activeItem: "urlParams" },
persistenceKeyPrefix: TablePersistenceKeyPrefix.tasks,
columnNames: {
id: "ID",
state: "State",
// application: "Application",
},
initialFilterValues: deserializedFilterValues,
isFilterEnabled: true,
isSortEnabled: true,
isPaginationEnabled: true,
isActiveItemEnabled: true,
sortableColumns: ["id", "state"],
initialSort: { columnKey: "id", direction: "asc" },
filterCategories: [
{
categoryKey: "id",
title: "ID",
type: FilterType.search,
placeholderText: t("actions.filterBy", {
what: "ID...",
}),
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
{
categoryKey: "state",
title: "State",
type: FilterType.search,
placeholderText: t("actions.filterBy", {
what: "State...",
}),
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
],
initialItemsPerPage: 10,
});

const {
result: { data: currentPageItems = [], total: totalItemCount },
isFetching,
fetchError,
} = useServerTasks(
getHubRequestParams({
...tableControlState,
hubSortFieldKeys: {
id: "id",
state: "state",
},
})
);

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,
},
activeItemDerivedState: { activeItem, clearActiveItem },
} = tableControls;

// const token = keycloak.tokenParsed;
// const userScopes: string[] = token?.scope.split(" ") || [],
// archetypeWriteAccess = checkAccess(userScopes, archetypesWriteScopes),
// assessmentWriteAccess = checkAccess(userScopes, assessmentWriteScopes),
// reviewsWriteAccess = checkAccess(userScopes, reviewsWriteScopes);

const clearFilters = () => {
const currentPath = history.location.pathname;
const newSearch = new URLSearchParams(history.location.search);
newSearch.delete("filters");
history.push(`${currentPath}`);
filterToolbarProps.setFilterValues({});
};

return (
<>
<PageSection variant={PageSectionVariants.light}>
<TextContent>
<Text component="h1">{t("terms.tasks")}</Text>
</TextContent>
</PageSection>
<PageSection>
<div
style={{
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
}}
>
<Toolbar {...toolbarProps} clearAllFilters={clearFilters}>
<ToolbarContent>
<FilterToolbar {...filterToolbarProps} />
<ToolbarItem {...paginationToolbarItemProps}>
<SimplePagination
idPrefix="tasks-table"
isTop
paginationProps={paginationProps}
/>
</ToolbarItem>
</ToolbarContent>
</Toolbar>

<Table {...tableProps} id="tasks-table" aria-label="Tasks table">
<Thead>
<Tr>
<TableHeaderContentWithControls {...tableControls}>
<Th {...getThProps({ columnKey: "id" })} />
{/* <Th {...getThProps({ columnKey: "application" })} /> */}
</TableHeaderContentWithControls>
</Tr>
</Thead>
<ConditionalTableBody
isLoading={isFetching}
isError={!!fetchError}
isNoData={currentPageItems.length === 0}
noDataEmptyState={
<EmptyState variant="sm">
<EmptyStateHeader
titleText="No tasks found"
headingLevel="h2"
icon={<EmptyStateIcon icon={CubesIcon} />}
/>
</EmptyState>
}
numRenderedColumns={numRenderedColumns}
>
<Tbody>
{currentPageItems?.map((task, rowIndex) => (
<Tr key={task.id} {...getTrProps({ item: task })}>
<TableRowContentWithControls
{...tableControls}
item={task}
rowIndex={rowIndex}
>
<Td width={25} {...getTdProps({ columnKey: "id" })}>
{task.id}
</Td>
<Td width={25} {...getTdProps({ columnKey: "state" })}>
{task.state}
</Td>
{/* <Td
width={25}
{...getTdProps({ columnKey: "application" })}
>
{task.application.name}
</Td> */}
</TableRowContentWithControls>
</Tr>
))}
</Tbody>
</ConditionalTableBody>
</Table>
<SimplePagination
idPrefix="dependencies-table"
isTop={false}
paginationProps={paginationProps}
/>
</div>
</PageSection>

<TaskAppsDetailDrawer
task={activeItem ?? undefined}
onCloseClick={() => clearActiveItem()}
/>
</>
);
};

export default TasksPage;

0 comments on commit ffcc13e

Please sign in to comment.