Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix broken analysis report download #1407

Merged
merged 4 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@

type Direction = "asc" | "desc";

const buildQuery = (params: any) => {

Check warning on line 120 in client/src/app/api/rest.ts

View workflow job for this annotation

GitHub Actions / unit-test (18.x)

'buildQuery' is assigned a value but never used

Check warning on line 120 in client/src/app/api/rest.ts

View workflow job for this annotation

GitHub Actions / unit-test (18.x)

Unexpected any. Specify a different type
const query: string[] = [];

Object.keys(params).forEach((key) => {
const value = (params as any)[key];

Check warning on line 124 in client/src/app/api/rest.ts

View workflow job for this annotation

GitHub Actions / unit-test (18.x)

Unexpected any. Specify a different type

if (value !== undefined && value !== null) {
let queryParamValues: string[] = [];
Expand Down Expand Up @@ -721,23 +721,8 @@
export const getQuestionnaires = (): Promise<Questionnaire[]> =>
axios.get(QUESTIONNAIRES).then((response) => response.data);

export const getQuestionnaireById = <T>(
id: number | string,
isBlob: boolean = false
): Promise<T> =>
axios
.get(
`${QUESTIONNAIRES}/${id}`,
isBlob
? {
responseType: "blob",
headers: {
Accept: "application/x-yaml",
},
}
: {}
)
.then((response) => response.data);
export const getQuestionnaireById = <T>(id: number | string): Promise<T> =>
axios.get(`${QUESTIONNAIRES}/${id}`).then((response) => response.data);

Check warning on line 725 in client/src/app/api/rest.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/api/rest.ts#L725

Added line #L725 was not covered by tests

export const createQuestionnaire = (
obj: Questionnaire
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,19 @@ export const ApplicationDetailDrawerAnalysis: React.FC<
position="top"
>
<DownloadButton
id={application.id}
application={application}
mimeType={MimeType.TAR}
>
TAR
</DownloadButton>
/>
</Tooltip>
{" | "}
<Tooltip
content="Click to download Analysis report YAML file"
position="top"
>
<DownloadButton
id={application.id}
application={application}
mimeType={MimeType.YAML}
>
YAML
</DownloadButton>
/>
</Tooltip>
</DescriptionListDescription>
</DescriptionListGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,56 @@
import React from "react";
import { Button } from "@patternfly/react-core";
import { Alert, Button } from "@patternfly/react-core";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
import { saveAs } from "file-saver";

import { MimeType } from "@app/api/models";
import { useFetchStaticReport } from "@app/queries/applications";
import { NotificationsContext } from "@app/components/NotificationsContext";
import { AxiosError } from "axios";
import { getAxiosErrorMessage } from "@app/utils/utils";
import { Spinner } from "@patternfly/react-core";
import { useDownloadStaticReport } from "@app/queries/applications";
import { Application } from "@app/api/models";

export enum MimeType {
TAR = "tar",
YAML = "yaml",
}
interface IDownloadButtonProps {
id: number;
application: Application;
mimeType: MimeType;
children: React.ReactNode;
}

export const DownloadButton: React.FC<IDownloadButtonProps> = ({
id,
application,
mimeType,
children,
}) => {
const { pushNotification } = React.useContext(NotificationsContext);

const onFetchStaticReportError = (error: AxiosError) => {
pushNotification({
title: getAxiosErrorMessage(error),
variant: "danger",
});
};

const { data: report, refetch } = useFetchStaticReport(
id,
mimeType,
onFetchStaticReportError
);
const {
mutate: downloadFile,
isLoading,
isError,
} = useDownloadStaticReport();

const handleDownload = () => {
refetch().then(() => {
if (report) {
saveAs(new Blob([report]), `analysis-report-app-${id}.${mimeType}`);
}
downloadFile({
application: application,
mimeType: mimeType,
});
};

return (
<Button
key={mimeType}
onClick={handleDownload}
id={`download-${mimeType}-button`}
variant="link"
className={spacing.pXs}
>
{children}
</Button>
<>
{isLoading ? (
<Spinner size="sm" />
) : isError ? (
<Alert variant="warning" isInline title={"Error downloading report"}>
<p>{"An error has occurred. Try to download again."}</p>
</Alert>
) : (
<>
<Button
onClick={handleDownload}
id={`download-${mimeType}-button`}
variant="link"
className={spacing.pXs}
>
{mimeType === MimeType.YAML ? "YAML" : "Report"}
</Button>
</>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import * as React from "react";
import { saveAs } from "file-saver";
import { DropdownItem } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";

import { useFetchQuestionnaireBlob } from "@app/queries/questionnaires";
import { AxiosError } from "axios";
import { getAxiosErrorMessage } from "@app/utils/utils";
import { NotificationsContext } from "@app/components/NotificationsContext";
import { useDownloadQuestionnaire } from "@app/queries/questionnaires";

export interface IExportQuestionnaireDropdownItemProps {
id: number;
Expand All @@ -15,30 +10,18 @@ export const ExportQuestionnaireDropdownItem: React.FC<
IExportQuestionnaireDropdownItemProps
> = ({ id }) => {
const { t } = useTranslation();
const { pushNotification } = React.useContext(NotificationsContext);

const onExportQuestionnaireError = (error: AxiosError) => {
pushNotification({
title: getAxiosErrorMessage(error),
variant: "danger",
});
};

const { data: questionnaire, refetch } = useFetchQuestionnaireBlob(
id,
onExportQuestionnaireError
);
const {
mutate: downloadFile,
isLoading,
isError,
} = useDownloadQuestionnaire();

const exportQuestionnaire = () => {
refetch().then(() => {
if (questionnaire) {
saveAs(new Blob([questionnaire]), `questionnaire-${id}.yaml`);
}
});
const handleDownload = () => {
downloadFile(id);
};

return (
<DropdownItem key="export" component="button" onClick={exportQuestionnaire}>
<DropdownItem key="export" component="button" onClick={handleDownload}>
{t("actions.export")}
</DropdownItem>
);
Expand Down
63 changes: 49 additions & 14 deletions client/src/app/queries/applications.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import axios, { AxiosError } from "axios";

import { Application, MimeType } from "@app/api/models";
import {
APPLICATIONS,
createApplication,
deleteApplication,
deleteBulkApplications,
getApplicationAnalysis,
getApplicationById,
getApplications,
updateAllApplications,
updateApplication,
} from "@app/api/rest";
import { reviewsQueryKey } from "./reviews";
import { assessmentsQueryKey } from "./assessments";
import saveAs from "file-saver";

export const ApplicationDependencyQueryKey = "applicationdependencies";
export const ApplicationsQueryKey = "applications";
export const ReportQueryKey = "report";

interface DownloadOptions {
application: Application;
mimeType: MimeType;
}

export const useFetchApplications = () => {
const queryClient = useQueryClient();
const { isLoading, error, refetch, data } = useQuery({
Expand Down Expand Up @@ -134,15 +140,44 @@
);
};

// The report download is triggerred on demand by a refetch()
export const useFetchStaticReport = (
id: number,
type: MimeType,
onError: (err: AxiosError) => void
) =>
useQuery({
queryKey: [ReportQueryKey, id],
queryFn: () => getApplicationAnalysis(id, type),
onError: onError,
enabled: false,
});
export const downloadStaticReport = async ({
application,
mimeType,
}: DownloadOptions): Promise<void> => {
const yamlAcceptHeader = "application/x-yaml";
let url = `${APPLICATIONS}/${application.id}/analysis/report`;

Check warning on line 148 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L146-L148

Added lines #L146 - L148 were not covered by tests

switch (mimeType) {
case MimeType.YAML:
url = `${APPLICATIONS}/${application.id}/analysis`;
break;
case MimeType.TAR:
default:
url = `${APPLICATIONS}/${application.id}/analysis/report`;

Check warning on line 156 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L151-L156

Added lines #L151 - L156 were not covered by tests
}

try {
const response = await axios.get(url, {

Check warning on line 160 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L159-L160

Added lines #L159 - L160 were not covered by tests
responseType: "blob",
...(MimeType.YAML && {
headers: {
Accept: yamlAcceptHeader,
},
}),
});

if (response.status !== 200) {
throw new Error("Network response was not ok when downloading file.");

Check warning on line 170 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L170

Added line #L170 was not covered by tests
}

const blob = new Blob([response.data]);
saveAs(blob, `analysis-report-app-${application.name}.${mimeType}`);

Check warning on line 174 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L173-L174

Added lines #L173 - L174 were not covered by tests
} catch (error) {
console.error("There was an error downloading the file:", error);
throw error;

Check warning on line 177 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L176-L177

Added lines #L176 - L177 were not covered by tests
}
};

export const useDownloadStaticReport = () => {
return useMutation(downloadStaticReport);

Check warning on line 182 in client/src/app/queries/applications.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/applications.ts#L182

Added line #L182 was not covered by tests
};
34 changes: 32 additions & 2 deletions client/src/app/queries/questionnaires.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import axios, { AxiosError } from "axios";

import {
QUESTIONNAIRES,
createQuestionnaire,
deleteQuestionnaire,
getQuestionnaireById,
getQuestionnaires,
updateQuestionnaire,
} from "@app/api/rest";
import { Questionnaire } from "@app/api/models";
import saveAs from "file-saver";

export const QuestionnairesQueryKey = "questionnaires";
export const QuestionnaireByIdQueryKey = "questionnaireById";
Expand Down Expand Up @@ -84,7 +86,7 @@
) =>
useQuery({
queryKey: [QuestionnaireByIdQueryKey, id],
queryFn: () => getQuestionnaireById<Blob>(id, true),
queryFn: () => getQuestionnaireById<Blob>(id),

Check warning on line 89 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L89

Added line #L89 was not covered by tests
onError: onError,
enabled: false,
});
Expand Down Expand Up @@ -113,3 +115,31 @@
error,
};
};

export const downloadQuestionnaire = async (
id: number | string
): Promise<void> => {
const url = `${QUESTIONNAIRES}/${id}`;

Check warning on line 122 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L121-L122

Added lines #L121 - L122 were not covered by tests

try {
const response = await axios.get(url, {

Check warning on line 125 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L124-L125

Added lines #L124 - L125 were not covered by tests
responseType: "blob",
headers: {
Accept: "application/x-yaml",
},
});

if (response.status !== 200) {
throw new Error("Network response was not ok when downloading file.");

Check warning on line 133 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L133

Added line #L133 was not covered by tests
}

const blob = new Blob([response.data]);
saveAs(blob, `questionnaire-${id}.yaml`);

Check warning on line 137 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L136-L137

Added lines #L136 - L137 were not covered by tests
} catch (error) {
console.error("There was an error downloading the file:", error);
throw error;

Check warning on line 140 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L139-L140

Added lines #L139 - L140 were not covered by tests
}
};
export const useDownloadQuestionnaire = () => {
return useMutation(downloadQuestionnaire);

Check warning on line 144 in client/src/app/queries/questionnaires.ts

View check run for this annotation

Codecov / codecov/patch

client/src/app/queries/questionnaires.ts#L144

Added line #L144 was not covered by tests
};
Loading