Skip to content

Commit

Permalink
✨ Add Tasks tab to the Applications table application details drawer (#…
Browse files Browse the repository at this point in the history
…2123)

Enhancing the application details drawer by adding a "Tasks" tab,
displaying a table of tasks associated with the application,
implementing filtering, sorting, and pagination functionalities, and
including columns for Task ID, Task Kind, Status, and Priority.

Fixes: #1937 


screenshot of result:
![Screenshot from 2024-10-30
11-36-28](https://github.com/user-attachments/assets/1e36c51d-8586-4c8b-a955-e850a777a4f4)

---------

Signed-off-by: EstyBiton <[email protected]>
Signed-off-by: esty <[email protected]>
  • Loading branch information
EstyBiton authored Mar 3, 2025
1 parent af54f9b commit cd1ceb0
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 15 deletions.
2 changes: 2 additions & 0 deletions client/src/app/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const DevPaths = {
applications: "/applications",
applicationsAnalysisDetails:
"/applications/:applicationId/analysis-details/:taskId",
applicationsTaskDetails: "/applications/:applicationId/tasks/:taskId",
applicationsAnalysisDetailsAttachment:
"/applications/:applicationId/analysis-details/:taskId/attachments/:attachmentId",
applicationsAnalysisTab: "/applications/analysis-tab",
Expand Down Expand Up @@ -108,4 +109,5 @@ export interface AnalysisDetailsAttachmentRoute {
export interface TaskDetailsAttachmentRoute {
taskId: string;
attachmentId: string;
applicationId: string;
}
5 changes: 5 additions & 0 deletions client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ export const devRoutes: IRoute<DevPathValues>[] = [
comp: AnalysisDetails,
exact: true,
},
{
path: Paths.applicationsTaskDetails,
comp: TaskDetails,
exact: true,
},
{
path: Paths.applicationsAnalysisDetailsAttachment,
comp: AnalysisDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const TaskItem: React.FC<{
: `${task.id} (${task.addon}) - ${task.applicationName} - ${
task.priority ?? 0
}`;

const taskActionItems = useTaskActions(task._);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,29 @@ import { useFetchArchetypes } from "@app/queries/archetypes";
import { useFetchAssessments } from "@app/queries/assessments";
import { DecoratedApplication } from "../../applications-table/useDecoratedApplications";
import { TaskStates } from "@app/queries/tasks";
import { Toolbar, ToolbarContent, ToolbarItem } from "@patternfly/react-core";
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
import {
useTableControlState,
useTableControlProps,
} from "@app/hooks/table-controls";
import { SimplePagination } from "@app/components/SimplePagination";
import { useServerTasks } from "@app/queries/tasks";
import { FilterToolbar, FilterType } from "@app/components/FilterToolbar";
import {
getHubRequestParams,
deserializeFilterUrlParams,
} from "@app/hooks/table-controls";
import { useSelectionState } from "@migtools/lib-ui";
import { TablePersistenceKeyPrefix } from "@app/Constants";
import { TaskActionColumn } from "../../../../pages/tasks/TaskActionColumn";
import {
ConditionalTableBody,
TableHeaderContentWithControls,
TableRowContentWithControls,
} from "@app/components/TableControls";
import { IconWithLabel, TaskStateIcon } from "@app/components/Icons";
import { taskStateToLabel } from "@app/pages/tasks/tasks-page";

export interface IApplicationDetailDrawerProps
extends Pick<IPageDrawerContentProps, "onCloseClick"> {
Expand All @@ -73,18 +96,20 @@ export interface IApplicationDetailDrawerProps
onEditClick: () => void;
}

enum TabKey {
export enum TabKey {
Details = 0,
Tags,
Reports,
Facts,
Reviews,
Tasks,
}

export const ApplicationDetailDrawer: React.FC<
IApplicationDetailDrawerProps
> = ({ application, task, onCloseClick, onEditClick }) => {
const { t } = useTranslation();

const [activeTabKey, setActiveTabKey] = React.useState<TabKey>(
TabKey.Details
);
Expand Down Expand Up @@ -152,6 +177,14 @@ export const ApplicationDetailDrawer: React.FC<
<ReviewFields application={application} />
</Tab>
)}
{!application ? null : (
<Tab
eventKey={TabKey.Tasks}
title={<TabTitleText>{t("terms.tasks")}</TabTitleText>}
>
<TabTasksContent application={application} task={task} />
</Tab>
)}
</Tabs>
</div>
</PageDrawerContent>
Expand Down Expand Up @@ -521,3 +554,206 @@ const TabReportsContent: React.FC<{
</>
);
};

const TabTasksContent: React.FC<{
application: DecoratedApplication;
task: TaskDashboard | null;
}> = ({ application, task }) => {
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-apps-table",
persistTo: "urlParams",
persistenceKeyPrefix: TablePersistenceKeyPrefix.tasks,
columnNames: {
taskId: "Task ID",
taskKind: "Task Kind",
status: "Status",
},
isFilterEnabled: true,
isSortEnabled: true,
isPaginationEnabled: true,
sortableColumns: ["taskId", "taskKind", "status"],
initialSort: { columnKey: "taskId", direction: "asc" },
initialFilterValues: deserializedFilterValues,
filterCategories: [
{
categoryKey: "id",
title: "ID",
type: FilterType.numsearch,
placeholderText: t("actions.filterBy", {
what: "ID...",
}),
getServerFilterValue: (value) => {
console.log("this id:", value);
return value ? value : [];
},
},
{
categoryKey: "kind",
title: t("terms.kind"),
type: FilterType.search,
placeholderText: t("actions.filterBy", {
what: t("terms.kind") + "...",
}),
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
{
categoryKey: "state",
title: t("terms.status"),
type: FilterType.search,
placeholderText: t("actions.filterBy", {
what: t("terms.status") + "...",
}),
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
],
initialItemsPerPage: 10,
});

const {
result: { data: currentPageItems = [], total: totalItemCount },
isFetching,
fetchError,
} = useServerTasks(
getHubRequestParams({
...tableControlState,
hubSortFieldKeys: {
taskId: "id",
taskKind: "kind",
status: "status",
},
implicitFilters: [
{
field: "application.id",
operator: "=",
value: application.id,
},
],
}),
5000
);
const tableControls = useTableControlProps({
...tableControlState,
idProperty: "id",
currentPageItems: currentPageItems,
totalItemCount,
isLoading: isFetching,
variant: "compact",
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}`);
};
return (
<>
<Toolbar
{...toolbarProps}
className={spacing.mtSm}
clearAllFilters={clearFilters}
>
<ToolbarContent>
<FilterToolbar {...filterToolbarProps} />
<ToolbarItem {...paginationToolbarItemProps}>
<SimplePagination
idPrefix="task-apps-table"
isTop
isCompact
paginationProps={paginationProps}
/>
</ToolbarItem>
</ToolbarContent>
</Toolbar>
<Table {...tableProps} aria-label="task applications table">
<Thead>
<Tr>
<TableHeaderContentWithControls {...tableControls}>
<Th {...getThProps({ columnKey: "taskId" })} />
<Th
{...getThProps({ columnKey: "taskKind" })}
modifier="nowrap"
/>
<Th {...getThProps({ columnKey: "status" })} modifier="nowrap" />
</TableHeaderContentWithControls>
</Tr>
</Thead>

<ConditionalTableBody
isLoading={isFetching}
isError={!!fetchError}
isNoData={totalItemCount === 0}
numRenderedColumns={numRenderedColumns}
>
<Tbody>
{currentPageItems?.map((task, rowIndex) => (
<Tr key={task.id} {...getTrProps({ item: task })}>
<TableRowContentWithControls
{...tableControls}
item={task}
rowIndex={rowIndex}
>
<Td {...getTdProps({ columnKey: "taskId" })}>{task.id}</Td>
<Td {...getTdProps({ columnKey: "taskKind" })}>
{task.kind}
</Td>
<Td {...getTdProps({ columnKey: "status" })}>
<IconWithLabel
icon={<TaskStateIcon state={task.state} />}
label={
<Link
to={formatPath(Paths.applicationsTaskDetails, {
applicationId: application.id,
taskId: task.id,
})}
>
{t(taskStateToLabel[task.state ?? "No task"])}
</Link>
}
/>
</Td>
<Td
key={`row-actions-${task.id}`}
isActionCell
id={`row-actions-${task.id}`}
>
<TaskActionColumn task={task} />
</Td>
</TableRowContentWithControls>
</Tr>
))}
</Tbody>
</ConditionalTableBody>
</Table>
<SimplePagination
idPrefix="task-apps-table"
isTop={false}
isCompact
paginationProps={paginationProps}
/>
</>
);
};
31 changes: 25 additions & 6 deletions client/src/app/pages/tasks/TaskDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,43 @@ import { Paths, TaskDetailsAttachmentRoute } from "@app/Paths";
import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";
import { formatPath } from "@app/utils/utils";
import { TaskDetailsBase } from "./TaskDetailsBase";
import { useFetchApplicationById } from "@app/queries/applications";

export const TaskDetails = () => {
const { t } = useTranslation();
const { taskId, attachmentId } = useParams<TaskDetailsAttachmentRoute>();
const detailsPath = formatPath(Paths.taskDetails, { taskId });
const { taskId, attachmentId, applicationId } =
useParams<TaskDetailsAttachmentRoute>();
const currentPath = window.location.pathname;
const isFromApplication = currentPath.includes("application") ? true : false;
const { application } = useFetchApplicationById(applicationId);

const appName: string = application?.name ?? t("terms.unknown");
console.log(appName);
const detailsPath = isFromApplication
? formatPath(Paths.applicationsTaskDetails, {
applicationId: applicationId,
taskId: taskId,
})
: formatPath(Paths.taskDetails, { taskId });

return (
<TaskDetailsBase
breadcrumbs={[
{
title: t("terms.tasks"),
path: Paths.tasks,
title: t(isFromApplication ? "terms.applications" : "terms.tasks"),
path: isFromApplication ? Paths.applications : Paths.tasks,
},
isFromApplication
? {
title: appName,
path: `${Paths.applications}/?activeItem=${applicationId}`,
}
: null,
{
title: t("titles.taskWithId", { taskId }),
path: detailsPath,
},
]}
].filter(Boolean)}
detailsPath={detailsPath}
formatTitle={(taskName) => `Task details for task ${taskId}, ${taskName}`}
formatAttachmentPath={(attachmentId) =>
Expand All @@ -36,5 +56,4 @@ export const TaskDetails = () => {
/>
);
};

export default TaskDetails;
3 changes: 1 addition & 2 deletions client/src/app/pages/tasks/tasks-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { formatPath } from "@app/utils/utils";
import { Paths } from "@app/Paths";
import { TaskActionColumn } from "./TaskActionColumn";

const taskStateToLabel: Record<TaskState, string> = {
export const taskStateToLabel: Record<TaskState, string> = {
"No task": "taskState.NoTask",
"not supported": "",
Canceled: "taskState.Canceled",
Expand All @@ -71,7 +71,6 @@ export const TasksPage: React.FC = () => {

const urlParams = new URLSearchParams(window.location.search);
const filters = urlParams.get("filters") ?? "";

const deserializedFilterValues = deserializeFilterUrlParams({ filters });

const tableControlState = useTableControlState({
Expand Down
Loading

0 comments on commit cd1ceb0

Please sign in to comment.