Skip to content

Commit

Permalink
Add SimpleDocumentViewerModal to display analysis details
Browse files Browse the repository at this point in the history
Instead of opening a new browser window to show a user a json
formatted analysis details document, open a modal on page with
an embedded and enhanced CodeEditor.

The `SimpleDocumentViewerModal` component allows fetching and
displaying the document as either a json or yaml document.  Users
can request the other format on screen.  By using the
`@patternfly/react-code-editor` component, a number of good things
are enabled:
  - the content is displayed on page and is code colored per language
  - the contents can be copied to the clipboard
  - the contents can be save to a file
  - the user can easily view either the json or yaml format of the
    same document, as provided by the rest api endpoint

Signed-off-by: Scott J Dickerson <sdickers@redhat.com>
  • Loading branch information
sjd78 committed Jun 28, 2023
1 parent 1b0ca2b commit aeeba06
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 33 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@patternfly/react-charts": "^6.67.1",
"@patternfly/react-code-editor": "^4.82.115",
"@patternfly/react-core": "^4.214.1",
"@patternfly/react-styles": "^4.92.6",
"@patternfly/react-table": "^4.83.1",
"@patternfly/react-tokens": "^4.66.1",
"@react-keycloak/web": "^3.4.0",
Expand Down
39 changes: 28 additions & 11 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const ASSESSMENTS = PATHFINDER + "/assessments";
const jsonHeaders = { headers: { Accept: "application/json" } };
const formHeaders = { headers: { Accept: "multipart/form-data" } };
const fileHeaders = { headers: { Accept: "application/json" } };
const yamlHeaders = { headers: { Accept: "application/x-yaml" } };

type Direction = "asc" | "desc";

Expand All @@ -128,17 +129,6 @@ const buildQuery = (params: any) => {
return query;
};

//Volumes
// poll clean task
export const getTaskById = ({
queryKey,
}: {
queryKey: QueryKey;
}): AxiosPromise<Task> => {
const [_, processId] = queryKey;
return axios.get<Task>(`${TASKS}/${processId}`);
};

// Business services

export const getBusinessServices = (): AxiosPromise<Array<BusinessService>> => {
Expand Down Expand Up @@ -426,6 +416,33 @@ export const getApplicationImports = (
.get(`${APP_IMPORT}?importSummary.id=${importSummaryID}&isValid=${isValid}`)
.then((response) => response.data);

export const getApplicationAnalysis = (
applicationId: number,
format: "json" | "yaml"
): Promise<string> => {
const headers = format === "yaml" ? yamlHeaders : jsonHeaders;
return axios
.get<string>(`${APPLICATIONS}/${applicationId}/analysis`, headers)
.then((response) => response.data);
};

export function getTaskById(id: number, format: "json"): Promise<Task>;
export function getTaskById(id: number, format: "yaml"): Promise<string>;
export function getTaskById(
id: number,
format: "json" | "yaml"
): Promise<Task | string> {
if (format === "yaml") {
return axios
.get<Task>(`${TASKS}/${id}`, yamlHeaders)
.then((response) => response.data);
} else {
return axios
.get<string>(`${TASKS}/${id}`, jsonHeaders)
.then((response) => response.data);
}
}

export const getTasks = () =>
axios.get<Task[]>(TASKS).then((response) => response.data);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ import { NotificationsContext } from "@app/shared/notifications-context";
import { ConfirmDialog } from "@app/shared/components/confirm-dialog/confirm-dialog";
import { ApplicationDetailDrawerAnalysis } from "../components/application-detail-drawer";
import { useQueryClient } from "@tanstack/react-query";
import { SimpleDocumentViewerModal } from "@app/shared/components/simple-task-viewer";
import { getTaskById } from "@app/api/rest";

const ENTITY_FIELD = "entity";

Expand All @@ -94,6 +96,11 @@ export const ApplicationsTableAnalyze: React.FC = () => {
const [isApplicationImportModalOpen, setIsApplicationImportModalOpen] =
React.useState(false);

const [taskToView, setTaskToView] = React.useState<{
name: string;
task: number | undefined;
}>();

// Router
const history = useHistory();

Expand Down Expand Up @@ -364,7 +371,7 @@ export const ApplicationsTableAnalyze: React.FC = () => {
isAriaDisabled: !getTask(row),
onClick: () => {
const task = getTask(row);
if (task) window.open(`/hub/tasks/${task.id}`, "_blank");
if (task) setTaskToView({ name: row.name, task: task.id });
},
});
}
Expand Down Expand Up @@ -727,6 +734,7 @@ export const ApplicationsTableAnalyze: React.FC = () => {
"dialog.message.delete"
)}`}
</Modal>

{isConfirmDialogOpen && (
<ConfirmDialog
title={t("dialog.title.delete", {
Expand All @@ -749,6 +757,13 @@ export const ApplicationsTableAnalyze: React.FC = () => {
}}
/>
)}

<SimpleDocumentViewerModal<Task | string>
title={`Analysis details for ${taskToView?.name}`}
fetch={getTaskById}
documentId={taskToView?.task}
onClose={() => setTaskToView(undefined)}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
import { EmptyTextMessage } from "@app/shared/components";
import { useFetchFacts } from "@app/queries/facts";
import { ApplicationFacts } from "./application-facts";
import { SimpleDocumentViewerModal } from "@app/shared/components/simple-task-viewer";
import { getApplicationAnalysis, getTaskById } from "@app/api/rest";

export interface IApplicationDetailDrawerAnalysisProps
extends Pick<
Expand All @@ -39,6 +41,8 @@ export const ApplicationDetailDrawerAnalysis: React.FC<

const { identities } = useFetchIdentities();
const { facts, isFetching } = useFetchFacts(application?.id);
const [appAnalysisToView, setAppAnalysisToView] = React.useState<number>();
const [taskIdToView, setTaskIdToView] = React.useState<number>();

let matchingSourceCredsRef: Identity | undefined;
let matchingMavenCredsRef: Identity | undefined;
Expand All @@ -47,10 +51,6 @@ export const ApplicationDetailDrawerAnalysis: React.FC<
matchingMavenCredsRef = getKindIDByRef(identities, application, "maven");
}

const openAnalysisDetails = () => {
if (task) window.open(`/hub/tasks/${task.id}`, "_blank");
};

const notAvailable = <EmptyTextMessage message={t("terms.notAvailable")} />;

const updatedApplication = applications?.find(
Expand Down Expand Up @@ -101,19 +101,25 @@ export const ApplicationDetailDrawerAnalysis: React.FC<
{task?.state === "Succeeded" && application ? (
<>
<Tooltip content="View Report">
<Button variant="link" isInline>
<Link
to={`/hub/applications/${application.id}/analysis`}
target="_blank"
>
View analysis
</Link>
<Button
type="button"
variant="link"
isInline
onClick={() => setAppAnalysisToView(application.id)}
>
View analysis
</Button>
</Tooltip>
<SimpleDocumentViewerModal<string>
title={`Analysis for ${application?.name}`}
fetch={getApplicationAnalysis}
documentId={appAnalysisToView}
onClose={() => setAppAnalysisToView(undefined)}
/>
</>
) : task?.state === "Failed" ? (
<>
{task ? (
task ? (
<>
<Button
icon={
<span className={spacing.mrXs}>
Expand All @@ -122,19 +128,25 @@ export const ApplicationDetailDrawerAnalysis: React.FC<
}
type="button"
variant="link"
onClick={openAnalysisDetails}
onClick={() => setTaskIdToView(task.id)}
className={spacing.ml_0}
style={{ margin: "0", padding: "0" }}
>
Analysis details
</Button>
) : (
<span className={spacing.mlSm}>
<ExclamationCircleIcon color="#c9190b"></ExclamationCircleIcon>
Failed
</span>
)}
</>
<SimpleDocumentViewerModal<Task | string>
title={`Analysis details for ${application?.name}`}
fetch={getTaskById}
documentId={taskIdToView}
onClose={() => setTaskIdToView(undefined)}
/>
</>
) : (
<span className={spacing.mlSm}>
<ExclamationCircleIcon color="#c9190b"></ExclamationCircleIcon>
Failed
</span>
)
) : (
notAvailable
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./simple-document-viewer";
Loading

0 comments on commit aeeba06

Please sign in to comment.