diff --git a/client/src/app/api/rest.ts b/client/src/app/api/rest.ts index 203f5f2ed5..0502982548 100644 --- a/client/src/app/api/rest.ts +++ b/client/src/app/api/rest.ts @@ -721,23 +721,8 @@ export const updateProxy = (obj: Proxy): Promise => export const getQuestionnaires = (): Promise => axios.get(QUESTIONNAIRES).then((response) => response.data); -export const getQuestionnaireById = ( - id: number | string, - isBlob: boolean = false -): Promise => - axios - .get( - `${QUESTIONNAIRES}/${id}`, - isBlob - ? { - responseType: "blob", - headers: { - Accept: "application/x-yaml", - }, - } - : {} - ) - .then((response) => response.data); +export const getQuestionnaireById = (id: number | string): Promise => + axios.get(`${QUESTIONNAIRES}/${id}`).then((response) => response.data); export const createQuestionnaire = ( obj: Questionnaire diff --git a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-analysis.tsx b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-analysis.tsx index ba46c5629a..c47a1bb4ed 100644 --- a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-analysis.tsx +++ b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-analysis.tsx @@ -139,11 +139,9 @@ export const ApplicationDetailDrawerAnalysis: React.FC< position="top" > - TAR - + /> {" | "} - YAML - + /> diff --git a/client/src/app/pages/applications/components/application-detail-drawer/components/download-button.tsx b/client/src/app/pages/applications/components/application-detail-drawer/components/download-button.tsx index 5d0c2dad4a..b27c726879 100644 --- a/client/src/app/pages/applications/components/application-detail-drawer/components/download-button.tsx +++ b/client/src/app/pages/applications/components/application-detail-drawer/components/download-button.tsx @@ -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 = ({ - 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 ( - + <> + {isLoading ? ( + + ) : isError ? ( + +

{"An error has occurred. Try to download again."}

+
+ ) : ( + <> + + + )} + ); }; diff --git a/client/src/app/pages/assessment-management/assessment-settings/components/export-questionnaire-dropdown-item.tsx b/client/src/app/pages/assessment-management/assessment-settings/components/export-questionnaire-dropdown-item.tsx index 5e438a5b40..126df5b7a5 100644 --- a/client/src/app/pages/assessment-management/assessment-settings/components/export-questionnaire-dropdown-item.tsx +++ b/client/src/app/pages/assessment-management/assessment-settings/components/export-questionnaire-dropdown-item.tsx @@ -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; @@ -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 ( - + {t("actions.export")} ); diff --git a/client/src/app/queries/applications.ts b/client/src/app/queries/applications.ts index 197ab755c0..80a58b4673 100644 --- a/client/src/app/queries/applications.ts +++ b/client/src/app/queries/applications.ts @@ -1,12 +1,12 @@ 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, @@ -14,11 +14,17 @@ import { } 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({ @@ -134,15 +140,44 @@ export const useBulkDeleteApplicationMutation = ( ); }; -// 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 => { + const yamlAcceptHeader = "application/x-yaml"; + let url = `${APPLICATIONS}/${application.id}/analysis/report`; + + switch (mimeType) { + case MimeType.YAML: + url = `${APPLICATIONS}/${application.id}/analysis`; + break; + case MimeType.TAR: + default: + url = `${APPLICATIONS}/${application.id}/analysis/report`; + } + + try { + const response = await axios.get(url, { + responseType: "blob", + ...(MimeType.YAML && { + headers: { + Accept: yamlAcceptHeader, + }, + }), + }); + + if (response.status !== 200) { + throw new Error("Network response was not ok when downloading file."); + } + + const blob = new Blob([response.data]); + saveAs(blob, `analysis-report-app-${application.name}.${mimeType}`); + } catch (error) { + console.error("There was an error downloading the file:", error); + throw error; + } +}; + +export const useDownloadStaticReport = () => { + return useMutation(downloadStaticReport); +}; diff --git a/client/src/app/queries/questionnaires.ts b/client/src/app/queries/questionnaires.ts index 6a010b01f6..691d8cc9d4 100644 --- a/client/src/app/queries/questionnaires.ts +++ b/client/src/app/queries/questionnaires.ts @@ -1,7 +1,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { AxiosError } from "axios"; +import axios, { AxiosError } from "axios"; import { + QUESTIONNAIRES, createQuestionnaire, deleteQuestionnaire, getQuestionnaireById, @@ -9,6 +10,7 @@ import { updateQuestionnaire, } from "@app/api/rest"; import { Questionnaire } from "@app/api/models"; +import saveAs from "file-saver"; export const QuestionnairesQueryKey = "questionnaires"; export const QuestionnaireByIdQueryKey = "questionnaireById"; @@ -84,7 +86,7 @@ export const useFetchQuestionnaireBlob = ( ) => useQuery({ queryKey: [QuestionnaireByIdQueryKey, id], - queryFn: () => getQuestionnaireById(id, true), + queryFn: () => getQuestionnaireById(id), onError: onError, enabled: false, }); @@ -113,3 +115,31 @@ export const useCreateQuestionnaireMutation = ( error, }; }; + +export const downloadQuestionnaire = async ( + id: number | string +): Promise => { + const url = `${QUESTIONNAIRES}/${id}`; + + try { + const response = await axios.get(url, { + responseType: "blob", + headers: { + Accept: "application/x-yaml", + }, + }); + + if (response.status !== 200) { + throw new Error("Network response was not ok when downloading file."); + } + + const blob = new Blob([response.data]); + saveAs(blob, `questionnaire-${id}.yaml`); + } catch (error) { + console.error("There was an error downloading the file:", error); + throw error; + } +}; +export const useDownloadQuestionnaire = () => { + return useMutation(downloadQuestionnaire); +};