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: dépôt vecteur - échec à la vérification standard et vecteur #579 #606

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions assets/@types/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ export type StoredDataReport = {
processing_executions: StoredDataReportProcessingExecution[];
};

export type DeliveryReport = {
input_upload: Upload & {
file_tree: UploadTree;
checks: CheckDetailed[];
};
};

export type StoredDataReportProcessingExecution = ProcessingExecution & {
output: ProcessingExecutionOutputStoredDataDto;
logs?: CheckOrProcessingExecutionLogs;
Expand Down
9 changes: 9 additions & 0 deletions assets/entrepot/api/upload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SymfonyRouting from "../../modules/Routing";
import { jsonFetch } from "../../modules/jsonFetch";
import { DeliveryReport } from "../../@types/app";
import { Upload, UploadTree, UploadTypeEnum } from "../../@types/app";

const getList = (datastoreId: string, type?: UploadTypeEnum, otherOptions: RequestInit = {}) => {
Expand Down Expand Up @@ -61,6 +62,13 @@ const remove = (datastoreId: string, uploadId: string) => {
return jsonFetch<null>(url, { method: "DELETE" });
};

const getDeliveryReport = (datastoreId: string, uploadId: string, otherOptions: RequestInit = {}) => {
const url = SymfonyRouting.generate("cartesgouvfr_api_upload_get_delivery_report", { datastoreId, uploadId });
return jsonFetch<DeliveryReport>(url, {
...otherOptions,
});
};

const upload = {
getList,
add,
Expand All @@ -69,6 +77,7 @@ const upload = {
pingIntegrationProgress,
getFileTree,
remove,
getDeliveryReport,
};

export default upload;
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,24 @@ const DatasheetUploadIntegrationDialog: FC<DatasheetUploadIntegrationDialogProps
</div>
)}

{integrationStatus === "at_least_one_failure" && uploadQuery.data?.tags.datasheet_name !== undefined && (
<div className={fr.cx("fr-grid-row")}>
<ButtonsGroup
buttons={[
{
children: "Consulter la fiche de données",
linkProps: routes.datastore_datasheet_view({
datastoreId,
datasheetName: uploadQuery.data?.tags.datasheet_name,
activeTab: DatasheetViewActiveTabEnum.Dataset,
}).link,
},
]}
inlineLayoutWhen="always"
/>
</div>
)}

{integrationStatus === "at_least_one_failure" && uploadQuery.data?.tags?.vectordb_id !== undefined && (
<div className={fr.cx("fr-grid-row")}>
<ButtonsGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ const DatasetListTab: FC<DataListTabProps> = ({ datastoreId, datasheet }) => {
{unfinishedUploads && unfinishedUploads.length > 0 && (
<div className={fr.cx("fr-grid-row", "fr-grid-row--center", "fr-grid-row--middle")}>
<div className={fr.cx("fr-col")}>
<UnfinishedUploadList datastoreId={datastoreId} uploadList={unfinishedUploads} />
<UnfinishedUploadList
datastoreId={datastoreId}
uploadList={unfinishedUploads}
nbPublications={datasheet.nb_publications}
datasheet={datasheet}
/>
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,47 @@ import { fr } from "@codegouvfr/react-dsfr";
import Button from "@codegouvfr/react-dsfr/Button";
import { FC, memo } from "react";
import { symToStr } from "tsafe/symToStr";
import { useMutation, useQueryClient } from "@tanstack/react-query";

import RQKeys from "../../../../../modules/entrepot/RQKeys";
import api from "../../../../api";
import { type DatasheetDetailed } from "../../../../../@types/app";
import { routes } from "../../../../../router/router";
import { Upload } from "../../../../../@types/app";
import ReportStatusBadge from "../../../stored_data/StoredDataDetails/ReportTab/ReportStatusBadge";
import { deleteDeliveryConfirmModal } from "../DatasheetView";
import Wait from "../../../../../components/Utils/Wait";
import LoadingIcon from "../../../../../components/Utils/LoadingIcon";
import { useTranslation } from "../../../../../i18n/i18n";

type UnfinishedUploadListProps = {
datastoreId: string;
uploadList?: Upload[];
nbPublications: number;
datasheet: DatasheetDetailed;
};
const UnfinishedUploadList: FC<UnfinishedUploadListProps> = ({ datastoreId, uploadList }) => {

const UnfinishedUploadList: FC<UnfinishedUploadListProps> = ({ datastoreId, uploadList, nbPublications, datasheet }) => {
const { t } = useTranslation("DatastoreManageStorage");

const queryClient = useQueryClient();

const isLastUpload = (uploadList: Upload[]): boolean => {
return uploadList.length === 1 && nbPublications === 0;
};

const deleteUnfinishedUpload = useMutation({
mutationFn: (uploadId: string) => api.upload.remove(datastoreId, uploadId),
onSuccess(uploadId) {
queryClient.setQueryData(RQKeys.datastore_datasheet(datastoreId, datasheet.name), (datasheet: { upload_list: Upload[] }) => {
return {
...datasheet,
upload_list: datasheet.upload_list.filter((upload) => upload._id !== uploadId),
};
});
},
});

return (
<>
<div className={fr.cx("fr-grid-row")}>
Expand All @@ -20,21 +52,92 @@ const UnfinishedUploadList: FC<UnfinishedUploadListProps> = ({ datastoreId, uplo
</h5>
</div>

{uploadList?.map((upload) => (
<div key={upload._id} className={fr.cx("fr-grid-row", "fr-grid-row--middle", "fr-mt-2v")}>
<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>{upload.name}</div>
</div>
{uploadList?.map((upload) => {
const integrationProgress = JSON.parse(upload.tags.integration_progress || "{}");
const steps = Object.entries(integrationProgress);
const failureCase = steps.some(([, status]) => status === "failed");

return (
<div key={upload._id} className={fr.cx("fr-grid-row", "fr-grid-row--middle", "fr-mt-2v")}>
<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>
{upload.name}
{failureCase ? (
<ReportStatusBadge status="FAILURE" className={fr.cx("fr-ml-2w")} />
) : (
<ReportStatusBadge status="WAITING" className={fr.cx("fr-ml-2w")} />
)}
</div>
</div>

<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--right", "fr-grid-row--middle")}>
<Button linkProps={routes.datastore_datasheet_upload_integration({ datastoreId, uploadId: upload._id }).link}>
{"Reprendre l’intégration"}
</Button>
<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--right", "fr-grid-row--middle")}>
{failureCase ? (
<>
<Button
className={fr.cx("fr-mr-2w")}
linkProps={
routes.datastore_delivery_details({
datastoreId,
uploadDataId: upload._id,
datasheetName: upload.tags.datasheet_name,
}).link
}
>
{"Voir le rapport"}
</Button>
<Button
iconId="fr-icon-delete-fill"
priority="secondary"
onClick={() => {
if (isLastUpload(uploadList)) {
deleteDeliveryConfirmModal.open();
} else {
deleteUnfinishedUpload.mutate(upload._id);
}
}}
>
{"Supprimer"}
</Button>
</>
) : (
<>
<Button
className={fr.cx("fr-mr-2w")}
linkProps={routes.datastore_datasheet_upload_integration({ datastoreId, uploadId: upload._id }).link}
>
{"Reprendre l'intégration"}
</Button>
<Button
iconId="fr-icon-delete-fill"
priority="secondary"
onClick={() => {
if (isLastUpload(uploadList)) {
deleteDeliveryConfirmModal.open();
} else {
deleteUnfinishedUpload.mutate(upload._id);
}
}}
>
{"Supprimer"}
</Button>
</>
)}
</div>
</div>
</div>
);
})}
{deleteUnfinishedUpload.isPending && (
<Wait>
<div className={fr.cx("fr-container")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>
<LoadingIcon className={fr.cx("fr-mr-2v")} />
<h6 className={fr.cx("fr-m-0")}>{t("storage.upload.deletion.in_progress")}</h6>
</div>
</div>
</div>
))}
</Wait>
)}
</>
);
};
Expand Down
25 changes: 25 additions & 0 deletions assets/entrepot/pages/datasheet/DatasheetView/DatasheetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const deleteDataConfirmModal = createModal({
isOpenedByDefault: false,
});

export const deleteDeliveryConfirmModal = createModal({
id: "delete-delivery-confirm-modal",
isOpenedByDefault: false,
});

export enum DatasheetViewActiveTabEnum {
Metadata = "metadata",
Dataset = "dataset",
Expand Down Expand Up @@ -260,6 +265,26 @@ const DatasheetView: FC<DatasheetViewProps> = ({ datastoreId, datasheetName }) =
</deleteDataConfirmModal.Component>,
document.body
)}
{createPortal(
<deleteDeliveryConfirmModal.Component
title={`Voulez-vous supprimer la fiche de données ${datasheetName} ?`}
buttons={[
{
children: tCommon("no"),
doClosesModal: true,
priority: "secondary",
},
{
children: tCommon("yes"),
onClick: () => datasheetDeleteMutation.mutate(),
priority: "primary",
},
]}
>
<strong>En supprimant cette livraison, la fiche de données {datasheetName} sera supprimée.</strong>
</deleteDeliveryConfirmModal.Component>,
document.body
)}
</>
</DatastoreLayout>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { fr } from "@codegouvfr/react-dsfr";
import Alert from "@codegouvfr/react-dsfr/Alert";
import Button from "@codegouvfr/react-dsfr/Button";
import Tabs from "@codegouvfr/react-dsfr/Tabs";
import { useQuery } from "@tanstack/react-query";
import { FC, useMemo } from "react";

import { DeliveryReport } from "../../../../@types/app";
import DatastoreLayout from "../../../../components/Layout/DatastoreLayout";
import LoadingIcon from "../../../../components/Utils/LoadingIcon";
import RQKeys from "../../../../modules/entrepot/RQKeys";
import { CartesApiException } from "../../../../modules/jsonFetch";
import { routes } from "../../../../router/router";
import api from "../../../api";
import DeliveryPreviewTab from "./PreviewTab/DeliveryPreviewTab";
import ReportTab from "./ReportTab/ReportTab";

type DeliveryDetailsProps = {
datastoreId: string;
uploadDataId: string;
};

const DeliveryDetails: FC<DeliveryDetailsProps> = ({ datastoreId, uploadDataId }) => {
const datastoreQuery = useQuery({
queryKey: RQKeys.datastore(datastoreId),
queryFn: ({ signal }) => api.datastore.get(datastoreId, { signal }),
staleTime: 3600000,
});

const reportQuery = useQuery<DeliveryReport, CartesApiException>({
queryKey: RQKeys.datastore_delivery_report(datastoreId, uploadDataId),
queryFn: ({ signal }) => api.upload.getDeliveryReport(datastoreId, uploadDataId, { signal }),
staleTime: 3600000,
});

const datasheetName = useMemo(() => reportQuery?.data?.input_upload?.tags?.datasheet_name, [reportQuery?.data?.input_upload?.tags?.datasheet_name]);

return (
<DatastoreLayout datastoreId={datastoreId} documentTitle={`Rapport de livraison ${reportQuery?.data?.input_upload?.name ?? ""}`}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>
{datasheetName ? (
<Button
iconId="fr-icon-arrow-left-s-line"
priority="tertiary no outline"
linkProps={routes.datastore_datasheet_view({ datastoreId, datasheetName, activeTab: "dataset" }).link}
title="Retour à la fiche de donnée"
size="large"
/>
) : (
<Button
iconId="fr-icon-arrow-left-s-line"
priority="tertiary no outline"
linkProps={routes.datasheet_list({ datastoreId }).link}
title="Retour à mes données"
size="large"
/>
)}
<h1 className={fr.cx("fr-m-0")}>
{"Rapport de livraison"}
{reportQuery.isLoading && <LoadingIcon className={fr.cx("fr-ml-2v")} largeIcon={true} />}
</h1>
</div>
{reportQuery?.data?.input_upload?.name && (
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle", "fr-mb-4w")}>
<h2>{reportQuery?.data?.input_upload?.name}</h2>
</div>
)}

<div className={fr.cx("fr-grid-row", "fr-grid-row--middle", "fr-mb-4w")}>
{reportQuery.isError && <Alert severity="error" closable title={reportQuery.error.message} onClose={reportQuery.refetch} />}
</div>

{reportQuery.data && (
<div className={fr.cx("fr-grid-row")}>
<div className={fr.cx("fr-col")}>
<Tabs
tabs={[
{
label: "Aperçu de la donnée",
content: <DeliveryPreviewTab reportData={reportQuery.data} />,
},
{
label: "Rapport de génération",
content: <ReportTab datastoreName={datastoreQuery.data?.name} reportQuery={reportQuery} />,
},
]}
/>
</div>
</div>
)}
</DatastoreLayout>
);
};

export default DeliveryDetails;
Loading
Loading