Skip to content

Commit

Permalink
🐛 Add task detail links to tasks page (#1980)
Browse files Browse the repository at this point in the history
1. extract base component to re-use for Analysis Details and Task
Details
2. add 2 new routes: 
   a) /tasks/:taskId 
   b) /tasks/:taskId/attachments/:attachmentId
3. user can enter details screen by: 
   a) task status name (link) 
   b) "Task details" action (per row kebab actions)
4. persist page state in session storage for Applications
    and Task Manager pages to fix problems with returning
    from task details page.

Resolves: #1975

---------

Signed-off-by: Radoslaw Szwajkowski <[email protected]>
  • Loading branch information
rszwajko authored Jun 26, 2024
1 parent cbef5e9 commit 91400ed
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 113 deletions.
5 changes: 4 additions & 1 deletion client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"selectNone": "Select none",
"selectPage": "Select page",
"submitReview": "Submit review",
"taskDetails": "Task details",
"unlink": "Unlink",
"view": "View",
"viewErrorReport": "View error report",
Expand Down Expand Up @@ -463,6 +464,7 @@
"tagCategory": "Tag category",
"tagCategoryDeleted": "Tag category deleted",
"tagCategories": "Tag categories",
"tasks": "Tasks",
"teamMember": "team member",
"terminated": "Terminated",
"ticket": "Ticket",
Expand All @@ -483,7 +485,8 @@
"titles": {
"archetypeDrawer": "Archetype details",
"taskManager": "Task Manager",
"task": "Task"
"task": "Task",
"taskWithId": "Task {{taskId}}"
},
"toastr": {
"success": {
Expand Down
7 changes: 7 additions & 0 deletions client/src/app/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const DevPaths = {

dependencies: "/dependencies",
tasks: "/tasks",
taskDetails: "/tasks/:taskId",
taskDetailsAttachment: "/tasks/:taskId/attachments/:attachmentId",
} as const;

export type DevPathValues = (typeof DevPaths)[keyof typeof DevPaths];
Expand Down Expand Up @@ -102,3 +104,8 @@ export interface AnalysisDetailsAttachmentRoute {
taskId: string;
attachmentId: string;
}

export interface TaskDetailsAttachmentRoute {
taskId: string;
attachmentId: string;
}
11 changes: 11 additions & 0 deletions client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const AssessmentSummary = lazy(
);

const TaskManager = lazy(() => import("./pages/tasks/tasks-page"));
const TaskDetails = lazy(() => import("./pages/tasks/TaskDetails"));

export interface IRoute<T> {
path: T;
Expand Down Expand Up @@ -195,6 +196,16 @@ export const devRoutes: IRoute<DevPathValues>[] = [
{
path: Paths.tasks,
comp: TaskManager,
exact: true,
},
{
path: Paths.taskDetails,
comp: TaskDetails,
exact: true,
},
{
path: Paths.taskDetailsAttachment,
comp: TaskDetails,
exact: false,
},
];
Expand Down
138 changes: 39 additions & 99 deletions client/src/app/pages/applications/analysis-details/AnalysisDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,116 +1,56 @@
import React from "react";
import { useHistory, useLocation, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";

import { PageSection } from "@patternfly/react-core";

import { AnalysisDetailsAttachmentRoute, Paths } from "@app/Paths";
import { PageHeader } from "@app/components/PageHeader";
import { formatPath } from "@app/utils/utils";
import {
DocumentId,
SimpleDocumentViewer,
} from "@app/components/simple-document-viewer";
import { useFetchApplicationById } from "@app/queries/applications";
import { useFetchTaskByID } from "@app/queries/tasks";

import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";
import { TaskDetailsBase } from "@app/pages/tasks/TaskDetailsBase";
import { useFetchApplicationById } from "@app/queries/applications";

export const AnalysisDetails: React.FC = () => {
export const AnalysisDetails = () => {
const { t } = useTranslation();

const { applicationId, taskId, attachmentId } =
useParams<AnalysisDetailsAttachmentRoute>();
const { search } = useLocation();
const hasMergedParam = new URLSearchParams(search).has("merged");

const history = useHistory();
const onDocumentChange = (documentId: DocumentId) =>
typeof documentId === "number"
? history.push(
formatPath(Paths.applicationsAnalysisDetailsAttachment, {
applicationId: applicationId,
taskId: taskId,
attachmentId: documentId,
})
)
: history.push({
pathname: formatPath(Paths.applicationsAnalysisDetails, {
applicationId: applicationId,
taskId: taskId,
}),
search: documentId === "MERGED_VIEW" ? "?merged=true" : undefined,
});
const detailsPath = formatPath(Paths.applicationsAnalysisDetails, {
applicationId: applicationId,
taskId: taskId,
});

const { application } = useFetchApplicationById(applicationId);
const { task } = useFetchTaskByID(Number(taskId));

const taskName = task?.name ?? t("terms.unknown");
const appName: string = application?.name ?? t("terms.unknown");
const attachmentName = task?.attached?.find(
({ id }) => String(id) === attachmentId
)?.name;
const resolvedAttachmentId = attachmentName
? Number(attachmentId)
: undefined;
const resolvedLogMode = hasMergedParam ? "MERGED_VIEW" : "LOG_VIEW";

return (
<>
<PageSection variant="light">
<PageHeader
title={`Analysis details for ${taskName}`}
breadcrumbs={[
{
title: t("terms.applications"),
path: Paths.applications,
},
{
title: appName,
path: `${Paths.applications}/?activeItem=${applicationId}`,
},
{
title: t("actions.analysisDetails"),
path: formatPath(Paths.applicationsAnalysisDetails, {
applicationId: applicationId,
taskId: taskId,
}),
},
...(attachmentName
? [
{
title: t("terms.attachments"),
},
{
title: attachmentName,
path: formatPath(Paths.applicationsAnalysisDetails, {
applicationId: applicationId,
taskId: taskId,
attachment: attachmentId,
}),
},
]
: []),
]}
/>
</PageSection>
<PageSection>
<div
style={{
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
}}
className="simple-task-viewer-container"
>
<SimpleDocumentViewer
// force re-creating viewer via keys
key={`${task?.id}/${task?.attached?.length}`}
taskId={task ? Number(taskId) : undefined}
documentId={resolvedAttachmentId || resolvedLogMode}
attachments={task?.attached ?? []}
onDocumentChange={onDocumentChange}
height="full"
/>
</div>
</PageSection>
</>
<TaskDetailsBase
breadcrumbs={[
{
title: t("terms.applications"),
path: Paths.applications,
},
{
title: appName,
path: `${Paths.applications}/?activeItem=${applicationId}`,
},
{
title: t("actions.analysisDetails"),
path: formatPath(Paths.applicationsAnalysisDetails, {
applicationId,
taskId,
}),
},
]}
detailsPath={detailsPath}
formatTitle={(taskName) => `Analysis details for ${taskName}`}
formatAttachmentPath={(attachmentId) =>
formatPath(Paths.applicationsAnalysisDetailsAttachment, {
applicationId,
taskId,
attachmentId,
})
}
taskId={Number(taskId)}
attachmentId={attachmentId}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,12 @@ export const ApplicationsTable: React.FC = () => {
isSortEnabled: true,
isPaginationEnabled: true,
isActiveItemEnabled: true,
persistTo: { activeItem: "urlParams" },
persistTo: {
activeItem: "urlParams",
filter: "sessionStorage",
pagination: "sessionStorage",
sort: "sessionStorage",
},
isLoading: isFetchingApplications,
sortableColumns: ["name", "businessService", "tags", "effort"],
initialSort: { columnKey: "name", direction: "asc" },
Expand Down
40 changes: 40 additions & 0 deletions client/src/app/pages/tasks/TaskDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";

import { Paths, TaskDetailsAttachmentRoute } from "@app/Paths";
import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";
import { formatPath } from "@app/utils/utils";
import { TaskDetailsBase } from "./TaskDetailsBase";

export const TaskDetails = () => {
const { t } = useTranslation();
const { taskId, attachmentId } = useParams<TaskDetailsAttachmentRoute>();
const detailsPath = formatPath(Paths.taskDetails, { taskId });
return (
<TaskDetailsBase
breadcrumbs={[
{
title: t("terms.tasks"),
path: Paths.tasks,
},
{
title: t("titles.taskWithId", { taskId }),
path: detailsPath,
},
]}
detailsPath={detailsPath}
formatTitle={(taskName) => `Task details for task ${taskId}, ${taskName}`}
formatAttachmentPath={(attachmentId) =>
formatPath(Paths.taskDetailsAttachment, {
taskId,
attachmentId,
})
}
taskId={Number(taskId)}
attachmentId={attachmentId}
/>
);
};

export default TaskDetails;
96 changes: 96 additions & 0 deletions client/src/app/pages/tasks/TaskDetailsBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";

import { PageSection } from "@patternfly/react-core";

import { PageHeader, PageHeaderProps } from "@app/components/PageHeader";
import {
DocumentId,
SimpleDocumentViewer,
} from "@app/components/simple-document-viewer";
import { useFetchTaskByID } from "@app/queries/tasks";
import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";

export const TaskDetailsBase: React.FC<{
breadcrumbs: PageHeaderProps["breadcrumbs"];
formatTitle: (taskName: string) => string;
detailsPath: string;
formatAttachmentPath: (attachmentId: number | string) => string;
taskId: number;
attachmentId?: string;
}> = ({
breadcrumbs,
formatTitle,
detailsPath,
formatAttachmentPath,
taskId,
attachmentId,
}) => {
const { t } = useTranslation();

const { search } = useLocation();
const hasMergedParam = new URLSearchParams(search).has("merged");

const history = useHistory();
const onDocumentChange = (documentId: DocumentId) =>
typeof documentId === "number"
? history.push(formatAttachmentPath(documentId))
: history.push({
pathname: detailsPath,
search: documentId === "MERGED_VIEW" ? "?merged=true" : undefined,
});

const { task } = useFetchTaskByID(taskId);

const taskName: string = task?.name ?? t("terms.unknown");
const attachmentName = task?.attached?.find(
({ id }) => String(id) === attachmentId
)?.name;
const resolvedAttachmentId = attachmentName
? Number(attachmentId)
: undefined;
const resolvedLogMode = hasMergedParam ? "MERGED_VIEW" : "LOG_VIEW";

return (
<>
<PageSection variant="light">
<PageHeader
title={formatTitle(taskName)}
breadcrumbs={[
...breadcrumbs,
...(attachmentName && attachmentId
? [
{
title: t("terms.attachments"),
},
{
title: attachmentName,
path: formatAttachmentPath(attachmentId),
},
]
: []),
]}
/>
</PageSection>
<PageSection>
<div
style={{
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
}}
className="simple-task-viewer-container"
>
<SimpleDocumentViewer
// force re-creating viewer via keys
key={`${task?.id}/${task?.attached?.length}`}
taskId={task ? taskId : undefined}
documentId={resolvedAttachmentId || resolvedLogMode}
attachments={task?.attached ?? []}
onDocumentChange={onDocumentChange}
height="full"
/>
</div>
</PageSection>
</>
);
};
Loading

0 comments on commit 91400ed

Please sign in to comment.