diff --git a/.env b/.env index f93476da..4ae7e9bf 100644 --- a/.env +++ b/.env @@ -34,9 +34,12 @@ HTTPS_PROXY= # id du processus d'intégration en base API_ENTREPOT_PROC_INT_VECT_FILES_DB= -# id du processus de génération de pyramide +# id du processus de génération de pyramide vecteur API_ENTREPOT_PROC_CREATE_VECT_PYR= +# id du processus de génération de pyramide raster +API_ENTREPOT_PROC_CREATE_RAST_PYR= + # Id de la community liee au datastore bac a sable SANDBOX_COMMUNITY_ID= @@ -47,8 +50,11 @@ SANDBOX_SERVICE_ACCOUNT_CLIENT_SECRET= # id du processus d'intégration en base pour le Bac a sable SANDBOX_PROC_INT_VECT_FILES_DB= -# id du processus de génération de pyramide pour le Bac a sable +# id du processus de génération de pyramide vecteur pour le Bac a sable SANDBOX_PROC_CREATE_VECT_PYR= +# id du processus de génération de pyramide raster pour le Bac à sable +SANDBOX_PROC_CREATE_RAST_PYR= + # URL de l'API espace collaboratif API_ESPACE_COLLABORATIF_URL= diff --git a/assets/@types/app.ts b/assets/@types/app.ts index 38f8b1b9..7a51add2 100644 --- a/assets/@types/app.ts +++ b/assets/@types/app.ts @@ -83,7 +83,8 @@ export enum DatasheetDocumentTypeEnum { export type DatasheetDetailed = Datasheet & { vector_db_list: VectorDb[] | undefined; - pyramid_list: Pyramid[] | undefined; + pyramid_vector_list: PyramidVector[] | undefined; + pyramid_raster_list: PyramidRaster[] | undefined; upload_list: Upload[] | undefined; service_list: Service[] | undefined; }; @@ -108,7 +109,7 @@ export type VectorDb = StoredData & { }; /** stored_data (donnée stockée) du type ROK4-PYRAMID-VECTOR */ -export type Pyramid = StoredData & { +export type PyramidVector = StoredData & { type: StoredDataPrivateDetailResponseDtoTypeEnum.ROK4PYRAMIDVECTOR; tags: { datasheet_name?: string; @@ -120,6 +121,18 @@ export type Pyramid = StoredData & { }; }; +/** stored_data (donnée stockée) du type ROK4-PYRAMID-VECTOR */ +export type PyramidRaster = StoredData & { + type: StoredDataPrivateDetailResponseDtoTypeEnum.ROK4PYRAMIDRASTER; + tags: { + datasheet_name?: string; + upload_id?: string; + proc_int_id?: string; + vectordb_id?: string; + proc_pyr_creat_id?: string; + }; +}; + /** upload (livraison) */ export type Upload = UploadPrivateDetailResponseDto & { tags: { @@ -180,7 +193,13 @@ export type TmsMetadata = { }; /** configuration & offerings */ -export type Configuration = ConfigurationDetailResponseDto & { styles?: CartesStyle[] }; +export type Configuration = ConfigurationDetailResponseDto & { + styles?: CartesStyle[]; + tags: { + datasheet_name?: string; + }; + pyramid?: PyramidVector | PyramidRaster; +}; export type Offering = OfferingDetailResponseDto; export { OfferingStatusEnum } from "./entrepot"; diff --git a/assets/components/Layout/DatastoreLayout.tsx b/assets/components/Layout/DatastoreLayout.tsx index ce7e7b33..89014b74 100644 --- a/assets/components/Layout/DatastoreLayout.tsx +++ b/assets/components/Layout/DatastoreLayout.tsx @@ -2,7 +2,7 @@ import { BreadcrumbProps } from "@codegouvfr/react-dsfr/Breadcrumb"; import { useQuery } from "@tanstack/react-query"; import { FC, PropsWithChildren, memo, useMemo } from "react"; -import { Datastore } from "../../@types/app"; +import type { Datastore } from "../../@types/app"; import { datastoreNavItems } from "../../config/datastoreNavItems"; import api from "../../entrepot/api"; import RQKeys from "../../modules/entrepot/RQKeys"; diff --git a/assets/components/Utils/MenuList.tsx b/assets/components/Utils/MenuList.tsx index 5ff943ed..dd788a25 100644 --- a/assets/components/Utils/MenuList.tsx +++ b/assets/components/Utils/MenuList.tsx @@ -29,7 +29,7 @@ type MenuListItem = MenuListItemCommon & type MenuListProps = { menuOpenButtonProps?: Omit; - items: MenuListItem[]; + items: (MenuListItem | undefined | false)[]; disabled?: boolean; }; @@ -48,6 +48,8 @@ const MenuList: FC = ({ menuOpenButtonProps, items = [], disabled setAnchorEl(null); }; + const _items: MenuListItem[] = useMemo(() => items.filter((item) => item !== undefined && item !== false), [items]); + // props du bouton ouvrir menu const _menuOpenBtnProps = useMemo(() => { const _props: ButtonProps = menuOpenButtonProps ? { ...(menuOpenButtonProps as ButtonProps) } : ({} as ButtonProps); @@ -66,7 +68,7 @@ const MenuList: FC = ({ menuOpenButtonProps, items = [], disabled return _props; }, [menuOpenButtonProps, disabled, isMenuOpen, otherActionsBtnId, otherActionsMenuId]); - const atLeastOneIcon = useMemo(() => items.filter((item) => item.iconId !== undefined).length > 0, [items]); + const atLeastOneIcon = useMemo(() => _items.filter((item) => item.iconId !== undefined).length > 0, [_items]); return ( <> @@ -91,7 +93,7 @@ const MenuList: FC = ({ menuOpenButtonProps, items = [], disabled }} > {disabled === false && - items.map((item, i) => { + _items.map((item, i) => { const itemContent = ( = (props) => { value: v, label: v.toString(), }))} - getAriaLabel={() => "Minimum distance"} value={values} onChange={(_, newValue) => { if (!Array.isArray(newValue)) { diff --git a/assets/components/Utils/TextWithLink.tsx b/assets/components/Utils/TextWithLink.tsx deleted file mode 100644 index e10b54dd..00000000 --- a/assets/components/Utils/TextWithLink.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { FC } from "react"; - -type TextWithLinkProps = { - text: string; - linkText: string; - linkHref: string; -}; -const TextWithLink: FC = ({ text, linkText, linkHref }) => { - return ( - <> - {text}  - - {linkText} - - - ); -}; - -export default TextWithLink; diff --git a/assets/components/Utils/ZoomRange.tsx b/assets/components/Utils/ZoomRange.tsx index 1453c69e..b058a65d 100644 --- a/assets/components/Utils/ZoomRange.tsx +++ b/assets/components/Utils/ZoomRange.tsx @@ -1,4 +1,6 @@ import { fr } from "@codegouvfr/react-dsfr"; +import Range, { RangeProps } from "@codegouvfr/react-dsfr/Range"; +import { cx } from "@codegouvfr/react-dsfr/tools/cx"; import Map, { type MapOptions } from "ol/Map"; import View, { ViewOptions } from "ol/View"; import { ScaleLine } from "ol/control"; @@ -6,26 +8,31 @@ import BaseLayer from "ol/layer/Base"; import TileLayer from "ol/layer/Tile"; import { fromLonLat } from "ol/proj"; import WMTS, { optionsFromCapabilities } from "ol/source/WMTS"; -import { FC, memo, useCallback, useEffect, useRef } from "react"; +import { FC, memo, ReactNode, useCallback, useEffect, useRef } from "react"; +import { symToStr } from "tsafe/symToStr"; +import { tss } from "tss-react"; import olDefaults from "../../data/ol-defaults.json"; import useCapabilities from "../../hooks/useCapabilities"; -import RangeSlider from "./RangeSlider"; -import "../../sass/components/zoom-range.scss"; +import imgMapZoomBottom from "../../img/zoom-range/map-zoom-bottom.png"; +import imgMapZoomTop from "../../img/zoom-range/map-zoom-top.png"; type ZoomRangeProps = { - min: number; - max: number; + min: RangeProps["min"]; + max: RangeProps["min"]; values: number[]; onChange: (values: number[]) => void; + step?: RangeProps["step"]; center?: number[]; + mode?: "top" | "bottom" | "both"; + overlayContent?: ReactNode; }; const ZoomRange: FC = (props) => { const { data: capabilities } = useCapabilities(); - const { min, max, values, center = olDefaults.center, onChange } = props; + const { min, max, values, center = olDefaults.center, onChange, step, mode = "both", overlayContent } = props; // References sur les deux cartes const leftMapRef = useRef(); @@ -109,15 +116,140 @@ const ZoomRange: FC = (props) => { rightMapRef.current?.updateSize(); }, [values]); + const handleChange = useCallback( + (newValue: number, zoomType?: "top" | "bottom") => { + switch (zoomType) { + case "top": + onChange([newValue, values[1]]); + break; + case "bottom": + onChange([values[0], newValue]); + break; + default: + onChange([newValue]); + break; + } + }, + [onChange, values] + ); + + const { classes } = useStyles(); + return (
-
-
-
+
+ {(mode === "both" || mode === "top") && ( +
+
+
+ )} + + {mode !== "both" && ( +
+
+ Illustration d'une carte fixe +
+ {overlayContent &&
{overlayContent}
} +
+
+
+ )} + + {(mode === "both" || mode === "bottom") && ( +
+
+
+ )}
- onChange(newValues)} /> + { + switch (mode) { + case "top": + return { + nativeInputProps: { + value: values?.[0] ?? min, + onChange: (e) => { + handleChange(e.currentTarget.valueAsNumber); + }, + }, + }; + case "bottom": + return { + nativeInputProps: { + value: values?.[0] ?? max, + onChange: (e) => { + handleChange(e.currentTarget.valueAsNumber); + }, + }, + }; + case "both": + return { + double: true, + nativeInputProps: [ + { + value: values?.[0] ?? min, + onChange: (e) => { + handleChange(e.currentTarget.valueAsNumber, "top"); + }, + }, + { + value: values?.[1] ?? max, + onChange: (e) => { + handleChange(e.currentTarget.valueAsNumber, "bottom"); + }, + }, + ], + }; + } + })()} + />
); }; +ZoomRange.displayName = symToStr({ ZoomRange }); + export default memo(ZoomRange); + +const useStyles = tss.withName(ZoomRange.displayName).create(() => ({ + map: { + height: "300px", + width: "100%", + }, + falseMapRoot: { + position: "relative", + display: "inline-block", + width: "100%", + height: "300px", + }, + falseMapImg: { + display: "block", + width: "100%", + height: "300px", + }, + falseMapOverlay: { + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + display: "flex", + justifyContent: "center", + alignItems: "center", + backgroundColor: "rgba(0, 0, 0, 0.6)", + textAlign: "center", + }, + falseMapOverlayContent: { + backgroundColor: fr.colors.decisions.background.default.grey.default, + color: fr.colors.decisions.text.default.grey.default, + }, +})); diff --git a/assets/data/ol-defaults.json b/assets/data/ol-defaults.json index d8e61906..fa321a98 100644 --- a/assets/data/ol-defaults.json +++ b/assets/data/ol-defaults.json @@ -1,10 +1,13 @@ { "default_background_layer": "GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2", - "center": [2.35, 48.85], + "center": [ + 2.35, + 48.85 + ], "projection": "EPSG:3857", "zoom": 10, "zoom_levels": { - "TOP": 0, + "TOP": 1, "BOTTOM": 18 } } \ No newline at end of file diff --git a/assets/entrepot/api/index.ts b/assets/entrepot/api/index.ts index 03be498b..0dd0249b 100644 --- a/assets/entrepot/api/index.ts +++ b/assets/entrepot/api/index.ts @@ -9,7 +9,8 @@ import community from "./community"; import user from "./user"; import wfs from "./wfs"; import wmsVector from "./wms-vector"; -import pyramid from "./pyramid"; +import pyramidVector from "./pyramidVector"; +import pyramidRaster from "./pyramidRaster"; import service from "./service"; import epsg from "./epsg"; import annexe from "./annexe"; @@ -31,7 +32,8 @@ const api = { user, wfs, wmsVector, - pyramid, + pyramidVector, + pyramidRaster, service, annexe, style, diff --git a/assets/entrepot/api/pyramidRaster.ts b/assets/entrepot/api/pyramidRaster.ts new file mode 100644 index 00000000..43740011 --- /dev/null +++ b/assets/entrepot/api/pyramidRaster.ts @@ -0,0 +1,54 @@ +import type { PyramidRaster, Service } from "../../@types/app"; +import SymfonyRouting from "../../modules/Routing"; +import { jsonFetch } from "../../modules/jsonFetch"; + +const add = (datastoreId: string, formData: FormData | object) => { + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_raster_add", { datastoreId }); + return jsonFetch( + url, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }, + formData + ); +}; + +const publishWmsRasterWmts = (datastoreId: string, pyramidId: string, type: string, formData: FormData | object) => { + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_raster_wms_raster_wmts_add", { datastoreId, pyramidId, type }); + return jsonFetch( + url, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }, + formData + ); +}; + +const editWmsRasterWmts = (datastoreId: string, pyramidId: string, offeringId: string, type: string, formData: FormData | object) => { + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_raster_wms_raster_wmts_edit", { datastoreId, pyramidId, offeringId, type }); + return jsonFetch( + url, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }, + formData + ); +}; + +export default { + add, + publishWmsRasterWmts, + editWmsRasterWmts: editWmsRasterWmts, +}; diff --git a/assets/entrepot/api/pyramid.ts b/assets/entrepot/api/pyramidVector.ts similarity index 91% rename from assets/entrepot/api/pyramid.ts rename to assets/entrepot/api/pyramidVector.ts index 7e1f5654..072f34ed 100644 --- a/assets/entrepot/api/pyramid.ts +++ b/assets/entrepot/api/pyramidVector.ts @@ -3,7 +3,7 @@ import { jsonFetch } from "../../modules/jsonFetch"; import { Service } from "../../@types/app"; const add = (datastoreId: string, formData: FormData | object) => { - const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_add", { datastoreId }); + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_vector_add", { datastoreId }); return jsonFetch( url, { @@ -18,7 +18,7 @@ const add = (datastoreId: string, formData: FormData | object) => { }; const publish = (datastoreId: string, pyramidId: string, formData: FormData | object) => { - const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_tms_add", { datastoreId, pyramidId }); + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_vector_tms_add", { datastoreId, pyramidId }); return jsonFetch( url, { @@ -33,7 +33,7 @@ const publish = (datastoreId: string, pyramidId: string, formData: FormData | ob }; const publishEdit = (datastoreId: string, pyramidId: string, offeringId: string, formData: FormData | object) => { - const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_tms_edit", { datastoreId, pyramidId, offeringId }); + const url = SymfonyRouting.generate("cartesgouvfr_api_pyramid_vector_tms_edit", { datastoreId, pyramidId, offeringId }); return jsonFetch( url, { diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/DatasetListTab.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/DatasetListTab.tsx index 1e87ed88..e2da4876 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/DatasetListTab.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/DatasetListTab.tsx @@ -3,11 +3,12 @@ import Button from "@codegouvfr/react-dsfr/Button"; import { FC, memo, useMemo } from "react"; import { symToStr } from "tsafe/symToStr"; -import { routes } from "../../../../../router/router"; import { type DatasheetDetailed } from "../../../../../@types/app"; -import PyramidList from "./PyramidList/PyramidList"; +import { routes } from "../../../../../router/router"; +import PyramidVectorList from "./PyramidVectorList/PyramidVectorList"; import UnfinishedUploadList from "./UnfinishedUploadList"; import VectorDbList from "./VectorDbList/VectorDbList"; +import PyramidRasterList from "./PyramidRasterList/PyramidRasterList"; type DataListTabProps = { datastoreId: string; @@ -52,7 +53,12 @@ const DatasetListTab: FC = ({ datastoreId, datasheet }) => {
- + +
+
+
+
+
diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidList.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidList.tsx deleted file mode 100644 index 41d6fce8..00000000 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidList.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { fr } from "@codegouvfr/react-dsfr"; -import { FC, memo } from "react"; -import { symToStr } from "tsafe/symToStr"; - -import { Pyramid } from "../../../../../../@types/app"; -import PyramidListItem from "./PyramidListItem"; - -type PyramidListProps = { - datasheetName: string; - datastoreId: string; - pyramidList: Pyramid[] | undefined; -}; - -const PyramidList: FC = ({ datasheetName, datastoreId, pyramidList }) => { - return ( - <> -
-
- -  Pyramides de tuiles vectorielles ({pyramidList?.length}) -
-
- {pyramidList?.map((pyramid) => )} - - ); -}; - -PyramidList.displayName = symToStr({ PyramidList }); - -export default memo(PyramidList); diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterList.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterList.tsx new file mode 100644 index 00000000..8e4abac1 --- /dev/null +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterList.tsx @@ -0,0 +1,32 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import { FC, memo } from "react"; +import { symToStr } from "tsafe/symToStr"; + +import { PyramidRaster } from "../../../../../../@types/app"; +import PyramidRasterListItem from "./PyramidRasterListItem"; + +type PyramidRasterListProps = { + datasheetName: string; + datastoreId: string; + pyramidList: PyramidRaster[] | undefined; +}; + +const PyramidRasterList: FC = ({ datasheetName, datastoreId, pyramidList }) => { + return ( + <> +
+
+ +  Pyramides de tuiles raster ({pyramidList?.length}) +
+
+ {pyramidList?.map((pyramid) => ( + + ))} + + ); +}; + +PyramidRasterList.displayName = symToStr({ PyramidRasterList }); + +export default memo(PyramidRasterList); diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem.tsx new file mode 100644 index 00000000..0bf3aae3 --- /dev/null +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem.tsx @@ -0,0 +1,314 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import Badge from "@codegouvfr/react-dsfr/Badge"; +import Button from "@codegouvfr/react-dsfr/Button"; +import { createModal } from "@codegouvfr/react-dsfr/Modal"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { FC, memo, ReactNode, useMemo, useRef } from "react"; +import { createPortal } from "react-dom"; + +import { DatastoreEndpoint, EndpointTypeEnum, OfferingTypeEnum, PyramidRaster, StoredDataStatusEnum } from "../../../../../../@types/app"; +import StoredDataStatusBadge from "../../../../../../components/Utils/Badges/StoredDataStatusBadge"; +import LoadingIcon from "../../../../../../components/Utils/LoadingIcon"; +import LoadingText from "../../../../../../components/Utils/LoadingText"; +import MenuList from "../../../../../../components/Utils/MenuList"; +import Wait from "../../../../../../components/Utils/Wait"; +import useToggle from "../../../../../../hooks/useToggle"; +import { declareComponentKeys, Translations, useTranslation } from "../../../../../../i18n/i18n"; +import RQKeys from "../../../../../../modules/entrepot/RQKeys"; +import { routes } from "../../../../../../router/router"; +import { formatDateFromISO, offeringTypeDisplayName } from "../../../../../../utils"; +import api from "../../../../../api"; +import PyramidStoredDataDesc from "../PyramidStoredDataDesc"; +import { PyramidRasterServiceChoiceDialog, type PyramidRasterServiceChoiceDialogProps } from "./PyramidRasterServiceChoiceDialog"; + +const getHintText = (endpoints: DatastoreEndpoint[]): ReactNode => ( +
    + {endpoints.map((endpoint) => ( +
  • {`${endpoint.quota - endpoint.use} publications possibles sur ${endpoint.endpoint.name}`}
  • + ))} +
+); + +const isAvailable = (endpoints: DatastoreEndpoint | DatastoreEndpoint[]): boolean => { + if (!Array.isArray(endpoints)) { + endpoints = [endpoints]; + } + const availables = endpoints.reduce((accumulator, endpoint) => accumulator + (endpoint.quota - endpoint.use), 0); + return availables !== 0; +}; + +type PyramidRasterListItemProps = { + datasheetName: string; + pyramid: PyramidRaster; + datastoreId: string; +}; + +const PyramidRasterListItem: FC = ({ datasheetName, datastoreId, pyramid }) => { + const { t } = useTranslation({ PyramidRasterListItem }); + const { t: tCommon } = useTranslation("Common"); + + const [showDescription, toggleShowDescription] = useToggle(false); + + const dataUsesQuery = useQuery({ + queryKey: RQKeys.datastore_stored_data_uses(datastoreId, pyramid._id), + queryFn: ({ signal }) => api.storedData.getUses(datastoreId, pyramid._id, { signal }), + staleTime: 600000, + enabled: showDescription, + }); + + /* Suppression de la pyramide */ + const queryClient = useQueryClient(); + + const deletePyramidMutation = useMutation({ + mutationFn: () => api.storedData.remove(datastoreId, pyramid._id), + onSuccess() { + if (datasheetName) { + queryClient.invalidateQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) }); + } + }, + }); + + const confirmRemovePyramidModal = useMemo( + () => + createModal({ + id: `confirm-delete-pyramid-${pyramid._id}`, + isOpenedByDefault: false, + }), + [pyramid._id] + ); + + const endpointsQuery = useQuery({ + queryKey: RQKeys.datastore_endpoints(datastoreId), + queryFn: ({ signal }) => api.datastore.getEndpoints(datastoreId, {}, { signal }), + retry: false, + staleTime: 3600000, + }); + const { wmsRasterEndpoints, wmtsEndpoints } = useMemo(() => { + const wmsRasterEndpoints = Array.isArray(endpointsQuery?.data) + ? endpointsQuery?.data?.filter((endpoint) => endpoint.endpoint.type === EndpointTypeEnum.WMSRASTER) + : []; + + const wmtsEndpoints = Array.isArray(endpointsQuery?.data) + ? endpointsQuery?.data?.filter((endpoint) => endpoint.endpoint.type === EndpointTypeEnum.WMTSTMS) + : []; + + return { wmsRasterEndpoints, wmtsEndpoints }; + }, [endpointsQuery.data]); + + const serviceChoiceDialogActions = useRef({}); + + return ( + <> +
+
+
+
+
+
+ +
+
+

{pyramid?.last_event?.date && formatDateFromISO(pyramid?.last_event?.date)}

+ + + confirmRemovePyramidModal.open(), + }, + ]} + /> +
+
+
+ {showDescription && } +
+ + {deletePyramidMutation.error && ( + + )} + {deletePyramidMutation.isPending && ( + +
+
+ +
{tCommon("removing")}
+
+
+
+ )} + {createPortal( + deletePyramidMutation.mutate(), + priority: "primary", + }, + ]} + > + {dataUsesQuery.isFetching && } + + {dataUsesQuery.data?.offerings_list && dataUsesQuery.data?.offerings_list?.length > 0 && ( +
+

{t("following_services_deleted")}

+
+
    + {dataUsesQuery.data?.offerings_list.map((offering, i) => ( +
  • + {offering.layer_name} + {offeringTypeDisplayName(offering.type)} +
  • + ))} +
+
+
+ )} +
, + document.body + )} + + ); +}; + +export default memo(PyramidRasterListItem); + +// traductions +export const { i18n } = declareComponentKeys< + | "show_linked_datas" + | "other_actions" + | "show_details" + | "publish_pyramid_raster" + | "choose_service_type" + | "wms_raster_label" + | "wms_raster_hint_text" + | "wmts_label" + | "wmts_hint_text" + | { K: "confirm_delete_modal.title"; P: { pyramidName: string }; R: string } + | "following_services_deleted" + | { K: "error_deleting"; P: { pyramidName: string }; R: string } +>()({ + PyramidRasterListItem, +}); + +export const PyramidRasterListItemFrTranslations: Translations<"fr">["PyramidRasterListItem"] = { + show_linked_datas: "Voir les données liées", + other_actions: "Autres actions", + show_details: "Voir les détails", + publish_pyramid_raster: "Publier", + choose_service_type: "Choisir le service à configurer", + wms_raster_hint_text: + "Création puis publication d'images à partir d'une pyramide de tuiles raster. Ce service s'appuie sur le protocole WMS en version 1.3.0.", + wms_raster_label: "Service d'images (Web Map Service - WMS)", + wmts_hint_text: "Création puis publication d'images à partir d'une pyramide de tuiles raster. Ce service s'appuie sur le protocole WMTS en version 1.0.0.", + wmts_label: "Services d'images tuilées (Web Map Tile Service - WMTS)", + "confirm_delete_modal.title": ({ pyramidName }) => `Êtes-vous sûr de vouloir supprimer la pyramide ${pyramidName} ?`, + following_services_deleted: "Les services suivants seront aussi supprimés :", + error_deleting: ({ pyramidName }) => `La suppression de la pyramide ${pyramidName} a échoué`, +}; + +export const PyramidRasterListItemEnTranslations: Translations<"en">["PyramidRasterListItem"] = { + show_linked_datas: "Show linked datas", + other_actions: "Other actions", + show_details: "Show details", + publish_pyramid_raster: "Publish", + choose_service_type: "Define service to create", + wms_raster_hint_text: undefined, + wms_raster_label: undefined, + wmts_hint_text: undefined, + wmts_label: undefined, + "confirm_delete_modal.title": ({ pyramidName }) => `Are you sure you want to delete pyramid ${pyramidName} ?`, + following_services_deleted: "The following services will be deleted :", + error_deleting: ({ pyramidName }) => `Deleting ${pyramidName} pyramid failed`, +}; diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterServiceChoiceDialog.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterServiceChoiceDialog.tsx new file mode 100644 index 00000000..ae030103 --- /dev/null +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterServiceChoiceDialog.tsx @@ -0,0 +1,122 @@ +import { createModal } from "@codegouvfr/react-dsfr/Modal"; +import { useIsModalOpen } from "@codegouvfr/react-dsfr/Modal/useIsModalOpen"; +import RadioButtons from "@codegouvfr/react-dsfr/RadioButtons"; +import { FC, ReactNode, useEffect, useId, useMemo, useState } from "react"; +import { createPortal } from "react-dom"; + +import { useTranslation } from "../../../../../../i18n/i18n"; + +export type PyramidRasterServiceChoiceDialogProps = { + actions: { + open?: (params: PyramidRasterServiceChoiceDialogParams) => Promise; + }; +}; + +export type DialogOptionType = { + id: string; + text: ReactNode; + hintText?: ReactNode; + disabled?: boolean; +}; + +export type PyramidRasterServiceChoiceDialogParams = { + title: string; + desc?: ReactNode; + options: DialogOptionType[]; +}; + +export type PyramidRasterServiceChoiceDialogResponse = { + response?: string; +}; + +/** + * @see https://github.com/codegouvfr/react-dsfr/blob/main/test/integration/cra/src/MyDialog.tsx + */ +export const PyramidRasterServiceChoiceDialog: FC = ({ actions }) => { + const { t } = useTranslation("Common"); + + const id = useId(); + + const modal = useMemo( + () => + createModal({ + id: `service-type-choice-modal-pyramid-raster-${id}`, + isOpenedByDefault: false, + }), + [id] + ); + + const [openState, setOpenState] = useState< + | { + dialogParams: PyramidRasterServiceChoiceDialogParams; + resolve: (params: PyramidRasterServiceChoiceDialogResponse) => void; + } + | undefined + >(undefined); + + useEffect(() => { + actions.open = (dialogParams) => + new Promise((resolve) => { + setOpenState({ + dialogParams, + resolve, + }); + modal.open(); + }); + }, [actions, modal]); + + useIsModalOpen(modal, { + onConceal: async () => { + openState?.resolve({ response: undefined }); + + setOpenState(undefined); + }, + }); + + const [selectedOption, setSelectedOption] = useState(); + + return createPortal( + { + openState?.resolve({ response: undefined }); + + setOpenState(undefined); + }, + }, + { + children: t("continue"), + onClick: () => { + openState?.resolve({ + response: selectedOption, + }); + + setOpenState(undefined); + }, + disabled: selectedOption === undefined, + }, + ]} + concealingBackdrop={false} + > + {openState?.dialogParams.desc} + + ({ + label: opt.text, + hintText: opt.hintText, + nativeInputProps: { + checked: selectedOption === opt.id, + onChange: () => setSelectedOption(opt.id), + disabled: opt.disabled, + }, + })) ?? [] + } + /> + , + document.body + ); +}; diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidDesc.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidStoredDataDesc.tsx similarity index 83% rename from assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidDesc.tsx rename to assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidStoredDataDesc.tsx index ba77e7f4..60cc0187 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidDesc.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidStoredDataDesc.tsx @@ -3,16 +3,16 @@ import Badge from "@codegouvfr/react-dsfr/Badge"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; import { FC, memo, useMemo } from "react"; -import api from "../../../../../api"; -import LoadingText from "../../../../../../components/Utils/LoadingText"; -import RQKeys from "../../../../../../modules/entrepot/RQKeys"; -import { Pyramid, VectorDb } from "../../../../../../@types/app"; -import { OfferingListResponseDto, ProcessingExecutionStoredDataDto } from "../../../../../../@types/entrepot"; -import { offeringTypeDisplayName } from "../../../../../../utils"; +import type { PyramidRaster, PyramidVector, VectorDb } from "../../../../../@types/app"; +import type { OfferingListResponseDto, ProcessingExecutionStoredDataDto } from "../../../../../@types/entrepot"; +import LoadingText from "../../../../../components/Utils/LoadingText"; +import RQKeys from "../../../../../modules/entrepot/RQKeys"; +import { offeringTypeDisplayName } from "../../../../../utils"; +import api from "../../../../api"; -type PyramidDescProps = { +type PyramidStoredDataDescProps = { datastoreId: string; - pyramid: Pyramid; + pyramid: PyramidVector | PyramidRaster; dataUsesQuery: UseQueryResult< { stored_data_list: ProcessingExecutionStoredDataDto[]; @@ -22,7 +22,7 @@ type PyramidDescProps = { >; }; -const PyramidDesc: FC = ({ datastoreId, pyramid, dataUsesQuery }) => { +const PyramidStoredDataDesc: FC = ({ datastoreId, pyramid, dataUsesQuery }) => { const vectorDbUsedId = useMemo(() => pyramid.tags.vectordb_id, [pyramid.tags.vectordb_id]); const vectorDbUsedQuery = useQuery({ @@ -82,4 +82,4 @@ const PyramidDesc: FC = ({ datastoreId, pyramid, dataUsesQuery ); }; -export default memo(PyramidDesc); +export default memo(PyramidStoredDataDesc); diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorList.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorList.tsx new file mode 100644 index 00000000..e53c737f --- /dev/null +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorList.tsx @@ -0,0 +1,32 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import { FC, memo } from "react"; +import { symToStr } from "tsafe/symToStr"; + +import { PyramidVector } from "../../../../../../@types/app"; +import PyramidVectorListItem from "./PyramidVectorListItem"; + +type PyramidVectorListProps = { + datasheetName: string; + datastoreId: string; + pyramidList: PyramidVector[] | undefined; +}; + +const PyramidVectorList: FC = ({ datasheetName, datastoreId, pyramidList }) => { + return ( + <> +
+
+ +  Pyramides de tuiles vectorielles ({pyramidList?.length}) +
+
+ {pyramidList?.map((pyramid) => ( + + ))} + + ); +}; + +PyramidVectorList.displayName = symToStr({ PyramidVectorList }); + +export default memo(PyramidVectorList); diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem.tsx similarity index 90% rename from assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem.tsx rename to assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem.tsx index f2e05983..5d049dad 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem.tsx @@ -7,6 +7,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { FC, memo, useMemo } from "react"; import { createPortal } from "react-dom"; +import { PyramidVector, StoredDataStatusEnum } from "../../../../../../@types/app"; import StoredDataStatusBadge from "../../../../../../components/Utils/Badges/StoredDataStatusBadge"; import LoadingIcon from "../../../../../../components/Utils/LoadingIcon"; import LoadingText from "../../../../../../components/Utils/LoadingText"; @@ -16,21 +17,20 @@ import useToggle from "../../../../../../hooks/useToggle"; import { Translations, declareComponentKeys, getTranslation, useTranslation } from "../../../../../../i18n/i18n"; import RQKeys from "../../../../../../modules/entrepot/RQKeys"; import { routes } from "../../../../../../router/router"; -import { Pyramid, StoredDataStatusEnum } from "../../../../../../@types/app"; import { formatDateFromISO, offeringTypeDisplayName } from "../../../../../../utils"; import api from "../../../../../api"; -import PyramidDesc from "./PyramidDesc"; +import PyramidStoredDataDesc from "../PyramidStoredDataDesc"; -type PyramidListItemProps = { +type PyramidVectorListItemProps = { datasheetName: string; - pyramid: Pyramid; + pyramid: PyramidVector; datastoreId: string; }; const { t: tCommon } = getTranslation("Common"); -const PyramidListItem: FC = ({ datasheetName, datastoreId, pyramid }) => { - const { t } = useTranslation({ PyramidListItem }); +const PyramidVectorListItem: FC = ({ datasheetName, datastoreId, pyramid }) => { + const { t } = useTranslation({ PyramidVectorListItem }); const [showDescription, toggleShowDescription] = useToggle(false); @@ -77,6 +77,11 @@ const PyramidListItem: FC = ({ datasheetName, datastoreId, onClick={toggleShowDescription} /> {pyramid.name} + {pyramid.tags?.is_sample === "true" && ( + + Echantillon + + )}
@@ -117,7 +122,7 @@ const PyramidListItem: FC = ({ datasheetName, datastoreId,
- {showDescription && } + {showDescription && } {deletePyramidMutation.error && ( = ({ datasheetName, datastoreId, ); }; -export default memo(PyramidListItem); +export default memo(PyramidVectorListItem); // traductions export const { i18n } = declareComponentKeys< @@ -192,10 +197,10 @@ export const { i18n } = declareComponentKeys< | "following_services_deleted" | { K: "error_deleting"; P: { pyramidName: string }; R: string } >()({ - PyramidListItem, + PyramidVectorListItem, }); -export const PyramidListItemFrTranslations: Translations<"fr">["PyramidListItem"] = { +export const PyramidVectorListItemFrTranslations: Translations<"fr">["PyramidVectorListItem"] = { show_linked_datas: "Voir les données liées", other_actions: "Autres actions", show_details: "Voir les détails", @@ -205,7 +210,7 @@ export const PyramidListItemFrTranslations: Translations<"fr">["PyramidListItem" error_deleting: ({ pyramidName }) => `La suppression de la pyramide ${pyramidName} a échoué`, }; -export const PyramidListItemEnTranslations: Translations<"en">["PyramidListItem"] = { +export const PyramidVectorListItemEnTranslations: Translations<"en">["PyramidVectorListItem"] = { show_linked_datas: "Show linked datas", other_actions: "Other actions", show_details: "Show details", diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbDesc.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbDesc.tsx index 94ef6dac..ab5c2eea 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbDesc.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbDesc.tsx @@ -3,9 +3,9 @@ import Badge from "@codegouvfr/react-dsfr/Badge"; import { UseQueryResult } from "@tanstack/react-query"; import { FC, memo, useMemo } from "react"; -import LoadingText from "../../../../../../components/Utils/LoadingText"; import { StoredDataStatusEnum, StoredDataTypeEnum } from "../../../../../../@types/app"; import { OfferingListResponseDto, ProcessingExecutionStoredDataDto } from "../../../../../../@types/entrepot"; +import LoadingText from "../../../../../../components/Utils/LoadingText"; import { offeringTypeDisplayName } from "../../../../../../utils"; type VectorDbDescProps = { diff --git a/assets/entrepot/pages/datasheet/DatasheetView/DatasheetView.tsx b/assets/entrepot/pages/datasheet/DatasheetView/DatasheetView.tsx index 9272596b..dcbdc527 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/DatasheetView.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/DatasheetView.tsx @@ -166,7 +166,10 @@ const DatasheetView: FC = ({ datastoreId, datasheetName }) = }, { label: t("tab_label.datasets", { - num: (datasheetQuery.data?.vector_db_list?.length || 0) + (datasheetQuery.data?.pyramid_list?.length || 0), + num: + (datasheetQuery.data?.vector_db_list?.length || 0) + + (datasheetQuery.data?.pyramid_vector_list?.length || 0) + + (datasheetQuery.data?.pyramid_raster_list?.length || 0), }), tabId: DatasheetViewActiveTabEnum.Dataset, }, @@ -243,8 +246,8 @@ const DatasheetView: FC = ({ datastoreId, datasheetName }) = {datasheetQuery?.data?.vector_db_list?.length && datasheetQuery?.data?.vector_db_list.length > 0 ? (
  • {datasheetQuery?.data?.vector_db_list.length} base(s) de données
  • ) : null} - {datasheetQuery?.data?.pyramid_list?.length && datasheetQuery?.data?.pyramid_list.length > 0 ? ( -
  • {datasheetQuery?.data?.pyramid_list.length} pyramide(s) de tuiles vectorielles
  • + {datasheetQuery?.data?.pyramid_vector_list?.length && datasheetQuery?.data?.pyramid_vector_list.length > 0 ? ( +
  • {datasheetQuery?.data?.pyramid_vector_list.length} pyramide(s) de tuiles vectorielles
  • ) : null} {datasheetQuery.data?.service_list?.length && datasheetQuery.data.service_list.length > 0 ? (
  • {datasheetQuery.data?.service_list.length} service(s) publié(s)
  • @@ -255,7 +258,7 @@ const DatasheetView: FC = ({ datastoreId, datasheetName }) = {metadataQuery.data &&
  • La métadonnée associée ({metadataQuery.data.file_identifier})
  • } - {/* TODO : pyramides tuiles vectorielles, raster, métadonnées etc... */} + {/* TODO : pyramides tuiles raster, documents etc... */} , document.body diff --git a/assets/entrepot/pages/datasheet/DatasheetView/MetadataTab/MetadataTab.tsx b/assets/entrepot/pages/datasheet/DatasheetView/MetadataTab/MetadataTab.tsx index a7172bc4..d27bb6ae 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/MetadataTab/MetadataTab.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/MetadataTab/MetadataTab.tsx @@ -41,8 +41,8 @@ const MetadataTab: FC = ({ datastoreId, datasheet, metadataQue }, [metadata]); const storedDataList: StoredData[] = useMemo( - () => [...(datasheet?.vector_db_list ?? []), ...(datasheet?.pyramid_list ?? [])], - [datasheet?.vector_db_list, datasheet?.pyramid_list] + () => [...(datasheet?.vector_db_list ?? []), ...(datasheet?.pyramid_vector_list ?? [])], + [datasheet?.vector_db_list, datasheet?.pyramid_vector_list] ); const catalogueDatasheetUrl = useMemo(() => { diff --git a/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServiceDesc.tsx b/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServiceDesc.tsx index 869d2bcc..2a74e1ef 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServiceDesc.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServiceDesc.tsx @@ -2,10 +2,11 @@ import { fr } from "@codegouvfr/react-dsfr"; import { useQuery } from "@tanstack/react-query"; import { FC, useMemo } from "react"; -import api from "../../../../api"; +import type { PyramidRaster, PyramidVector, Service, VectorDb } from "../../../../../@types/app"; +import { ConfigurationTypeEnum, StoredDataTypeEnum } from "../../../../../@types/app"; import LoadingText from "../../../../../components/Utils/LoadingText"; import RQKeys from "../../../../../modules/entrepot/RQKeys"; -import { ConfigurationTypeEnum, Pyramid, Service, VectorDb } from "../../../../../@types/app"; +import api from "../../../../api"; type ServiceDescProps = { service: Service; @@ -13,13 +14,13 @@ type ServiceDescProps = { }; const ServiceDesc: FC = ({ service, datastoreId }) => { /** - * - si le service est du type ConfigurationTypeEnum.WMTSTMS, service.configuration.type_infos.used_data?.[0].stored_data est pyramidUsedId. Il faut donc faire une autre requête pour récupérer les infos de vectorDb + * - si le service est du type ConfigurationTypeEnum.WMTSTMS ou ConfigurationTypeEnum.WMSRASTER, service.configuration.type_infos.used_data?.[0].stored_data est pyramidUsedId. Il faut donc faire une autre requête pour récupérer les infos de vectorDb * - si le service est du type ConfigurationTypeEnum.WFS ou ConfigurationTypeEnum.WMSVECTOR, service.configuration.type_infos.used_data?.[0].stored_data est vectorDbUsedId * - il y aura peut-être d'autres cas à gérer */ const pyramidUsedId = useMemo(() => { - if (service.configuration.type === ConfigurationTypeEnum.WMTSTMS) { + if ([ConfigurationTypeEnum.WMTSTMS, ConfigurationTypeEnum.WMSRASTER].includes(service.configuration.type)) { return service.configuration.type_infos.used_data?.[0].stored_data; } }, [service]); @@ -30,14 +31,14 @@ const ServiceDesc: FC = ({ service, datastoreId }) => { if (pyramidUsedId === undefined) { return Promise.reject(); } - return api.storedData.get(datastoreId, pyramidUsedId, { signal }); + return api.storedData.get(datastoreId, pyramidUsedId, { signal }); }, staleTime: 600000, enabled: !!pyramidUsedId, }); const vectorDbUsedId = useMemo(() => { - if (service.configuration.type === ConfigurationTypeEnum.WMTSTMS) { + if ([ConfigurationTypeEnum.WMTSTMS, ConfigurationTypeEnum.WMSRASTER].includes(service.configuration.type)) { return pyramidUsedQuery.data?.tags.vectordb_id; } else if ([ConfigurationTypeEnum.WFS, ConfigurationTypeEnum.WMSVECTOR].includes(service.configuration.type)) { return service.configuration.type_infos.used_data?.[0].stored_data; @@ -83,7 +84,15 @@ const ServiceDesc: FC = ({ service, datastoreId }) => { style={{ backgroundColor: fr.colors.decisions.background.default.grey.default }} >
    - Pyramide de tuiles vectorielles utilisée + {" "} + {(() => { + switch (pyramidUsedQuery.data.type) { + case StoredDataTypeEnum.ROK4PYRAMIDVECTOR: + return "Pyramide de tuiles vectorielles utilisée"; + case StoredDataTypeEnum.ROK4PYRAMIDRASTER: + return "Pyramide de tuiles raster utilisée"; + } + })()}
      diff --git a/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServicesListItem.tsx b/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServicesListItem.tsx index 5a0d820c..c7202f8a 100644 --- a/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServicesListItem.tsx +++ b/assets/entrepot/pages/datasheet/DatasheetView/ServiceListTab/ServicesListItem.tsx @@ -7,6 +7,7 @@ import { FC } from "react"; import { createPortal } from "react-dom"; import { symToStr } from "tsafe/symToStr"; +import { OfferingStatusEnum, OfferingTypeEnum, StoredDataTypeEnum, type Service } from "../../../../../@types/app"; import OfferingStatusBadge from "../../../../../components/Utils/Badges/OfferingStatusBadge"; import MenuList from "../../../../../components/Utils/MenuList"; import Wait from "../../../../../components/Utils/Wait"; @@ -14,7 +15,6 @@ import useToggle from "../../../../../hooks/useToggle"; import RQKeys from "../../../../../modules/entrepot/RQKeys"; import { routes } from "../../../../../router/router"; import { useSnackbarStore } from "../../../../../stores/SnackbarStore"; -import { OfferingStatusEnum, OfferingTypeEnum, type Service } from "../../../../../@types/app"; import { formatDateFromISO, offeringTypeDisplayName } from "../../../../../utils"; import api from "../../../../api"; import ServiceDesc from "./ServiceDesc"; @@ -36,7 +36,7 @@ const ServicesListItem: FC = ({ service, datasheetName, d const unpublishServiceMutation = useMutation({ mutationFn: (service: Service) => { - if (![OfferingTypeEnum.WFS, OfferingTypeEnum.WMSVECTOR, OfferingTypeEnum.WMTSTMS].includes(service.type)) { + if (![OfferingTypeEnum.WFS, OfferingTypeEnum.WMSVECTOR, OfferingTypeEnum.WMSRASTER, OfferingTypeEnum.WMTSTMS].includes(service.type)) { console.warn(`Dépublication de service ${service.type} n'a pas encore été implémentée`); return Promise.reject(`Dépublication de service ${service.type} n'a pas encore été implémentée`); } @@ -105,12 +105,11 @@ const ServicesListItem: FC = ({ service, datasheetName, d } }, }, - { + [OfferingTypeEnum.WFS, OfferingTypeEnum.WMTSTMS].includes(service.type) && { text: "Gérer les styles", iconId: "ri-flashlight-line", linkProps: routes.datastore_service_view({ datastoreId, datasheetName, offeringId: service._id, activeTab: "styles" }) .link, - disabled: ![OfferingTypeEnum.WFS, OfferingTypeEnum.WMTSTMS].includes(service.type), }, // { // text: "Mettre à jour la légende", @@ -123,7 +122,9 @@ const ServicesListItem: FC = ({ service, datasheetName, d linkProps: routes.datastore_manage_permissions({ datastoreId }).link, disabled: service.open === true, }, - { + [OfferingTypeEnum.WMSVECTOR, OfferingTypeEnum.WMSRASTER, OfferingTypeEnum.WFS, OfferingTypeEnum.WMTSTMS].includes( + service.type + ) && { text: "Modifier les informations de publication", iconId: "ri-edit-box-line", linkProps: (() => { @@ -136,29 +137,52 @@ const ServicesListItem: FC = ({ service, datasheetName, d datasheetName, }).link; - case OfferingTypeEnum.WFS: - return routes.datastore_wfs_service_edit({ + case OfferingTypeEnum.WMSRASTER: + return routes.datastore_pyramid_raster_wms_raster_service_edit({ datastoreId, - vectorDbId: service.configuration.type_infos.used_data[0].stored_data, + pyramidId: service.configuration.type_infos.used_data[0].stored_data, offeringId: service._id, datasheetName, }).link; - case OfferingTypeEnum.WMTSTMS: - return routes.datastore_pyramid_vector_tms_service_edit({ + case OfferingTypeEnum.WFS: + return routes.datastore_wfs_service_edit({ datastoreId, - pyramidId: service.configuration.type_infos.used_data[0].stored_data, + vectorDbId: service.configuration.type_infos.used_data[0].stored_data, offeringId: service._id, datasheetName, }).link; + case OfferingTypeEnum.WMTSTMS: + switch (service.configuration.pyramid?.type) { + case StoredDataTypeEnum.ROK4PYRAMIDVECTOR: + return routes.datastore_pyramid_vector_tms_service_edit({ + datastoreId, + pyramidId: service.configuration.type_infos.used_data[0].stored_data, + offeringId: service._id, + datasheetName, + }).link; + case StoredDataTypeEnum.ROK4PYRAMIDRASTER: + return routes.datastore_pyramid_raster_wmts_service_edit({ + datastoreId, + pyramidId: service.configuration.type_infos.used_data[0].stored_data, + offeringId: service._id, + datasheetName, + }).link; + + default: + return routes.page_not_found().link; + } + default: - return { - onClick: () => console.warn("Action non implémentée"), - }; + return routes.page_not_found().link; } })(), - disabled: ![OfferingTypeEnum.WMSVECTOR, OfferingTypeEnum.WFS, OfferingTypeEnum.WMTSTMS].includes(service.type), + }, + service.type === OfferingTypeEnum.WMSVECTOR && { + text: "Créer un service raster WMS/WMTS", + iconId: "ri-add-box-line", + linkProps: routes.datastore_pyramid_raster_generate({ datastoreId, offeringId: service._id, datasheetName }).link, }, // NOTE : reporté cf. issue #249 // { diff --git a/assets/entrepot/pages/service/default-values.ts b/assets/entrepot/pages/service/default-values.ts index 2de9dc87..3f6f7998 100644 --- a/assets/entrepot/pages/service/default-values.ts +++ b/assets/entrepot/pages/service/default-values.ts @@ -3,7 +3,6 @@ import { format as datefnsFormat } from "date-fns"; import { EndpointTypeEnum, Metadata, MetadataFormValuesType, MetadataHierarchyLevel, Service, StoredData } from "../../../@types/app"; import { ConfigurationWfsDetailsContent, ConfigurationWmsVectorDetailsContent, ConfigurationWmtsTmsDetailsContent } from "../../../@types/entrepot"; import { getProjectionCode, removeDiacritics } from "../../../utils"; -import { getEndpointSuffix } from "./metadatas/Description"; import { PyramidVectorTmsServiceFormValuesType } from "./tms/PyramidVectorTmsServiceForm"; import { WfsServiceFormValuesType, WfsTableInfos } from "./wfs/WfsServiceForm"; import { WmsVectorServiceFormValuesType } from "./wms-vector/WmsVectorServiceForm"; @@ -11,6 +10,23 @@ import { WmsVectorServiceFormValuesType } from "./wms-vector/WmsVectorServiceFor const DEFAULT_CHARSET = "utf8"; const DEFAULT_LANGUAGE = { language: "français", code: "fre" }; +export const getEndpointSuffix = (endpointType: EndpointTypeEnum | string) => { + switch (endpointType) { + case EndpointTypeEnum.WFS: + return "wfs"; + case EndpointTypeEnum.WMSVECTOR: + return "wmsv"; + case EndpointTypeEnum.WMSRASTER: + return "wmsr"; + case "tms": + return "tms"; + case "wmts": + return "wmts"; + default: + return "other"; // TODO + } +}; + const getMetadataFormDefaultValues = (metadata?: Metadata): MetadataFormValuesType => { return { languages: metadata?.csw_metadata?.language ? [metadata?.csw_metadata?.language] : [DEFAULT_LANGUAGE], @@ -168,7 +184,7 @@ export const getPyramidVectorTmsServiceFormDefaultValues = ( attribution_url: offering?.configuration.attribution?.url, }; } else { - const suffix = getEndpointSuffix(EndpointTypeEnum.WMTSTMS); + const suffix = getEndpointSuffix("tms"); const storedDataName = pyramid?.name ?? ""; const nice = removeDiacritics(storedDataName.toLowerCase()).replace(/ /g, "_"); @@ -189,3 +205,43 @@ export const getPyramidVectorTmsServiceFormDefaultValues = ( return defValues; }; + +export const getPyramidRasterWmsRasterServiceFormDefaultValues = (offering?: Service | null, editMode?: boolean, pyramid?: StoredData, metadata?: Metadata) => { + // NOTE : a priori à peu près la même chose que la publication d'une pyramide vecteur en tms + + let technicalName: string | undefined; + + if (editMode) { + technicalName = offering?.configuration.layer_name; + } else { + const suffix = getEndpointSuffix(EndpointTypeEnum.WMSRASTER); + const storedDataName = pyramid?.name ?? ""; + const nice = removeDiacritics(storedDataName.toLowerCase()).replace(/ /g, "_"); + technicalName = `${nice}_${suffix}`; + } + + return { + ...getPyramidVectorTmsServiceFormDefaultValues(offering, editMode, pyramid, metadata), + technical_name: technicalName, + }; +}; + +export const getPyramidRasterWmtsServiceFormDefaultValues = (offering?: Service | null, editMode?: boolean, pyramid?: StoredData, metadata?: Metadata) => { + // NOTE : a priori à peu près la même chose que la publication d'une pyramide vecteur en tms + + let technicalName: string | undefined; + + if (editMode) { + technicalName = offering?.configuration.layer_name; + } else { + const suffix = getEndpointSuffix("wmts"); + const storedDataName = pyramid?.name ?? ""; + const nice = removeDiacritics(storedDataName.toLowerCase()).replace(/ /g, "_"); + technicalName = `${nice}_${suffix}`; + } + + return { + ...getPyramidVectorTmsServiceFormDefaultValues(offering, editMode, pyramid, metadata), + technical_name: technicalName, + }; +}; diff --git a/assets/entrepot/pages/service/metadatas/AdditionalInfo.tsx b/assets/entrepot/pages/service/metadatas/AdditionalInfo.tsx index f17b3d0d..3fc5c408 100644 --- a/assets/entrepot/pages/service/metadatas/AdditionalInfo.tsx +++ b/assets/entrepot/pages/service/metadatas/AdditionalInfo.tsx @@ -6,7 +6,8 @@ import { Select as SelectNext } from "@codegouvfr/react-dsfr/SelectNext"; import { FC, useMemo } from "react"; import { Controller, UseFormReturn } from "react-hook-form"; -import { MetadataHierarchyLevel, type Pyramid, type ServiceFormValuesBaseType, type VectorDb } from "../../../../@types/app"; +import type { PyramidRaster, PyramidVector } from "../../../../@types/app"; +import { MetadataHierarchyLevel, type ServiceFormValuesBaseType, type VectorDb } from "../../../../@types/app"; import AutocompleteSelect from "../../../../components/Input/AutocompleteSelect"; import resolutions from "../../../../data/md_resolutions.json"; import { getTranslation } from "../../../../i18n/i18n"; @@ -30,7 +31,7 @@ import { LanguageType, charsets, getLanguages } from "../../../../utils"; // }; type AdditionalInfoProps = { - storedData: VectorDb | Pyramid; + storedData: VectorDb | PyramidVector | PyramidRaster; visible: boolean; form: UseFormReturn; datastoreId: string; diff --git a/assets/entrepot/pages/service/metadatas/Description.tsx b/assets/entrepot/pages/service/metadatas/Description.tsx index e0399b1a..6b5123e0 100644 --- a/assets/entrepot/pages/service/metadatas/Description.tsx +++ b/assets/entrepot/pages/service/metadatas/Description.tsx @@ -5,7 +5,7 @@ import { XMLParser } from "fast-xml-parser"; import { FC, useEffect } from "react"; import { Controller, UseFormReturn } from "react-hook-form"; -import { EndpointTypeEnum, type ServiceFormValuesBaseType } from "../../../../@types/app"; +import { type ServiceFormValuesBaseType } from "../../../../@types/app"; import AutocompleteSelect from "../../../../components/Input/AutocompleteSelect"; import MarkdownEditor from "../../../../components/Input/MarkdownEditor"; import frequencyCodes from "../../../../data/maintenance_frequency.json"; @@ -15,19 +15,6 @@ import { getInspireKeywords, regex } from "../../../../utils"; const keywords = getInspireKeywords(); -export const getEndpointSuffix = (endpointType: EndpointTypeEnum) => { - switch (endpointType) { - case EndpointTypeEnum.WFS: - return "wfs"; - case EndpointTypeEnum.WMSVECTOR: - return "wmsv"; - case EndpointTypeEnum.WMTSTMS: - return "tms"; - default: - return "other"; // TODO - } -}; - type DescriptionProps = { visible: boolean; form: UseFormReturn; diff --git a/assets/entrepot/pages/service/tms/PyramidVectorGenerateForm.tsx b/assets/entrepot/pages/service/tms/PyramidVectorGenerateForm.tsx index 85c7b8b8..8b01c7a5 100644 --- a/assets/entrepot/pages/service/tms/PyramidVectorGenerateForm.tsx +++ b/assets/entrepot/pages/service/tms/PyramidVectorGenerateForm.tsx @@ -4,7 +4,7 @@ import Button from "@codegouvfr/react-dsfr/Button"; import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup"; import Stepper from "@codegouvfr/react-dsfr/Stepper"; import { yupResolver } from "@hookform/resolvers/yup"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { FC, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import * as yup from "yup"; @@ -84,6 +84,8 @@ const PyramidVectorGenerateForm: FC = ({ datastoreId, vec /* l'etape courante */ const [currentStep, setCurrentStep] = useState(STEPS.TABLES_SELECTION); + const queryClient = useQueryClient(); + const vectorDbQuery = useQuery({ queryKey: RQKeys.datastore_stored_data(datastoreId, vectorDbId), queryFn: () => api.storedData.get(datastoreId, vectorDbId), @@ -137,10 +139,13 @@ const PyramidVectorGenerateForm: FC = ({ datastoreId, vec setIsSubmitting(true); - api.pyramid + api.pyramidVector .add(datastoreId, formatted) .then(() => { if (vectorDbQuery.data?.tags?.datasheet_name) { + queryClient.invalidateQueries({ + queryKey: RQKeys.datastore_datasheet(datastoreId, vectorDbQuery.data?.tags.datasheet_name), + }); routes.datastore_datasheet_view({ datastoreId, datasheetName: vectorDbQuery.data?.tags.datasheet_name, activeTab: "dataset" }).push(); } else { routes.datasheet_list({ datastoreId }).push(); diff --git a/assets/entrepot/pages/service/tms/PyramidVectorTmsServiceForm.tsx b/assets/entrepot/pages/service/tms/PyramidVectorTmsServiceForm.tsx index 9f383b30..6ba76f46 100644 --- a/assets/entrepot/pages/service/tms/PyramidVectorTmsServiceForm.tsx +++ b/assets/entrepot/pages/service/tms/PyramidVectorTmsServiceForm.tsx @@ -9,7 +9,7 @@ import { declareComponentKeys } from "i18nifty"; import { FC, useCallback, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; -import { ConfigurationTypeEnum, EndpointTypeEnum, Pyramid, Service, ServiceFormValuesBaseType } from "../../../../@types/app"; +import { ConfigurationTypeEnum, EndpointTypeEnum, PyramidVector, Service, ServiceFormValuesBaseType } from "../../../../@types/app"; import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; import LoadingIcon from "../../../../components/Utils/LoadingIcon"; import LoadingText from "../../../../components/Utils/LoadingText"; @@ -32,9 +32,9 @@ export type PyramidVectorTmsServiceFormValuesType = ServiceFormValuesBaseType; const commonValidation = new CommonSchemasValidation(); const STEPS = { - METADATAS_UPLOAD: 1, - METADATAS_DESCRIPTION: 2, - METADATAS_ADDITIONALINFORMATIONS: 3, + METADATA_UPLOAD: 1, + METADATA_DESCRIPTION: 2, + METADATA_ADDITIONALINFORMATIONS: 3, ACCESSRESTRICTIONS: 4, }; @@ -51,14 +51,14 @@ const PyramidVectorTmsServiceForm: FC = ({ dat const editMode = useMemo(() => !!offeringId, [offeringId]); /* l'etape courante */ - const [currentStep, setCurrentStep] = useState(STEPS.METADATAS_UPLOAD); + const [currentStep, setCurrentStep] = useState(STEPS.METADATA_UPLOAD); const queryClient = useQueryClient(); const createServiceMutation = useMutation({ mutationFn: () => { const formValues = getFormValues(); - return api.pyramid.publish(datastoreId, pyramidId, formValues); + return api.pyramidVector.publish(datastoreId, pyramidId, formValues); }, onSuccess() { if (pyramidQuery.data?.tags?.datasheet_name) { @@ -79,8 +79,7 @@ const PyramidVectorTmsServiceForm: FC = ({ dat } const formValues = getFormValues(); - - return api.pyramid.publishEdit(datastoreId, pyramidId, offeringId, formValues); + return api.pyramidVector.publishEdit(datastoreId, pyramidId, offeringId, formValues); }, onSuccess() { if (offeringId !== undefined) { @@ -103,7 +102,7 @@ const PyramidVectorTmsServiceForm: FC = ({ dat const pyramidQuery = useQuery({ queryKey: RQKeys.datastore_stored_data(datastoreId, pyramidId), - queryFn: () => api.storedData.get(datastoreId, pyramidId), + queryFn: ({ signal }) => api.storedData.get(datastoreId, pyramidId, { signal }), staleTime: Infinity, enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), }); @@ -135,13 +134,13 @@ const PyramidVectorTmsServiceForm: FC = ({ dat // Definition du schema const schemas = {}; - schemas[STEPS.METADATAS_UPLOAD] = commonValidation.getMDUploadFileSchema(); - schemas[STEPS.METADATAS_DESCRIPTION] = commonValidation.getMDDescriptionSchema( + schemas[STEPS.METADATA_UPLOAD] = commonValidation.getMDUploadFileSchema(); + schemas[STEPS.METADATA_DESCRIPTION] = commonValidation.getMDDescriptionSchema( existingLayerNamesQuery.data, editMode, offeringQuery.data?.configuration.layer_name ); - schemas[STEPS.METADATAS_ADDITIONALINFORMATIONS] = commonValidation.getMDAdditionalInfoSchema(); + schemas[STEPS.METADATA_ADDITIONALINFORMATIONS] = commonValidation.getMDAdditionalInfoSchema(); schemas[STEPS.ACCESSRESTRICTIONS] = commonValidation.getAccessRestrictionSchema(); const defaultValues: PyramidVectorTmsServiceFormValuesType = useMemo( @@ -188,7 +187,7 @@ const PyramidVectorTmsServiceForm: FC = ({ dat

      {t("title", { editMode })}

      {pyramidQuery.isLoading || offeringQuery.isLoading ? ( - + ) : pyramidQuery.data === undefined ? ( = ({ dat )} {editServiceMutation.error && } - - + + = ({ dat alignment="between" buttons={[ { - children: t("previous_step"), + children: tCommon("previous_step"), iconId: "fr-icon-arrow-left-fill", priority: "tertiary", onClick: previousStep, - disabled: currentStep === STEPS.METADATAS_UPLOAD, + disabled: currentStep === STEPS.METADATA_UPLOAD, }, { - children: currentStep < Object.values(STEPS).length ? t("continue") : t("publish"), + children: currentStep < Object.values(STEPS).length ? tCommon("continue") : t("publish"), onClick: nextStep, }, ]} @@ -289,8 +288,6 @@ export const { i18n } = declareComponentKeys< | "stored_data.fetch_failed" | "offering.fetch_failed" | { K: "step.title"; P: { stepNumber: number }; R: string } - | "previous_step" - | "continue" | "publish" | "publish.in_progress" | "modify.in_progress" @@ -301,8 +298,8 @@ export const { i18n } = declareComponentKeys< export const PyramidVectorTmsServiceFormFrTranslations: Translations<"fr">["PyramidVectorTmsServiceForm"] = { title: ({ editMode }) => (editMode ? "Modifier le service TMS" : "Publier un service TMS"), - "stored_data.loading": "Chargement de la donnée stockée...", - "stored_data_and_offering.loading": "Chargement de la donnée stockée et du service à modifier...", + "stored_data.loading": "Chargement de la donnée stockée", + "stored_data_and_offering.loading": "Chargement de la donnée stockée et du service à modifier", "stored_data.fetch_failed": "Récupération des informations sur la donnée stockée a échoué", "offering.fetch_failed": "Récupération des informations sur le service à modifier a échoué", "step.title": ({ stepNumber }) => { @@ -320,8 +317,6 @@ export const PyramidVectorTmsServiceFormFrTranslations: Translations<"fr">["Pyra return ""; } }, - previous_step: "Étape précédente", - continue: "Continuer", publish: "Publier le service maintenant", "publish.in_progress": "Création du service TMS en cours", "modify.in_progress": "Modification des informations du service TMS en cours", @@ -335,8 +330,6 @@ export const PyramidVectorTmsServiceFormEnTranslations: Translations<"en">["Pyra "stored_data.fetch_failed": undefined, "offering.fetch_failed": undefined, "step.title": undefined, - previous_step: undefined, - continue: undefined, publish: undefined, "publish.in_progress": undefined, "modify.in_progress": undefined, diff --git a/assets/entrepot/pages/service/view/ServiceView.tsx b/assets/entrepot/pages/service/view/ServiceView.tsx index 751edfcf..f403d829 100644 --- a/assets/entrepot/pages/service/view/ServiceView.tsx +++ b/assets/entrepot/pages/service/view/ServiceView.tsx @@ -6,7 +6,7 @@ import { Tabs, TabsProps } from "@codegouvfr/react-dsfr/Tabs"; import { useQuery } from "@tanstack/react-query"; import { FC, useEffect, useMemo, useState } from "react"; -import { CartesStyle, OfferingStatusEnum, OfferingTypeEnum, Service, TypeInfosWithBbox } from "../../../../@types/app"; +import { type CartesStyle, OfferingStatusEnum, OfferingTypeEnum, type Service, StoredDataTypeEnum, type TypeInfosWithBbox } from "../../../../@types/app"; import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; import LoadingText from "../../../../components/Utils/LoadingText"; import type { MapInitial } from "../../../../components/Utils/RMap"; @@ -32,7 +32,7 @@ const ServiceView: FC = ({ datastoreId, offeringId, datasheetN const serviceQuery = useQuery({ queryKey: RQKeys.datastore_offering(datastoreId, offeringId), - queryFn: () => api.service.getService(datastoreId, offeringId), + queryFn: ({ signal }) => api.service.getService(datastoreId, offeringId, { signal }), staleTime: 60000, }); @@ -63,7 +63,9 @@ const ServiceView: FC = ({ datastoreId, offeringId, datasheetN // ONGLETS const tabs: TabsProps["tabs"] = useMemo(() => { - const canManageStyles = serviceQuery.data?.type === OfferingTypeEnum.WFS || serviceQuery.data?.type === OfferingTypeEnum.WMTSTMS; + const canManageStyles = + serviceQuery.data?.type === OfferingTypeEnum.WFS || + (serviceQuery.data?.type === OfferingTypeEnum.WMTSTMS && serviceQuery.data.configuration.pyramid?.type === StoredDataTypeEnum.ROK4PYRAMIDVECTOR); const _tabs: TabsProps["tabs"] = [ { @@ -133,6 +135,22 @@ const ServiceView: FC = ({ datastoreId, offeringId, datasheetN
    )} + {serviceQuery.data?.type === OfferingTypeEnum.WMSVECTOR && serviceQuery.data?.status === OfferingStatusEnum.PUBLISHED && ( +
    + +
    + )} +
    {initialValues && }
    diff --git a/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm.tsx b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm.tsx new file mode 100644 index 00000000..e10a5fc6 --- /dev/null +++ b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm.tsx @@ -0,0 +1,309 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import Button from "@codegouvfr/react-dsfr/Button"; +import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup"; +import Input from "@codegouvfr/react-dsfr/Input"; +import Stepper from "@codegouvfr/react-dsfr/Stepper"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { declareComponentKeys } from "i18nifty"; +import { FC, useCallback, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as yup from "yup"; + +import type { PyramidRaster, Service } from "../../../../@types/app"; +import type { ConfigurationWmsVectorDetailsContent } from "../../../../@types/entrepot"; +import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; +import LoadingIcon from "../../../../components/Utils/LoadingIcon"; +import LoadingText from "../../../../components/Utils/LoadingText"; +import Wait from "../../../../components/Utils/Wait"; +import ZoomRange from "../../../../components/Utils/ZoomRange"; +import olDefaults from "../../../../data/ol-defaults.json"; +import useScrollToTopEffect from "../../../../hooks/useScrollToTopEffect"; +import { Translations, useTranslation } from "../../../../i18n/i18n"; +import RQKeys from "../../../../modules/entrepot/RQKeys"; +import { CartesApiException } from "../../../../modules/jsonFetch"; +import { routes } from "../../../../router/router"; +import { bboxToWkt } from "../../../../utils"; +import api from "../../../api"; +import { DatasheetViewActiveTabEnum } from "../../datasheet/DatasheetView/DatasheetView"; + +const STEPS = { + TECHNICAL_NAME: 1, + ZOOM_RANGE: 2, +}; + +type PyramidRasterGenerateFormType = { + technical_name: string; + zoom_range: number[]; +}; + +type PyramidRasterGenerateFormProps = { + datastoreId: string; + offeringId: string; + datasheetName: string; +}; +const PyramidRasterGenerateForm: FC = ({ datastoreId, offeringId, datasheetName }) => { + const { t } = useTranslation("PyramidRasterGenerateForm"); + const { t: tCommon } = useTranslation("Common"); + + const [currentStep, setCurrentStep] = useState(STEPS.TECHNICAL_NAME); + + const serviceQuery = useQuery({ + queryKey: RQKeys.datastore_offering(datastoreId, offeringId), + queryFn: () => api.service.getService(datastoreId, offeringId), + staleTime: 60000, + }); + + const queryClient = useQueryClient(); + + const schemas = {}; + schemas[STEPS.TECHNICAL_NAME] = yup.object({ + technical_name: yup.string().typeError(t("technical_name.error.mandatory")).required(t("technical_name.error.mandatory")), + }); + + schemas[STEPS.ZOOM_RANGE] = yup.object({ + zoom_range: yup.array().of(yup.number()).length(2, t("zoom_range.error")).required(t("zoom_range.error")), + }); + + const form = useForm({ + resolver: yupResolver(schemas[currentStep]), + mode: "onChange", + defaultValues: { + technical_name: "", + zoom_range: [4, 16], + }, + }); + + const { + register, + trigger, + getValues: getFormValues, + setValue: setFormValue, + formState: { errors }, + watch, + } = form; + + const zoomRange = watch("zoom_range"); + + const generatePyramidRasterMutation = useMutation({ + mutationFn: () => { + const formData = { + ...getFormValues(), + wmsv_offering_id: offeringId, + wmsv_config_bbox: bboxToWkt((serviceQuery.data?.configuration.type_infos as ConfigurationWmsVectorDetailsContent).bbox!), + }; + + return api.pyramidRaster.add(datastoreId, formData); + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName), + }); + routes.datastore_datasheet_view({ datastoreId, datasheetName: datasheetName, activeTab: "dataset" }).push(); + }, + }); + + useScrollToTopEffect(currentStep); + + const previousStep = useCallback(() => setCurrentStep((currentStep) => currentStep - 1), []); + + const nextStep = useCallback(async () => { + const isStepValid = await trigger(undefined, { shouldFocus: true }); // demande de valider le formulaire + if (!isStepValid) return; // ne fait rien si formulaire invalide + // formulaire est valide + if (currentStep < Object.values(STEPS).length) { + // on passe à la prochaine étape du formulaire + setCurrentStep((currentStep) => currentStep + 1); + } else { + // on est à la dernière étape du formulaire donc on envoie la sauce + generatePyramidRasterMutation.mutate(); + } + }, [currentStep, generatePyramidRasterMutation, trigger]); + + return ( + +

    {t("title")}

    + + {serviceQuery.isLoading ? ( + + ) : serviceQuery.error !== null ? ( + +

    {serviceQuery.error?.message}

    + + + } + /> + ) : (serviceQuery.data?.configuration.type_infos as ConfigurationWmsVectorDetailsContent).bbox === undefined ? ( + + {t("back_to_datasheet")} + + } + /> + ) : ( + <> + + +

    {tCommon("mandatory_fields")}

    + + {generatePyramidRasterMutation.error && ( + + )} + +
    +

    {t("technical_name.lead_text")}

    + +
    + +
    +

    {t("zoom_range.lead_text")}

    +

    {t("zoom_range.explanation")}

    + {currentStep === STEPS.ZOOM_RANGE && ( + <> + setFormValue("zoom_range", values)} + step={1} + mode="both" + /> + {errors.zoom_range?.message !== undefined &&

    {errors.zoom_range?.message}

    } + + )} +
    + + + + )} + + {generatePyramidRasterMutation.isPending && ( + +
    +
    +
    + +
    +
    +
    {t("generate.in_progress")}
    +
    +
    +
    +
    + )} +
    + ); +}; + +export default PyramidRasterGenerateForm; + +export const { i18n } = declareComponentKeys< + | "title" + | { K: "step.title"; P: { stepNumber: number }; R: string } + | "wmsv-service.loading" + | "wmsv-service.fetch_failed" + | "wmsv-service.bbox_not_found" + | "back_to_datasheet" + | "technical_name.lead_text" + | "technical_name.label" + | "technical_name.explanation" + | "technical_name.error.mandatory" + | "zoom_range.lead_text" + | "zoom_range.explanation" + | "zoom_range.error" + | "generate.in_progress" +>()({ + PyramidRasterGenerateForm, +}); + +export const PyramidRasterGenerateFormFrTranslations: Translations<"fr">["PyramidRasterGenerateForm"] = { + // title: "Créer un service raster WMS/WMTS", + title: "Générer une pyramide de tuiles raster", + "step.title": ({ stepNumber }) => { + switch (stepNumber) { + case 1: + return "Nom de la pyramide de tuiles raster"; + case 2: + return "Niveaux de pyramide"; + default: + return ""; + } + }, + "wmsv-service.loading": "Chargement du service WMS-Vecteur...", + "wmsv-service.fetch_failed": "Récupération des informations sur le service WMS-Vecteur a échoué", + "wmsv-service.bbox_not_found": "La bbox du service WMS-Vecteur n'a pas été trouvée, veuillez vérifier le service et la donnée stockée utilisée", + back_to_datasheet: "Retour à la fiche de données", + "technical_name.lead_text": "Choisissez le nom technique de la pyramide de tuiles raster", + "technical_name.label": "Nom technique de la pyramide de tuiles raster", + "technical_name.explanation": + "II s'agit du nom technique du service qui apparaitra dans votre espace de travail, il ne sera pas publié en ligne. Si vous le renommez, choisissez un nom explicite.", + "technical_name.error.mandatory": "Le nom technique de la pyramide de tuiles raster est obligatoire", + "zoom_range.lead_text": "Choisissez les niveaux de pyramide à générer", + "zoom_range.explanation": + "Les niveaux de zoom de la pyramide de tuiles raster sont prédéfinis. Choisissez la borne minimum de votre pyramide de tuiles en vous aidant de la carte de gauche et le zoom maximum en vous aidant de la carte de droite. Tous les niveaux intermédiaires seront générés.", + "zoom_range.error": "Les bornes de la pyramide sont obligatoires.", + "generate.in_progress": "Génération de pyramide de tuiles raster en cours", +}; + +export const PyramidRasterGenerateFormEnTranslations: Translations<"en">["PyramidRasterGenerateForm"] = { + title: undefined, + "step.title": undefined, + "wmsv-service.loading": undefined, + "wmsv-service.fetch_failed": undefined, + "wmsv-service.bbox_not_found": undefined, + back_to_datasheet: undefined, + "technical_name.error.mandatory": undefined, + "technical_name.lead_text": undefined, + "technical_name.label": undefined, + "technical_name.explanation": undefined, + "zoom_range.lead_text": undefined, + "zoom_range.explanation": undefined, + "zoom_range.error": undefined, + "generate.in_progress": undefined, +}; diff --git a/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm.tsx b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm.tsx new file mode 100644 index 00000000..167ae424 --- /dev/null +++ b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm.tsx @@ -0,0 +1,320 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import Button from "@codegouvfr/react-dsfr/Button"; +import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup"; +import Stepper from "@codegouvfr/react-dsfr/Stepper"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { declareComponentKeys } from "i18nifty"; +import { FC, useCallback, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; +import { symToStr } from "tsafe/symToStr"; + +import { ConfigurationTypeEnum, EndpointTypeEnum, PyramidRaster, Service, ServiceFormValuesBaseType } from "../../../../@types/app"; +import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; +import LoadingIcon from "../../../../components/Utils/LoadingIcon"; +import LoadingText from "../../../../components/Utils/LoadingText"; +import Wait from "../../../../components/Utils/Wait"; +import useScrollToTopEffect from "../../../../hooks/useScrollToTopEffect"; +import { Translations, useTranslation } from "../../../../i18n/i18n"; +import RQKeys from "../../../../modules/entrepot/RQKeys"; +import { CartesApiException } from "../../../../modules/jsonFetch"; +import { routes } from "../../../../router/router"; +import api from "../../../api"; +import AccessRestrictions from "../AccessRestrictions"; +import { CommonSchemasValidation } from "../common-schemas-validation"; +import { getPyramidRasterWmsRasterServiceFormDefaultValues } from "../default-values"; +import AdditionalInfo from "../metadatas/AdditionalInfo"; +import Description from "../metadatas/Description"; +import UploadMDFile from "../metadatas/UploadMDFile"; + +const STEPS = { + METADATA_UPLOAD: 1, + METADATA_DESCRIPTION: 2, + METADATA_ADDITIONALINFORMATIONS: 3, + ACCESSRESTRICTIONS: 4, +}; + +const commonValidation = new CommonSchemasValidation(); + +type PyramidRasterWmsRasterServiceFormProps = { + datastoreId: string; + pyramidId: string; + datasheetName: string; + offeringId?: string; +}; +const PyramidRasterWmsRasterServiceForm: FC = ({ datastoreId, pyramidId, datasheetName, offeringId }) => { + const { t } = useTranslation("PyramidRasterWmsRasterServiceForm"); + const { t: tCommon } = useTranslation("Common"); + + const editMode = useMemo(() => Boolean(offeringId), [offeringId]); + + const [currentStep, setCurrentStep] = useState(STEPS.METADATA_UPLOAD); + + const queryClient = useQueryClient(); + + const createServiceMutation = useMutation({ + mutationFn: () => { + const formValues = getFormValues(); + return api.pyramidRaster.publishWmsRasterWmts(datastoreId, pyramidId, ConfigurationTypeEnum.WMSRASTER, formValues); + }, + onSuccess() { + queryClient.invalidateQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) }); + routes.datastore_datasheet_view({ datastoreId, datasheetName: datasheetName, activeTab: "services" }).push(); + }, + }); + + const editServiceMutation = useMutation({ + mutationFn: () => { + if (offeringId === undefined) { + return Promise.reject(); + } + + const formValues = getFormValues(); + return api.pyramidRaster.editWmsRasterWmts(datastoreId, pyramidId, offeringId, ConfigurationTypeEnum.WMSRASTER, formValues); + }, + onSuccess() { + if (offeringId !== undefined) { + queryClient.removeQueries({ queryKey: RQKeys.datastore_offering(datastoreId, offeringId) }); + } + + queryClient.invalidateQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) }); + routes.datastore_datasheet_view({ datastoreId, datasheetName: datasheetName, activeTab: "services" }).push(); + queryClient.refetchQueries({ queryKey: RQKeys.datastore_metadata_by_datasheet_name(datastoreId, datasheetName) }); + }, + }); + + const pyramidQuery = useQuery({ + queryKey: RQKeys.datastore_stored_data(datastoreId, pyramidId), + queryFn: () => api.storedData.get(datastoreId, pyramidId), + staleTime: Infinity, + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const existingLayerNamesQuery = useQuery({ + queryKey: RQKeys.datastore_layernames_list(datastoreId, ConfigurationTypeEnum.WMSRASTER), + queryFn: ({ signal }) => api.service.getExistingLayerNames(datastoreId, ConfigurationTypeEnum.WMSRASTER, { signal }), + refetchInterval: 30000, + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const offeringQuery = useQuery({ + queryKey: RQKeys.datastore_offering(datastoreId, offeringId ?? "xxxx"), + queryFn: ({ signal }) => { + if (offeringId) { + return api.service.getService(datastoreId, offeringId, { signal }); + } + return Promise.resolve(null); + }, + enabled: editMode && !(createServiceMutation.isPending || editServiceMutation.isPending), + staleTime: Infinity, + }); + + const metadataQuery = useQuery({ + queryKey: RQKeys.datastore_metadata_by_datasheet_name(datastoreId, datasheetName), + queryFn: ({ signal }) => api.metadata.getByDatasheetName(datastoreId, datasheetName, { signal }), + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const schemas = {}; + schemas[STEPS.METADATA_UPLOAD] = commonValidation.getMDUploadFileSchema(); + schemas[STEPS.METADATA_DESCRIPTION] = commonValidation.getMDDescriptionSchema( + existingLayerNamesQuery.data, + editMode, + offeringQuery.data?.configuration.layer_name + ); + schemas[STEPS.METADATA_ADDITIONALINFORMATIONS] = commonValidation.getMDAdditionalInfoSchema(); + schemas[STEPS.ACCESSRESTRICTIONS] = commonValidation.getAccessRestrictionSchema(); + + const defaultValues: ServiceFormValuesBaseType = useMemo( + () => getPyramidRasterWmsRasterServiceFormDefaultValues(offeringQuery.data, editMode, pyramidQuery.data, metadataQuery.data), + [editMode, offeringQuery.data, pyramidQuery.data, metadataQuery.data] + ); + + const form = useForm({ + resolver: yupResolver(schemas[currentStep]), + mode: "onChange", + values: defaultValues, + }); + + const { getValues: getFormValues, trigger } = form; + + useScrollToTopEffect(currentStep); + + const previousStep = useCallback(() => setCurrentStep((currentStep) => currentStep - 1), []); + + const nextStep = useCallback(async () => { + const isStepValid = await trigger(undefined, { shouldFocus: true }); // demande de valider le formulaire + + if (!isStepValid) return; // ne fait rien si formulaire invalide + + // formulaire est valide + if (currentStep < Object.values(STEPS).length) { + // on passe à la prochaine étape du formulaire + setCurrentStep((currentStep) => currentStep + 1); + } else { + // on est à la dernière étape du formulaire donc on envoie la sauce + if (editMode) { + editServiceMutation.mutate(); + } else { + createServiceMutation.mutate(); + } + } + }, [createServiceMutation, editServiceMutation, currentStep, trigger, editMode]); + + return ( + +

    {t("title", { editMode })}

    + + {pyramidQuery.isLoading || offeringQuery.isLoading || metadataQuery.isLoading ? ( + + ) : pyramidQuery.data === undefined ? ( + +

    {pyramidQuery.error?.message}

    + + + } + /> + ) : editMode === true && offeringQuery.data === undefined ? ( + +

    {offeringQuery.error?.message}

    + + + } + /> + ) : ( + <> + + {createServiceMutation.error && ( + + )} + {editServiceMutation.error && } + + + + + + + + + )} + + {(createServiceMutation.isPending || editServiceMutation.isPending) && ( + +
    +
    +
    + +
    +
    +
    {editMode ? t("modify.in_progress") : t("publish.in_progress")}
    +
    +
    +
    +
    + )} +
    + ); +}; + +PyramidRasterWmsRasterServiceForm.displayName = symToStr({ PyramidRasterWmsRasterServiceForm }); + +export default PyramidRasterWmsRasterServiceForm; + +export const { i18n } = declareComponentKeys< + | { K: "title"; P: { editMode: boolean }; R: string } + | "stored_data.loading" + | "stored_data_and_offering.loading" + | "stored_data.fetch_failed" + | "offering.fetch_failed" + | { K: "step.title"; P: { stepNumber: number }; R: string } + | "publish" + | "publish.in_progress" + | "modify.in_progress" + | "back_to_data_list" +>()({ + PyramidRasterWmsRasterServiceForm, +}); + +export const PyramidRasterWmsRasterServiceFormFrTranslations: Translations<"fr">["PyramidRasterWmsRasterServiceForm"] = { + title: ({ editMode }) => (editMode ? "Modifier le service WMS-Raster" : "Publier un service WMS-Raster"), + "stored_data.loading": "Chargement de la donnée stockée", + "stored_data_and_offering.loading": "Chargement de la donnée stockée et du service à modifier", + "stored_data.fetch_failed": "Récupération des informations sur la donnée stockée a échoué", + "offering.fetch_failed": "Récupération des informations sur le service à modifier a échoué", + "step.title": ({ stepNumber }) => { + switch (stepNumber) { + case 1: + return "Source des métadonnées"; + case 2: + return "Description de la ressource"; + case 3: + return "Informations supplémentaires"; + case 4: + return "Restrictions d’accès"; + + default: + return ""; + } + }, + publish: "Publier le service maintenant", + "publish.in_progress": "Création du service WMS-Raster en cours", + "modify.in_progress": "Modification des informations du service WMS-Raster en cours", + back_to_data_list: "Retour à mes données", +}; + +export const PyramidRasterWmsRasterServiceFormEnTranslations: Translations<"en">["PyramidRasterWmsRasterServiceForm"] = { + title: undefined, + "stored_data.loading": undefined, + "stored_data_and_offering.loading": undefined, + "stored_data.fetch_failed": undefined, + "offering.fetch_failed": undefined, + "step.title": undefined, + publish: undefined, + "publish.in_progress": undefined, + "modify.in_progress": undefined, + back_to_data_list: undefined, +}; diff --git a/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm.tsx b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm.tsx new file mode 100644 index 00000000..5db6c2f6 --- /dev/null +++ b/assets/entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm.tsx @@ -0,0 +1,320 @@ +import { fr } from "@codegouvfr/react-dsfr"; +import Alert from "@codegouvfr/react-dsfr/Alert"; +import Button from "@codegouvfr/react-dsfr/Button"; +import ButtonsGroup from "@codegouvfr/react-dsfr/ButtonsGroup"; +import Stepper from "@codegouvfr/react-dsfr/Stepper"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { declareComponentKeys } from "i18nifty"; +import { FC, useCallback, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; +import { symToStr } from "tsafe/symToStr"; + +import { ConfigurationTypeEnum, EndpointTypeEnum, PyramidRaster, Service, ServiceFormValuesBaseType } from "../../../../@types/app"; +import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; +import LoadingIcon from "../../../../components/Utils/LoadingIcon"; +import LoadingText from "../../../../components/Utils/LoadingText"; +import Wait from "../../../../components/Utils/Wait"; +import useScrollToTopEffect from "../../../../hooks/useScrollToTopEffect"; +import { Translations, useTranslation } from "../../../../i18n/i18n"; +import RQKeys from "../../../../modules/entrepot/RQKeys"; +import { CartesApiException } from "../../../../modules/jsonFetch"; +import { routes } from "../../../../router/router"; +import api from "../../../api"; +import AccessRestrictions from "../AccessRestrictions"; +import { CommonSchemasValidation } from "../common-schemas-validation"; +import { getPyramidRasterWmtsServiceFormDefaultValues } from "../default-values"; +import AdditionalInfo from "../metadatas/AdditionalInfo"; +import Description from "../metadatas/Description"; +import UploadMDFile from "../metadatas/UploadMDFile"; + +const STEPS = { + METADATA_UPLOAD: 1, + METADATA_DESCRIPTION: 2, + METADATA_ADDITIONALINFORMATIONS: 3, + ACCESSRESTRICTIONS: 4, +}; + +const commonValidation = new CommonSchemasValidation(); + +type PyramidRasterWmtsServiceFormProps = { + datastoreId: string; + pyramidId: string; + datasheetName: string; + offeringId?: string; +}; +const PyramidRasterWmtsServiceForm: FC = ({ datastoreId, pyramidId, datasheetName, offeringId }) => { + const { t } = useTranslation("PyramidRasterWmtsServiceForm"); + const { t: tCommon } = useTranslation("Common"); + + const editMode = useMemo(() => Boolean(offeringId), [offeringId]); + + const [currentStep, setCurrentStep] = useState(STEPS.METADATA_UPLOAD); + + const queryClient = useQueryClient(); + + const createServiceMutation = useMutation({ + mutationFn: () => { + const formValues = getFormValues(); + return api.pyramidRaster.publishWmsRasterWmts(datastoreId, pyramidId, ConfigurationTypeEnum.WMTSTMS, formValues); + }, + onSuccess() { + queryClient.invalidateQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) }); + routes.datastore_datasheet_view({ datastoreId, datasheetName: datasheetName, activeTab: "services" }).push(); + }, + }); + + const editServiceMutation = useMutation({ + mutationFn: () => { + if (offeringId === undefined) { + return Promise.reject(); + } + + const formValues = getFormValues(); + return api.pyramidRaster.editWmsRasterWmts(datastoreId, pyramidId, offeringId, ConfigurationTypeEnum.WMTSTMS, formValues); + }, + onSuccess() { + if (offeringId !== undefined) { + queryClient.removeQueries({ queryKey: RQKeys.datastore_offering(datastoreId, offeringId) }); + } + + queryClient.invalidateQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) }); + routes.datastore_datasheet_view({ datastoreId, datasheetName: datasheetName, activeTab: "services" }).push(); + queryClient.refetchQueries({ queryKey: RQKeys.datastore_metadata_by_datasheet_name(datastoreId, datasheetName) }); + }, + }); + + const pyramidQuery = useQuery({ + queryKey: RQKeys.datastore_stored_data(datastoreId, pyramidId), + queryFn: () => api.storedData.get(datastoreId, pyramidId), + staleTime: Infinity, + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const existingLayerNamesQuery = useQuery({ + queryKey: RQKeys.datastore_layernames_list(datastoreId, ConfigurationTypeEnum.WMTSTMS), + queryFn: ({ signal }) => api.service.getExistingLayerNames(datastoreId, ConfigurationTypeEnum.WMTSTMS, { signal }), + refetchInterval: 30000, + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const offeringQuery = useQuery({ + queryKey: RQKeys.datastore_offering(datastoreId, offeringId ?? "xxxx"), + queryFn: ({ signal }) => { + if (offeringId) { + return api.service.getService(datastoreId, offeringId, { signal }); + } + return Promise.resolve(null); + }, + enabled: editMode && !(createServiceMutation.isPending || editServiceMutation.isPending), + staleTime: Infinity, + }); + + const metadataQuery = useQuery({ + queryKey: RQKeys.datastore_metadata_by_datasheet_name(datastoreId, datasheetName), + queryFn: ({ signal }) => api.metadata.getByDatasheetName(datastoreId, datasheetName, { signal }), + enabled: !(createServiceMutation.isPending || editServiceMutation.isPending), + }); + + const schemas = {}; + schemas[STEPS.METADATA_UPLOAD] = commonValidation.getMDUploadFileSchema(); + schemas[STEPS.METADATA_DESCRIPTION] = commonValidation.getMDDescriptionSchema( + existingLayerNamesQuery.data, + editMode, + offeringQuery.data?.configuration.layer_name + ); + schemas[STEPS.METADATA_ADDITIONALINFORMATIONS] = commonValidation.getMDAdditionalInfoSchema(); + schemas[STEPS.ACCESSRESTRICTIONS] = commonValidation.getAccessRestrictionSchema(); + + const defaultValues: ServiceFormValuesBaseType = useMemo( + () => getPyramidRasterWmtsServiceFormDefaultValues(offeringQuery.data, editMode, pyramidQuery.data, metadataQuery.data), + [editMode, offeringQuery.data, pyramidQuery.data, metadataQuery.data] + ); + + const form = useForm({ + resolver: yupResolver(schemas[currentStep]), + mode: "onChange", + values: defaultValues, + }); + + const { getValues: getFormValues, trigger } = form; + + useScrollToTopEffect(currentStep); + + const previousStep = useCallback(() => setCurrentStep((currentStep) => currentStep - 1), []); + + const nextStep = useCallback(async () => { + const isStepValid = await trigger(undefined, { shouldFocus: true }); // demande de valider le formulaire + + if (!isStepValid) return; // ne fait rien si formulaire invalide + + // formulaire est valide + if (currentStep < Object.values(STEPS).length) { + // on passe à la prochaine étape du formulaire + setCurrentStep((currentStep) => currentStep + 1); + } else { + // on est à la dernière étape du formulaire donc on envoie la sauce + if (editMode) { + editServiceMutation.mutate(); + } else { + createServiceMutation.mutate(); + } + } + }, [createServiceMutation, editServiceMutation, currentStep, trigger, editMode]); + + return ( + +

    {t("title", { editMode })}

    + + {pyramidQuery.isLoading || offeringQuery.isLoading || metadataQuery.isLoading ? ( + + ) : pyramidQuery.data === undefined ? ( + +

    {pyramidQuery.error?.message}

    + + + } + /> + ) : editMode === true && offeringQuery.data === undefined ? ( + +

    {offeringQuery.error?.message}

    + + + } + /> + ) : ( + <> + + {createServiceMutation.error && ( + + )} + {editServiceMutation.error && } + + + + + + + + + )} + + {(createServiceMutation.isPending || editServiceMutation.isPending) && ( + +
    +
    +
    + +
    +
    +
    {editMode ? t("modify.in_progress") : t("publish.in_progress")}
    +
    +
    +
    +
    + )} +
    + ); +}; + +PyramidRasterWmtsServiceForm.displayName = symToStr({ PyramidRasterWmtsServiceForm }); + +export default PyramidRasterWmtsServiceForm; + +export const { i18n } = declareComponentKeys< + | { K: "title"; P: { editMode: boolean }; R: string } + | "stored_data.loading" + | "stored_data_and_offering.loading" + | "stored_data.fetch_failed" + | "offering.fetch_failed" + | { K: "step.title"; P: { stepNumber: number }; R: string } + | "publish" + | "publish.in_progress" + | "modify.in_progress" + | "back_to_data_list" +>()({ + PyramidRasterWmtsServiceForm, +}); + +export const PyramidRasterWmtsServiceFormFrTranslations: Translations<"fr">["PyramidRasterWmtsServiceForm"] = { + title: ({ editMode }) => (editMode ? "Modifier le service WMTS" : "Publier un service WMTS"), + "stored_data.loading": "Chargement de la donnée stockée", + "stored_data_and_offering.loading": "Chargement de la donnée stockée et du service à modifier", + "stored_data.fetch_failed": "Récupération des informations sur la donnée stockée a échoué", + "offering.fetch_failed": "Récupération des informations sur le service à modifier a échoué", + "step.title": ({ stepNumber }) => { + switch (stepNumber) { + case 1: + return "Source des métadonnées"; + case 2: + return "Description de la ressource"; + case 3: + return "Informations supplémentaires"; + case 4: + return "Restrictions d’accès"; + + default: + return ""; + } + }, + publish: "Publier le service maintenant", + "publish.in_progress": "Création du service WMTS en cours", + "modify.in_progress": "Modification des informations du service WMTS en cours", + back_to_data_list: "Retour à mes données", +}; + +export const PyramidRasterWmtsServiceFormEnTranslations: Translations<"en">["PyramidRasterWmtsServiceForm"] = { + title: undefined, + "stored_data.loading": undefined, + "stored_data_and_offering.loading": undefined, + "stored_data.fetch_failed": undefined, + "offering.fetch_failed": undefined, + "step.title": undefined, + publish: undefined, + "publish.in_progress": undefined, + "modify.in_progress": undefined, + back_to_data_list: undefined, +}; diff --git a/assets/entrepot/pages/service/wms-vector/WmsVectorServiceForm.tsx b/assets/entrepot/pages/service/wms-vector/WmsVectorServiceForm.tsx index dbd5ea6b..fb7504e5 100644 --- a/assets/entrepot/pages/service/wms-vector/WmsVectorServiceForm.tsx +++ b/assets/entrepot/pages/service/wms-vector/WmsVectorServiceForm.tsx @@ -301,7 +301,7 @@ const WmsVectorServiceForm: FC = ({ datastoreId, vect

    {t("title", { editMode })}

    - {vectorDbQuery.isLoading || offeringQuery.isLoading ? ( + {vectorDbQuery.isLoading || offeringQuery.isLoading || metadataQuery.isLoading ? ( ) : vectorDbQuery.data === undefined ? ( = ({ datastoreId, reportQuery }) => { const storedData = useMemo(() => reportQuery.data?.stored_data, [reportQuery.data?.stored_data]); const vectorDbQuery = useQuery({ - queryKey: RQKeys.datastore_stored_data(datastoreId, (storedData as Pyramid).tags.vectordb_id!), - queryFn: ({ signal }) => api.storedData.get(datastoreId, (storedData as Pyramid).tags.vectordb_id!, { signal }), - enabled: !!(storedData as Pyramid).tags.vectordb_id, + queryKey: RQKeys.datastore_stored_data(datastoreId, (storedData as PyramidVector).tags.vectordb_id!), + queryFn: ({ signal }) => api.storedData.get(datastoreId, (storedData as PyramidVector).tags.vectordb_id!, { signal }), + enabled: !!(storedData as PyramidVector).tags.vectordb_id, staleTime: 3600000, }); diff --git a/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/Logs.tsx b/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/Logs.tsx index ef6df712..a11d4989 100644 --- a/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/Logs.tsx +++ b/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/Logs.tsx @@ -7,7 +7,7 @@ type LogsProps = { const Logs: FC = ({ logs }) => { return ( <> - Journal : + Journal : {logs === undefined ? ( "Impossible de récupérer les journaux" ) : logs.length > 0 ? ( diff --git a/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/ReportTab.tsx b/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/ReportTab.tsx index 11f2f809..a0256622 100644 --- a/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/ReportTab.tsx +++ b/assets/entrepot/pages/stored_data/StoredDataDetails/ReportTab/ReportTab.tsx @@ -78,6 +78,7 @@ const ReportTab: FC = ({ datastoreName, reportQuery }) => { defaultExpanded={[ ProcessingExecutionDetailResponseDtoStatusEnum.FAILURE, ProcessingExecutionDetailResponseDtoStatusEnum.ABORTED, + ProcessingExecutionDetailResponseDtoStatusEnum.PROGRESS, ].includes(procExec.status)} > diff --git a/assets/entrepot/pages/stored_data/StoredDataDetails/StoredDataDetails.tsx b/assets/entrepot/pages/stored_data/StoredDataDetails/StoredDataDetails.tsx index 66557a79..b37b5f69 100644 --- a/assets/entrepot/pages/stored_data/StoredDataDetails/StoredDataDetails.tsx +++ b/assets/entrepot/pages/stored_data/StoredDataDetails/StoredDataDetails.tsx @@ -3,9 +3,9 @@ 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 { FC, useEffect, useMemo, useState } from "react"; -import { Datastore } from "../../../../@types/app"; +import { Datastore, StoredDataStatusEnum } from "../../../../@types/app"; import DatastoreLayout from "../../../../components/Layout/DatastoreLayout"; import LoadingIcon from "../../../../components/Utils/LoadingIcon"; import RQKeys from "../../../../modules/entrepot/RQKeys"; @@ -20,6 +20,8 @@ type StoredDataDetailsProps = { storedDataId: string; }; const StoredDataDetails: FC = ({ datastoreId, storedDataId }) => { + const [reportQueryEnabled, setReportQueryEnabled] = useState(true); + const datastoreQuery = useQuery({ queryKey: RQKeys.datastore(datastoreId), queryFn: ({ signal }) => api.datastore.get(datastoreId, { signal }), @@ -29,9 +31,19 @@ const StoredDataDetails: FC = ({ datastoreId, storedData const reportQuery = useQuery({ queryKey: RQKeys.datastore_stored_data_report(datastoreId, storedDataId), queryFn: ({ signal }) => api.storedData.getReportData(datastoreId, storedDataId, { signal }), - staleTime: 3600000, + refetchInterval: 30000, + enabled: reportQueryEnabled, }); + useEffect(() => { + if ( + reportQuery.data?.stored_data.status !== undefined && + [StoredDataStatusEnum.DELETED, StoredDataStatusEnum.GENERATED, StoredDataStatusEnum.UNSTABLE].includes(reportQuery.data?.stored_data.status) + ) { + setReportQueryEnabled(false); + } + }, [reportQuery.data?.stored_data.status]); + const datasheetName = useMemo(() => reportQuery?.data?.stored_data?.tags?.datasheet_name, [reportQuery?.data?.stored_data?.tags?.datasheet_name]); return ( @@ -81,6 +93,7 @@ const StoredDataDetails: FC = ({ datastoreId, storedData { label: "Rapport de génération", content: , + isDefault: true, }, ]} /> diff --git a/assets/i18n/Breadcrumb.tsx b/assets/i18n/Breadcrumb.tsx index e9f133a4..aee075c8 100644 --- a/assets/i18n/Breadcrumb.tsx +++ b/assets/i18n/Breadcrumb.tsx @@ -39,6 +39,11 @@ export const { i18n } = declareComponentKeys< | "datastore_pyramid_vector_generate" | "datastore_pyramid_vector_tms_service_new" | "datastore_pyramid_vector_tms_service_edit" + | "datastore_pyramid_raster_generate" + | "datastore_pyramid_raster_wms_raster_service_new" + | "datastore_pyramid_raster_wms_raster_service_edit" + | "datastore_pyramid_raster_wmts_service_new" + | "datastore_pyramid_raster_wmts_service_edit" | "datastore_service_view" >()("Breadcrumb"); @@ -76,11 +81,16 @@ export const BreadcrumbFrTranslations: Translations<"fr">["Breadcrumb"] = { datastore_stored_data_details: "Détails d'une donnée stockée", datastore_wfs_service_new: "Création d'un service WFS", datastore_wfs_service_edit: "Modification d'un service WFS", - datastore_wms_vector_service_new: "Création d'un service WMS", - datastore_wms_vector_service_edit: "Modification d'un service WMS", + datastore_wms_vector_service_new: "Création d'un service WMS-Vecteur", + datastore_wms_vector_service_edit: "Modification d'un service WMS-Vecteur", datastore_pyramid_vector_generate: "Génération d'une pyramide vecteur", datastore_pyramid_vector_tms_service_new: "Création d'un service TMS", datastore_pyramid_vector_tms_service_edit: "Modification d'un service TMS", + datastore_pyramid_raster_generate: "Génération d'une pyramide raster", + datastore_pyramid_raster_wms_raster_service_new: "Création d'un service WMS-Raster", + datastore_pyramid_raster_wms_raster_service_edit: "Modification d'un service WMS-Raster", + datastore_pyramid_raster_wmts_service_new: "Création d'un service WMTS", + datastore_pyramid_raster_wmts_service_edit: "Modification d'un service WMTS", datastore_service_view: "Prévisualisation d'un service", }; @@ -118,10 +128,15 @@ export const BreadcrumbEnTranslations: Translations<"en">["Breadcrumb"] = { datastore_stored_data_details: "Details of stored data", datastore_wfs_service_new: "Create a WFS service", datastore_wfs_service_edit: "Modify WFS service", - datastore_wms_vector_service_new: "Create a WMS service", - datastore_wms_vector_service_edit: "Modify a WMS service", + datastore_wms_vector_service_new: "Create a WMS-Vector service", + datastore_wms_vector_service_edit: "Modify a WMS-Vector service", datastore_pyramid_vector_generate: "Generate a vector pyramid", datastore_pyramid_vector_tms_service_new: "Create a TMS service", datastore_pyramid_vector_tms_service_edit: "Modify a TMS service", + datastore_pyramid_raster_generate: "Generate raster pyramid", + datastore_pyramid_raster_wms_raster_service_new: "Create a WMS-Raster service", + datastore_pyramid_raster_wms_raster_service_edit: "Modify a WMS-Raster service", + datastore_pyramid_raster_wmts_service_new: "Create a WMTS service", + datastore_pyramid_raster_wmts_service_edit: "Modify a WMTS service", datastore_service_view: "Preview a service", }; diff --git a/assets/i18n/i18n.ts b/assets/i18n/i18n.ts index 25c9af7d..0ea494be 100644 --- a/assets/i18n/i18n.ts +++ b/assets/i18n/i18n.ts @@ -37,7 +37,8 @@ export type ComponentKey = | typeof import("../entrepot/pages/datasheet/DatasheetNew/DatasheetUploadForm").i18n | typeof import("../entrepot/pages/datasheet/DatasheetList/DatasheetList").i18n | typeof import("../entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbListItem").i18n - | typeof import("../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem").i18n + | typeof import("../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem").i18n + | typeof import("../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem").i18n | typeof import("../entrepot/pages/datasheet/DatasheetView/DatasheetView").i18n | typeof import("../config/navItems").i18n | typeof import("../config/datastoreNavItems").i18n @@ -47,6 +48,9 @@ export type ComponentKey = | typeof import("../entrepot/pages/service/wms-vector/WmsVectorServiceForm").i18n | typeof import("../entrepot/pages/service/wfs/WfsServiceForm").i18n | typeof import("../entrepot/pages/service/tms/PyramidVectorTmsServiceForm").i18n + | typeof import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm").i18n + | typeof import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm").i18n + | typeof import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm").i18n | typeof import("../entrepot/pages/service/TableSelection").i18n | typeof import("../entrepot/pages/service/AccessRestrictions").i18n | typeof import("../entrepot/pages/service/wms-vector/UploadStyleFile").i18n diff --git a/assets/i18n/languages/en.tsx b/assets/i18n/languages/en.tsx index aedf8782..9b3d56af 100644 --- a/assets/i18n/languages/en.tsx +++ b/assets/i18n/languages/en.tsx @@ -6,7 +6,8 @@ import { CommunityMembersEnTranslations } from "../../entrepot/pages/communities import { contactEnTranslations } from "../../pages/assistance/contact/Contact"; import { DashboardProEnTranslations } from "../../entrepot/pages/dashboard/DashboardPro"; import { DatasheetListEnTranslations } from "../../entrepot/pages/datasheet/DatasheetList/DatasheetList"; -import { PyramidListItemFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem"; +import { PyramidVectorListItemEnTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem"; +import { PyramidRasterListItemEnTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem"; import { VectorDbListItemEnTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbListItem"; import { DatasheetViewEnTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasheetView"; import { DatastorePermissionsEnTranslations } from "../../entrepot/pages/datastore/ManagePermissions/DatastorePermissionsTr"; @@ -35,6 +36,9 @@ import { StyleEnTranslations } from "../Style"; import type { Translations } from "../i18n"; import { DatasheetUploadFormEnTranslations } from "../../entrepot/pages/datasheet/DatasheetNew/DatasheetUploadForm"; import { LoginDisabledEnTranslations } from "../../pages/LoginDisabled"; +import { PyramidRasterGenerateFormEnTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm"; +import { PyramidRasterWmsRasterServiceFormEnTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm"; +import { PyramidRasterWmtsServiceFormEnTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm"; export const translations: Translations<"en"> = { Common: commonEnTranslations, @@ -56,7 +60,8 @@ export const translations: Translations<"en"> = { navItems: navItemsEnTranslations, datastoreNavItems: datastoreNavItemsEnTranslations, VectorDbListItem: VectorDbListItemEnTranslations, - PyramidListItem: PyramidListItemFrTranslations, + PyramidVectorListItem: PyramidVectorListItemEnTranslations, + PyramidRasterListItem: PyramidRasterListItemEnTranslations, DatasheetView: DatasheetViewEnTranslations, SldStyleValidationErrors: SldStyleValidationErrorsEnTranslations, mapboxStyleValidation: mapboxStyleValidationEnTranslations, @@ -68,6 +73,9 @@ export const translations: Translations<"en"> = { TableSelection: TableSelectionEnTranslations, UploadStyleFile: UploadStyleFileEnTranslations, PyramidVectorTmsServiceForm: PyramidVectorTmsServiceFormEnTranslations, + PyramidRasterGenerateForm: PyramidRasterGenerateFormEnTranslations, + PyramidRasterWmsRasterServiceForm: PyramidRasterWmsRasterServiceFormEnTranslations, + PyramidRasterWmtsServiceForm: PyramidRasterWmtsServiceFormEnTranslations, EspaceCoCommunities: EspaceCoCommunitiesEnTranslations, DatasheetUploadForm: DatasheetUploadFormEnTranslations, DatasheetList: DatasheetListEnTranslations, diff --git a/assets/i18n/languages/fr.tsx b/assets/i18n/languages/fr.tsx index bc727cf4..b04b0229 100644 --- a/assets/i18n/languages/fr.tsx +++ b/assets/i18n/languages/fr.tsx @@ -6,7 +6,8 @@ import { CommunityMembersFrTranslations } from "../../entrepot/pages/communities import { DashboardProFrTranslations } from "../../entrepot/pages/dashboard/DashboardPro"; import { DatasheetListFrTranslations } from "../../entrepot/pages/datasheet/DatasheetList/DatasheetList"; import { DatasheetUploadFormFrTranslations } from "../../entrepot/pages/datasheet/DatasheetNew/DatasheetUploadForm"; -import { PyramidListItemFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidList/PyramidListItem"; +import { PyramidVectorListItemFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidVectorList/PyramidVectorListItem"; +import { PyramidRasterListItemFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/PyramidRasterList/PyramidRasterListItem"; import { VectorDbListItemFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasetListTab/VectorDbList/VectorDbListItem"; import { DatasheetViewFrTranslations } from "../../entrepot/pages/datasheet/DatasheetView/DatasheetView"; import { DatastorePermissionsFrTranslations } from "../../entrepot/pages/datastore/ManagePermissions/DatastorePermissionsTr"; @@ -35,6 +36,9 @@ import { RightsFrTranslations } from "../Rights"; import { StyleFrTranslations } from "../Style"; import type { Translations } from "../i18n"; import { LoginDisabledFrTranslations } from "../../pages/LoginDisabled"; +import { PyramidRasterGenerateFormFrTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm"; +import { PyramidRasterWmsRasterServiceFormFrTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm"; +import { PyramidRasterWmtsServiceFormFrTranslations } from "../../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm"; export const translations: Translations<"fr"> = { Common: commonFrTranslations, @@ -56,7 +60,8 @@ export const translations: Translations<"fr"> = { navItems: navItemsFrTranslations, datastoreNavItems: datastoreNavItemsFrTranslations, VectorDbListItem: VectorDbListItemFrTranslations, - PyramidListItem: PyramidListItemFrTranslations, + PyramidVectorListItem: PyramidVectorListItemFrTranslations, + PyramidRasterListItem: PyramidRasterListItemFrTranslations, DatasheetView: DatasheetViewFrTranslations, SldStyleValidationErrors: SldStyleValidationErrorsFrTranslations, mapboxStyleValidation: mapboxStyleValidationFrTranslations, @@ -68,6 +73,9 @@ export const translations: Translations<"fr"> = { TableSelection: TableSelectionFrTranslations, UploadStyleFile: UploadStyleFileFrTranslations, PyramidVectorTmsServiceForm: PyramidVectorTmsServiceFormFrTranslations, + PyramidRasterGenerateForm: PyramidRasterGenerateFormFrTranslations, + PyramidRasterWmsRasterServiceForm: PyramidRasterWmsRasterServiceFormFrTranslations, + PyramidRasterWmtsServiceForm: PyramidRasterWmtsServiceFormFrTranslations, EspaceCoCommunities: EspaceCoCommunitiesFrTranslations, DatasheetUploadForm: DatasheetUploadFormFrTranslations, DatasheetList: DatasheetListFrTranslations, diff --git a/assets/img/zoom-range/map-zoom-bottom.png b/assets/img/zoom-range/map-zoom-bottom.png new file mode 100644 index 00000000..56882e1d Binary files /dev/null and b/assets/img/zoom-range/map-zoom-bottom.png differ diff --git a/assets/img/zoom-range/map-zoom-top.png b/assets/img/zoom-range/map-zoom-top.png new file mode 100644 index 00000000..2e72fe1d Binary files /dev/null and b/assets/img/zoom-range/map-zoom-top.png differ diff --git a/assets/main.tsx b/assets/main.tsx index b7c6590b..3fee78ab 100644 --- a/assets/main.tsx +++ b/assets/main.tsx @@ -2,12 +2,14 @@ import { startReactDsfr } from "@codegouvfr/react-dsfr/spa"; import { disableReactDevTools } from "@fvilers/disable-react-devtools"; import React from "react"; import ReactDOM from "react-dom/client"; -import { mountStoreDevtool } from "simple-zustand-devtools"; +// import { mountStoreDevtool } from "simple-zustand-devtools"; import App from "./App"; -import { useApiEspaceCoStore } from "./stores/ApiEspaceCoStore"; -import { useAuthStore } from "./stores/AuthStore"; -import { useSnackbarStore } from "./stores/SnackbarStore"; +// import { useApiEspaceCoStore } from "./stores/ApiEspaceCoStore"; +// import { useAuthStore } from "./stores/AuthStore"; +// import { useSnackbarStore } from "./stores/SnackbarStore"; + +import "ol/ol.css"; // en prod if ((document.getElementById("root") as HTMLDivElement)?.dataset?.appEnv?.toLowerCase() === "prod") { @@ -15,9 +17,9 @@ if ((document.getElementById("root") as HTMLDivElement)?.dataset?.appEnv?.toLowe } // en dev/qualif else { - mountStoreDevtool("AuthStore", useAuthStore); - mountStoreDevtool("ApiEspaceCoStore", useApiEspaceCoStore); - mountStoreDevtool("SnackbarStore", useSnackbarStore); + // mountStoreDevtool("AuthStore", useAuthStore); + // mountStoreDevtool("ApiEspaceCoStore", useApiEspaceCoStore); + // mountStoreDevtool("SnackbarStore", useSnackbarStore); } startReactDsfr({ defaultColorScheme: "light" }); diff --git a/assets/modules/WebServices/WMSVectorService.ts b/assets/modules/WebServices/WMSService.ts similarity index 96% rename from assets/modules/WebServices/WMSVectorService.ts rename to assets/modules/WebServices/WMSService.ts index 77935ce5..b5da00c0 100644 --- a/assets/modules/WebServices/WMSVectorService.ts +++ b/assets/modules/WebServices/WMSService.ts @@ -6,7 +6,8 @@ import TileLayer from "ol/layer/Tile"; import TileWMSSource from "ol/source/TileWMS.js"; import BaseService from "./BaseService"; import { getRequestInfo } from "../../utils"; -class WMSVectorService extends BaseService { + +class WMSService extends BaseService { #requestInfo: Record; #parser: WMSCapabilities; @@ -64,4 +65,4 @@ class WMSVectorService extends BaseService { } } -export default WMSVectorService; +export default WMSService; diff --git a/assets/modules/WebServices/WMTSService.ts b/assets/modules/WebServices/WMTSService.ts new file mode 100644 index 00000000..055da987 --- /dev/null +++ b/assets/modules/WebServices/WMTSService.ts @@ -0,0 +1,66 @@ +import TileLayer from "ol/layer/Tile"; +import BaseService from "./BaseService"; +import WMTS, { optionsFromCapabilities } from "ol/source/WMTS"; +import { getRequestInfo } from "../../utils"; +import WMTSCapabilities from "ol/format/WMTSCapabilities"; + +class WMTSService extends BaseService { + getLayerNames(): string[] { + return [this.service.layer_name]; + } + + async getLayers() { + // On ne conserve que l'URL du WMTS + const wmtsUrl = this.service.urls.find((descUrl) => { + return descUrl.type === "WMTS"; + }); + if (wmtsUrl?.url === undefined) { + throw new Error("L'URL du flux WMTS n'a pas été trouvée"); + } + + const capabilities = await this.#getCapabilities(wmtsUrl.url); + + const wmtsOptions = optionsFromCapabilities(capabilities, { + layer: this.service.layer_name, + }); + + if (!wmtsOptions) return []; + const capsLayer = capabilities.Contents.Layer.find((layer) => layer.Identifier === this.service.layer_name) ?? null; + + const wmtsSource = new WMTS({ + ...wmtsOptions, + attributions: this.getAttribution(), + }); + if (capsLayer) { + wmtsSource.setProperties({ + name: capsLayer.Identifier ?? null, + title: capsLayer.Title ?? null, + abstract: capsLayer.abstract ?? null, + }); + } + const wmtsLayer = new TileLayer({ + source: wmtsSource, + }); + return [wmtsLayer]; + } + + async #getCapabilities(wmtsUrl: string) { + const requestInfo = getRequestInfo(wmtsUrl); + + const sp = new URLSearchParams(); + sp.append("service", requestInfo.service); + sp.append("request", "GetCapabilities"); + sp.append("version", requestInfo.version); + const getCapParams = sp.toString(); + + const getCapUrl = `${requestInfo.base_url}?${getCapParams}`; + + const format = new WMTSCapabilities(); + const response = await fetch(getCapUrl); + const text = await response.text(); + + return format.read(text); + } +} + +export default WMTSService; diff --git a/assets/modules/WebServices/WebServices.ts b/assets/modules/WebServices/WebServices.ts index be840ad1..7c011917 100644 --- a/assets/modules/WebServices/WebServices.ts +++ b/assets/modules/WebServices/WebServices.ts @@ -1,8 +1,9 @@ -import WFSService from "./WFSService"; -import WMSVectorService from "./WMSVectorService"; -import TMSService from "./TMSService"; +import { StoredDataTypeEnum, type Service } from "../../@types/app"; import { OfferingDetailResponseDtoTypeEnum } from "../../@types/entrepot"; -import { type Service } from "../../@types/app"; +import TMSService from "./TMSService"; +import WFSService from "./WFSService"; +import WMSService from "./WMSService"; +import WMTSService from "./WMTSService"; const getWebService = (service: Service) => { switch (service.type) { @@ -10,13 +11,21 @@ const getWebService = (service: Service) => { return new WFSService(service); } case OfferingDetailResponseDtoTypeEnum.WMTSTMS: { - return new TMSService(service); + switch (service.configuration.pyramid?.type) { + case StoredDataTypeEnum.ROK4PYRAMIDRASTER: + return new WMTSService(service); + case StoredDataTypeEnum.ROK4PYRAMIDVECTOR: + return new TMSService(service); + default: + throw Error(`L'affichage du flux du type ${service.type} n'est pas encore implémenté`); + } } - case OfferingDetailResponseDtoTypeEnum.WMSVECTOR: { - return new WMSVectorService(service); + case OfferingDetailResponseDtoTypeEnum.WMSVECTOR: + case OfferingDetailResponseDtoTypeEnum.WMSRASTER: { + return new WMSService(service); } default: - throw Error(`Service ${service.type} is not implemented`); + throw Error(`L'affichage du flux du type ${service.type} n'est pas encore implémenté`); } }; diff --git a/assets/modules/entrepot/breadcrumbs.ts b/assets/modules/entrepot/breadcrumbs.ts index acd87397..2ea3191a 100644 --- a/assets/modules/entrepot/breadcrumbs.ts +++ b/assets/modules/entrepot/breadcrumbs.ts @@ -169,6 +169,11 @@ const getBreadcrumb = (route: Route, datastore?: Datastore): Brea case "datastore_pyramid_vector_generate": case "datastore_pyramid_vector_tms_service_new": case "datastore_pyramid_vector_tms_service_edit": + case "datastore_pyramid_raster_generate": + case "datastore_pyramid_raster_wms_raster_service_new": + case "datastore_pyramid_raster_wms_raster_service_edit": + case "datastore_pyramid_raster_wmts_service_new": + case "datastore_pyramid_raster_wmts_service_edit": case "datastore_service_view": defaultProps.segments = [ ...defaultProps.segments, diff --git a/assets/router/RouterRenderer.tsx b/assets/router/RouterRenderer.tsx index 5fd209aa..1b75fe01 100644 --- a/assets/router/RouterRenderer.tsx +++ b/assets/router/RouterRenderer.tsx @@ -54,6 +54,9 @@ const WfsServiceForm = lazy(() => import("../entrepot/pages/service/wfs/WfsServi const WmsVectorServiceForm = lazy(() => import("../entrepot/pages/service/wms-vector/WmsVectorServiceForm")); const PyramidVectorGenerateForm = lazy(() => import("../entrepot/pages/service/tms/PyramidVectorGenerateForm")); const PyramidVectorTmsServiceForm = lazy(() => import("../entrepot/pages/service/tms/PyramidVectorTmsServiceForm")); +const PyramidRasterGenerateForm = lazy(() => import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterGenerateForm")); +const PyramidRasterWmsRasterServiceForm = lazy(() => import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmsRasterServiceForm")); +const PyramidRasterWmtsServiceForm = lazy(() => import("../entrepot/pages/service/wms-raster-wmts/PyramidRasterWmtsServiceForm")); const ServiceView = lazy(() => import("../entrepot/pages/service/view/ServiceView")); @@ -81,6 +84,8 @@ const RouterRenderer: FC = () => { switch (route.name) { case "home": return ; + case "page_not_found": + return ; case "about": return ; case "documentation": @@ -185,6 +190,48 @@ const RouterRenderer: FC = () => { offeringId={route.params.offeringId} /> ); + case "datastore_pyramid_raster_generate": + return ( + + ); + case "datastore_pyramid_raster_wms_raster_service_new": + return ( + + ); + case "datastore_pyramid_raster_wms_raster_service_edit": + return ( + + ); + case "datastore_pyramid_raster_wmts_service_new": + return ( + + ); + case "datastore_pyramid_raster_wmts_service_edit": + return ( + + ); case "datastore_service_view": return ; case "espaceco_community_list": diff --git a/assets/router/router.ts b/assets/router/router.ts index 57280d63..6af82880 100644 --- a/assets/router/router.ts +++ b/assets/router/router.ts @@ -14,6 +14,7 @@ const routeDefs = { }, () => (appRoot === "" ? "/" : appRoot) ), + page_not_found: defineRoute(`${appRoot}/404`), about: defineRoute(`${appRoot}/a-propos`), documentation: defineRoute(`${appRoot}/documentation`), contact: defineRoute(`${appRoot}/nous-ecrire`), @@ -213,6 +214,50 @@ const routeDefs = { (p) => `${appRoot}/entrepot/${p.datastoreId}/service/tms/${p.offeringId}/modification` ), + // Création/génération d'une pyramide raster + datastore_pyramid_raster_generate: defineRoute( + { + datastoreId: param.path.string, + offeringId: param.query.string, + datasheetName: param.query.string, + }, + (p) => `${appRoot}/entrepot/${p.datastoreId}/pyramide-raster/ajout` + ), + datastore_pyramid_raster_wms_raster_service_new: defineRoute( + { + datastoreId: param.path.string, + pyramidId: param.query.string, + datasheetName: param.query.string, + }, + (p) => `${appRoot}/entrepot/${p.datastoreId}/service/wms-raster/ajout` + ), + datastore_pyramid_raster_wms_raster_service_edit: defineRoute( + { + datastoreId: param.path.string, + pyramidId: param.query.string, + offeringId: param.path.string, + datasheetName: param.query.string, + }, + (p) => `${appRoot}/entrepot/${p.datastoreId}/service/wms-raster/${p.offeringId}/modification` + ), + datastore_pyramid_raster_wmts_service_new: defineRoute( + { + datastoreId: param.path.string, + pyramidId: param.query.string, + datasheetName: param.query.string, + }, + (p) => `${appRoot}/entrepot/${p.datastoreId}/service/wmts/ajout` + ), + datastore_pyramid_raster_wmts_service_edit: defineRoute( + { + datastoreId: param.path.string, + pyramidId: param.query.string, + offeringId: param.path.string, + datasheetName: param.query.string, + }, + (p) => `${appRoot}/entrepot/${p.datastoreId}/service/wmts/${p.offeringId}/modification` + ), + datastore_service_view: defineRoute( { datastoreId: param.path.string, diff --git a/assets/sass/components/zoom-range.scss b/assets/sass/components/zoom-range.scss deleted file mode 100644 index 13dbdb14..00000000 --- a/assets/sass/components/zoom-range.scss +++ /dev/null @@ -1,17 +0,0 @@ -.zoom-range-map { - height: 300px; -} - -.ui-map-zoom-levels { - display: flex; - .ui-top-zoom-level, - .ui-bottom-zoom-level { - height: 300px; - border: 1px solid lightgray; - flex: 0 1 50%; - margin: 0 1em; - } - .ui-bottom-zoom-level { - margin-left: 0; - } -} diff --git a/assets/utils.ts b/assets/utils.ts index 55f6babc..37cd9767 100644 --- a/assets/utils.ts +++ b/assets/utils.ts @@ -11,6 +11,7 @@ import charsets from "./data/charset_list.json"; // https://github.com/haliaeetus/iso-639/blob/master/data/iso_639-2.json import langs from "./data/iso_639-2.json"; import { OfferingTypeEnum } from "./@types/app"; +import { BoundingBox } from "./@types/entrepot"; export type LanguageType = { language: string; @@ -222,6 +223,14 @@ const trimObject = (obj: object): object => { return newObject; }; +const bboxToWkt = (bbox: BoundingBox) => { + const str = "POLYGON((west north,east north,east south,west south,west north))"; + + return str.replace(/[a-z]+/g, function (s) { + return bbox[s]; + }); +}; + export { getInspireKeywords, getLanguages, @@ -238,4 +247,5 @@ export { formatDateWithoutTimeFromISO, getArrayRange, trimObject, + bboxToWkt, }; diff --git a/config/parameters.yaml b/config/parameters.yaml index 01c92f8c..5c8bf641 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -20,6 +20,7 @@ parameters: processings: int_vect_files_db: "%env(resolve:SANDBOX_PROC_INT_VECT_FILES_DB)%" create_vect_pyr: "%env(resolve:SANDBOX_PROC_CREATE_VECT_PYR)%" + create_rast_pyr: "%env(resolve:SANDBOX_PROC_CREATE_RAST_PYR)%" api_entrepot_url: "%env(resolve:API_ENTREPOT_URL)%" annexes_url: "%env(resolve:ANNEXES_URL)%" @@ -40,6 +41,7 @@ parameters: processings: int_vect_files_db: "%env(resolve:API_ENTREPOT_PROC_INT_VECT_FILES_DB)%" create_vect_pyr: "%env(resolve:API_ENTREPOT_PROC_CREATE_VECT_PYR)%" + create_rast_pyr: "%env(resolve:API_ENTREPOT_PROC_CREATE_RAST_PYR)%" api_espaceco_url: "%env(resolve:API_ESPACE_COLLABORATIF_URL)%" catalogue_url: "%env(resolve:CATALOGUE_URL)%" diff --git a/processing-pyramid-raster.json b/processing-pyramid-raster.json new file mode 100644 index 00000000..c394fd0f --- /dev/null +++ b/processing-pyramid-raster.json @@ -0,0 +1,182 @@ +{ + "name": "Calcul ou mise à jour de pyramide raster par moissonnage WMS", + "description": "Il n'y a pas besoin de donnée en entrée. Sont fournis en paramètres toutes les informations sur le service WMS et le jeu de données à moissonner, ainsi que la zone sur laquelle faire le moissonnage", + "input_types": { + "upload": [], + "stored_data": ["ROK4-PYRAMID-RASTER"] + }, + "output_type": { + "stored_data": "ROK4-PYRAMID-RASTER", + "storage": ["S3"] + }, + "parameters": [ + { + "name": "harvest_layers", + "description": "Couches à moisonner (séparées par des virgules)", + "mandatory": true, + "constraints": { + "type": "string" + } + }, + { + "name": "top", + "description": "Le niveau du haut de la pyramide en sortie ", + "mandatory": false, + "constraints": { + "type": "string" + } + }, + { + "name": "harvest_dimensions", + "description": "Deux entiers positifs, dimensions pixel maximales de moisonnage, devra être un diviseur de la taille pixel des dalles", + "mandatory": false, + "constraints": { + "type": "array", + "items": { + "type": "integer" + }, + "maxItems": 2, + "minItems": 2 + } + }, + { + "name": "compression", + "description": "La compression des données en sortie (valeurs possibles: raw, jpg, png, zip, jpg90)", + "mandatory": false, + "constraints": { + "enum": ["raw", "jpg", "png", "zip", "jpg90"], + "type": "string" + } + }, + { + "name": "samplesperpixel", + "description": "Nombre de canaux dans les dalles en sortie (entier de 1 à 4)", + "mandatory": false, + "constraints": { + "type": "integer", + "maximum": 4, + "minimum": 1 + } + }, + { + "name": "parallelization", + "description": "Le niveau de parallélisation du calcul (défaut à 1, entier >= 1)", + "mandatory": false, + "default_value": 1 + }, + { + "name": "tms", + "description": "L identifiant du quadrillage à utiliser (Tile Matrix Set)", + "mandatory": false, + "constraints": { + "enum": ["PM"], + "type": "string" + } + }, + { + "name": "height", + "description": "Le nombre de tuile par dalle en hauteur (entier >= 1)", + "mandatory": false, + "default_value": 16 + }, + { + "name": "harvest_levels", + "description": "Identifiants des niveaux pour lesquels on moissonne les dalles (celui le plus bas sera le niveau du bas de la pyramide). On considère que les niveaux sont précisés de bas en haut.", + "mandatory": true, + "constraints": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "sampleformat", + "description": "Format des canaux dans les dalles en sortie (UINT8 ou FLOAT32)", + "mandatory": false, + "constraints": { + "enum": ["UINT8", "FLOAT32"], + "type": "string" + } + }, + { + "name": "harvest_threshold", + "description": "Taille minimale en octet des dalles moissonnées", + "mandatory": false, + "constraints": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "harvest_extras", + "description": "Paramètres de requêtes GetMap additionnels, hors layers, bbox, format et srs", + "mandatory": false, + "constraints": { + "type": "string" + } + }, + { + "name": "width", + "description": "Le nombre de tuile par dalle en largeur (entier >= 1)", + "mandatory": false, + "default_value": 16 + }, + { + "name": "harvest_area", + "description": "WKT de la zone sur laquelle le moissonnage doit se faire, en EPSG:4326", + "mandatory": true, + "constraints": { + "type": "string" + } + }, + { + "name": "harvest_format", + "description": "Format des images téléchargées", + "mandatory": true, + "constraints": { + "enum": [ + "image/png", + "image/tiff", + "image/jpeg", + "image/x-bil;bits=32", + "image/tiff&format_options=compression:deflate", + "image/tiff&format_options=compression:lzw", + "image/tiff&format_options=compression:packbits", + "image/tiff&format_options=compression:raw" + ], + "type": "string" + } + }, + { + "name": "harvest_url", + "description": "URL du service WMS, avec le protocole et le chemin", + "mandatory": true, + "constraints": { + "type": "string" + } + }, + { + "name": "bottom", + "description": "Le niveau du bas de la pyramide en sortie ", + "mandatory": true, + "constraints": { + "type": "string" + } + }, + { + "name": "nodata", + "description": "Valeur de nodata pour compléter les images", + "mandatory": false, + "constraints": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1 + } + } + ], + "_id": "748e4ebe-3ef6-447d-8221-ecd3bc2157e6", + "required_checks": [] +} diff --git a/src/Controller/Entrepot/DatasheetController.php b/src/Controller/Entrepot/DatasheetController.php index 1b7a57bc..7fdcc942 100644 --- a/src/Controller/Entrepot/DatasheetController.php +++ b/src/Controller/Entrepot/DatasheetController.php @@ -103,14 +103,12 @@ public function getDetailed(string $datastoreId, string $datasheetName): JsonRes 'tags' => [ CommonTags::DATASHEET_NAME => $datasheetName, ], - // 'fields' => ['name', 'description', 'type', 'visibility', 'status', 'srs', 'contact', 'size', 'last_event', 'tags', 'bbox'], ]); $storedDataList = $this->storedDataApiService->getAllDetailed($datastoreId, [ 'tags' => [ CommonTags::DATASHEET_NAME => $datasheetName, ], - // 'fields' => ['name', 'description', 'type', 'visibility', 'status', 'srs', 'contact', 'size', 'last_event', 'tags', 'bbox'], ]); $vectorDbList = array_filter($storedDataList, function ($storedData) { @@ -119,10 +117,16 @@ public function getDetailed(string $datastoreId, string $datasheetName): JsonRes $vectorDbList = array_values($vectorDbList); // Pyramid vector - $pyramidList = array_filter($storedDataList, function ($storedData) { + $pyramidVectorList = array_filter($storedDataList, function ($storedData) { return StoredDataTypes::ROK4_PYRAMID_VECTOR === $storedData['type']; }); - $pyramidList = array_values($pyramidList); + $pyramidVectorList = array_values($pyramidVectorList); + + // Pyramid raster + $pyramidRasterList = array_filter($storedDataList, function ($storedData) { + return StoredDataTypes::ROK4_PYRAMID_RASTER === $storedData['type']; + }); + $pyramidRasterList = array_values($pyramidRasterList); $metadataList = $this->metadataApiService->getAll($datastoreId, [ 'tags' => [ @@ -130,7 +134,11 @@ public function getDetailed(string $datastoreId, string $datasheetName): JsonRes ], ]); - if (0 === count($uploadList) && 0 === count($vectorDbList) && 0 === count($pyramidList) && 0 === count($metadataList)) { + if ( + 0 === count($uploadList) + && 0 === count($storedDataList) + && 0 === count($metadataList) + ) { throw new CartesApiException("La fiche de donnée [$datasheetName] n'existe pas", Response::HTTP_NOT_FOUND); } @@ -143,7 +151,8 @@ public function getDetailed(string $datastoreId, string $datasheetName): JsonRes return $this->json([ ...$datasheet, 'vector_db_list' => $vectorDbList, - 'pyramid_list' => $pyramidList, + 'pyramid_vector_list' => $pyramidVectorList, + 'pyramid_raster_list' => $pyramidRasterList, 'upload_list' => $uploadList, 'service_list' => $services, ]); diff --git a/src/Controller/Entrepot/PyramidRasterController.php b/src/Controller/Entrepot/PyramidRasterController.php new file mode 100644 index 00000000..07e06f9c --- /dev/null +++ b/src/Controller/Entrepot/PyramidRasterController.php @@ -0,0 +1,299 @@ + true], + condition: 'request.isXmlHttpRequest()' +)] +class PyramidRasterController extends ServiceController implements ApiControllerInterface +{ + public function __construct( + private DatastoreApiService $datastoreApiService, + private ConfigurationApiService $configurationApiService, + private StoredDataApiService $storedDataApiService, + private ProcessingApiService $processingApiService, + SandboxService $sandboxService, + CartesServiceApiService $cartesServiceApiService, + private CapabilitiesService $capabilitiesService, + private CartesMetadataApiService $cartesMetadataApiService, + ) { + parent::__construct($datastoreApiService, $configurationApiService, $cartesServiceApiService, $capabilitiesService, $cartesMetadataApiService, $sandboxService); + } + + #[Route('/add', name: 'add', methods: ['POST'])] + public function add(string $datastoreId, Request $request): JsonResponse + { + try { + $data = json_decode($request->getContent(), true); + + $processingId = $this->sandboxService->getProcGeneratePyramidRaster($datastoreId); + + $wmsvOffering = $this->configurationApiService->getOffering($datastoreId, $data['wmsv_offering_id']); + $wmsvConfiguration = $this->configurationApiService->get($datastoreId, $wmsvOffering['configuration']['_id']); + + $vectorDbId = $wmsvConfiguration['type_infos']['used_data'][0]['stored_data'] ?? null; + if (null === $vectorDbId) { + throw new AppException(sprintf('Donnée stockée du type %s référencée par le service WMS-Vecteur non trouvée', StoredDataTypes::VECTOR_DB), Response::HTTP_BAD_REQUEST); + } + + $vectordb = $this->storedDataApiService->get($datastoreId, $vectorDbId); + + $serviceEndpoint = $this->datastoreApiService->getEndpoint($datastoreId, $wmsvOffering['endpoint']['_id']); + $harvestUrl = $serviceEndpoint['endpoint']['urls'][0]['url'] ?? null; + + if (null === $harvestUrl) { + throw new AppException('URL du service WMS-Vecteur non trouvée', Response::HTTP_BAD_REQUEST); + } + + $zoomRange = $data['zoom_range']; + $harvestLevels = array_map(fn ($v) => strval($v), array_reverse(range($zoomRange[0], $zoomRange[1], 1), false)); + + $requestBody = [ + 'processing' => $processingId, + 'output' => [ + 'stored_data' => [ + 'name' => $data['technical_name'], + ], + ], + 'parameters' => [ + 'samplesperpixel' => 3, + 'sampleformat' => 'UINT8', + 'tms' => 'PM', + 'compression' => 'jpg', + 'bottom' => strval($zoomRange[1]), + 'harvest_levels' => $harvestLevels, + + 'harvest_format' => 'image/jpeg', + 'harvest_url' => $harvestUrl, + 'harvest_layers' => $wmsvOffering['layer_name'], + 'harvest_area' => $data['wmsv_config_bbox'], + ], + ]; + + $processingExecution = $this->processingApiService->addExecution($datastoreId, $requestBody); + $pyramidId = $processingExecution['output']['stored_data']['_id']; + + $pyramidTags = [ + CommonTags::DATASHEET_NAME => $vectordb['tags'][CommonTags::DATASHEET_NAME], + 'upload_id' => $vectordb['tags']['upload_id'], + 'proc_int_id' => $vectordb['tags']['proc_int_id'], + 'vectordb_id' => $vectorDbId, + 'proc_pyr_creat_id' => $processingExecution['_id'], + ]; + + $this->storedDataApiService->addTags($datastoreId, $pyramidId, $pyramidTags); + $this->processingApiService->launchExecution($datastoreId, $processingExecution['_id']); + + return new JsonResponse(); + } catch (ApiException|AppException $ex) { + throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex); + } + } + + #[Route('/{pyramidId}/wms-raster-wmts', name: 'wms_raster_wmts_add', methods: ['POST'])] + public function addWmsRasterWmts( + string $datastoreId, + string $pyramidId, + #[MapQueryParameter] string $type, + Request $request + ): JsonResponse { + try { + $acceptedTypes = [ConfigurationTypes::WMSRASTER, ConfigurationTypes::WMTSTMS]; + if (!in_array($type, $acceptedTypes)) { + throw new AppException(sprintf("Le type %s n'est pas accepté. Les types acceptés sont %s.", $type, join(', ', $acceptedTypes)), Response::HTTP_BAD_REQUEST); + } + + $data = json_decode($request->getContent(), true); + $pyramid = $this->storedDataApiService->get($datastoreId, $pyramidId); + $datasheetName = $pyramid['tags'][CommonTags::DATASHEET_NAME]; + + // création de requête pour la config + $configRequestBody = $this->getConfigRequestBody($data, $pyramid, $type, false, $datastoreId); + + // Restriction d'acces + $endpoint = $this->getEndpointByShareType($datastoreId, $type, $data['share_with']); + + // Ajout de la configuration + $configuration = $this->configurationApiService->add($datastoreId, $configRequestBody); + $configuration = $this->configurationApiService->addTags($datastoreId, $configuration['_id'], [ + CommonTags::DATASHEET_NAME => $pyramid['tags'][CommonTags::DATASHEET_NAME], + ]); + + // Creation d'une offering + try { + $offering = $this->configurationApiService->addOffering($datastoreId, $configuration['_id'], $endpoint['_id'], $endpoint['open']); + $offering['configuration'] = $configuration; + } catch (\Throwable $th) { + // si la création de l'offering plante, on défait la création de la config + $this->configurationApiService->remove($datastoreId, $configuration['_id']); + throw $th; + } + + // création d'une permission pour la communauté actuelle + if ('your_community' === $data['share_with']) { + $this->addPermissionForCurrentCommunity($datastoreId, $offering); + } + + // Création ou mise à jour du capabilities + try { + if (ConfigurationTypes::WMSRASTER === $configuration['type']) { + $this->capabilitiesService->createOrUpdate($datastoreId, $endpoint, $offering['urls'][0]['url']); + } elseif (ConfigurationTypes::WMTSTMS === $configuration['type'] && StoredDataTypes::ROK4_PYRAMID_RASTER === $pyramid['type']) { + $offeringUrl = array_values(array_filter($offering['urls'], fn ($url) => 'WMTS' === $url['type']))[0] ?? null; + if (null !== $offeringUrl) { + $this->capabilitiesService->createOrUpdate($datastoreId, $endpoint, $offeringUrl['url']); + } + } + } catch (\Exception $e) { + } + + // création ou mise à jour de metadata + if ($this->sandboxService->isSandboxDatastore($datastoreId)) { + $data['identifier'] = Sandbox::LAYERNAME_PREFIX.$data['identifier']; + } + $this->cartesMetadataApiService->createOrUpdate($datastoreId, $datasheetName, $data); + + return $this->json($offering); + } catch (ApiException|AppException $ex) { + throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex); + } + } + + #[Route('/{pyramidId}/wms-raster-wmts/{offeringId}/edit', name: 'wms_raster_wmts_edit', methods: ['POST'])] + public function editWmsRasterWmts( + string $datastoreId, + string $pyramidId, + string $offeringId, + #[MapQueryParameter] string $type, + Request $request + ): JsonResponse { + try { + $acceptedTypes = [ConfigurationTypes::WMSRASTER, ConfigurationTypes::WMTSTMS]; + if (!in_array($type, $acceptedTypes)) { + throw new AppException(sprintf("Le type %s n'est pas accepté. Les types acceptés sont %s.", $type, join(', ', $acceptedTypes)), Response::HTTP_BAD_REQUEST); + } + $data = json_decode($request->getContent(), true); + + // récup config et offering existants + $oldOffering = $this->configurationApiService->getOffering($datastoreId, $offeringId); + $oldConfiguration = $this->configurationApiService->get($datastoreId, $oldOffering['configuration']['_id']); + + $pyramid = $this->storedDataApiService->get($datastoreId, $pyramidId); + $datasheetName = $pyramid['tags'][CommonTags::DATASHEET_NAME]; + + // création de requête pour la config + $configRequestBody = $this->getConfigRequestBody($data, $pyramid, $type, true); + + $endpoint = $this->getEndpointByShareType($datastoreId, $type, $data['share_with']); + + // Mise à jour de la configuration + $configuration = $this->configurationApiService->replace($datastoreId, $oldConfiguration['_id'], $configRequestBody); + + // on recrée l'offering si changement d'endpoint, sinon demande la synchronisation + if ($oldOffering['open'] !== $endpoint['open']) { + $this->configurationApiService->removeOffering($datastoreId, $oldOffering['_id']); + + $offering = $this->configurationApiService->addOffering($datastoreId, $oldConfiguration['_id'], $endpoint['_id'], $endpoint['open']); + + if (false === $offering['open']) { + // création d'une permission pour la communauté actuelle + $this->addPermissionForCurrentCommunity($datastoreId, $offering); + } + } else { + $offering = $this->configurationApiService->syncOffering($datastoreId, $offeringId); + } + + $offering['configuration'] = $configuration; + + // Création ou mise à jour du capabilities + try { + if (ConfigurationTypes::WMSRASTER === $configuration['type']) { + $this->capabilitiesService->createOrUpdate($datastoreId, $endpoint, $offering['urls'][0]['url']); + } elseif (ConfigurationTypes::WMTSTMS === $configuration['type'] && StoredDataTypes::ROK4_PYRAMID_RASTER === $pyramid['type']) { + $offeringUrl = array_values(array_filter($offering['urls'], fn ($url) => 'WMTS' === $url['type']))[0] ?? null; + if (null !== $offeringUrl) { + $this->capabilitiesService->createOrUpdate($datastoreId, $endpoint, $offeringUrl['url']); + } + } + } catch (\Exception $e) { + } + + // création ou mise à jour de metadata + if ($this->sandboxService->isSandboxDatastore($datastoreId)) { + $data['identifier'] = Sandbox::LAYERNAME_PREFIX.$data['identifier']; + } + $this->cartesMetadataApiService->createOrUpdate($datastoreId, $datasheetName, $data); + + return $this->json($offering); + } catch (ApiException $ex) { + throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex); + } + } + + /** + * @param array $data + * @param array $pyramid + */ + private function getConfigRequestBody(array $data, array $pyramid, string $type, bool $editMode = false, ?string $datastoreId = null): array + { + $levels = $this->getPyramidZoomLevels($pyramid); + + $requestBody = [ + 'type' => $type, + 'name' => $data['public_name'], + 'type_infos' => [ + 'title' => $data['public_name'], + 'abstract' => json_encode($data['description']), + 'keywords' => $data['category'], + 'used_data' => [[ + 'bottom_level' => $levels['bottom_level'], + 'top_level' => $levels['top_level'], + 'stored_data' => $pyramid['_id'], + ]], + ], + ]; + + if (false === $editMode) { + $requestBody['layer_name'] = $data['technical_name']; + + // rajoute le préfixe "sandbox." si c'est la communauté bac à sable + if ($this->sandboxService->isSandboxDatastore($datastoreId)) { + $requestBody['layer_name'] = Sandbox::LAYERNAME_PREFIX.$requestBody['layer_name']; + } + } + + if ('' !== $data['attribution_text'] && '' !== $data['attribution_url']) { + $requestBody['attribution'] = [ + 'title' => $data['attribution_text'], + 'url' => $data['attribution_url'], + ]; + } + + return $requestBody; + } +} diff --git a/src/Controller/Entrepot/PyramidController.php b/src/Controller/Entrepot/PyramidVectorController.php similarity index 90% rename from src/Controller/Entrepot/PyramidController.php rename to src/Controller/Entrepot/PyramidVectorController.php index 7625dc03..34f65838 100644 --- a/src/Controller/Entrepot/PyramidController.php +++ b/src/Controller/Entrepot/PyramidVectorController.php @@ -5,11 +5,10 @@ use App\Constants\EntrepotApi\CommonTags; use App\Constants\EntrepotApi\ConfigurationTypes; use App\Constants\EntrepotApi\Sandbox; -use App\Constants\EntrepotApi\ZoomLevels; use App\Controller\ApiControllerInterface; -use App\Dto\Pyramid\AddPyramidDTO; -use App\Dto\Pyramid\CompositionDTO; -use App\Dto\Pyramid\PublishPyramidDTO; +use App\Dto\PyramidVector\AddPyramidDTO; +use App\Dto\PyramidVector\CompositionDTO; +use App\Dto\PyramidVector\PublishPyramidDTO; use App\Exception\ApiException; use App\Exception\CartesApiException; use App\Services\CapabilitiesService; @@ -25,12 +24,12 @@ use Symfony\Component\Routing\Annotation\Route; #[Route( - '/api/datastores/{datastoreId}/pyramid', - name: 'cartesgouvfr_api_pyramid_', + '/api/datastores/{datastoreId}/pyramid-vector', + name: 'cartesgouvfr_api_pyramid_vector_', options: ['expose' => true], condition: 'request.isXmlHttpRequest()' )] -class PyramidController extends ServiceController implements ApiControllerInterface +class PyramidVectorController extends ServiceController implements ApiControllerInterface { public function __construct( DatastoreApiService $datastoreApiService, @@ -80,7 +79,7 @@ public function add(string $datastoreId, #[MapRequestPayload] AddPyramidDTO $dto $parameters['area'] = $dto->area; } - $processing = $this->sandboxService->getProcGeneratePyramid($datastoreId); + $processing = $this->sandboxService->getProcGeneratePyramidVector($datastoreId); $requestBody = [ 'processing' => $processing, @@ -96,10 +95,6 @@ public function add(string $datastoreId, #[MapRequestPayload] AddPyramidDTO $dto $processingExecution = $this->processingApiService->addExecution($datastoreId, $requestBody); $pyramidId = $processingExecution['output']['stored_data']['_id']; - $this->storedDataApiService->addTags($datastoreId, $dto->vectordb_id, [ - 'pyramid_id' => $pyramidId, - ]); - $pyramidTags = [ CommonTags::DATASHEET_NAME => $vectordb['tags'][CommonTags::DATASHEET_NAME], 'upload_id' => $vectordb['tags']['upload_id'], @@ -230,7 +225,7 @@ public function editTms( private function getConfigRequestBody(PublishPyramidDTO $dto, array $pyramid, bool $editMode = false, ?string $datastoreId = null): array { // Recherche de bottom_level et top_level - $levels = $this->getBottomAndToLevel($pyramid); + $levels = $this->getPyramidZoomLevels($pyramid); $requestBody = [ 'type' => ConfigurationTypes::WMTSTMS, @@ -281,23 +276,4 @@ private function getLevels($composition): array return $levels; } - - /** - * @param array $pyramid - * - * @return array - */ - private function getBottomAndToLevel(array $pyramid) - { - if (!isset($pyramid['type_infos']) || !isset($pyramid['type_infos']['levels'])) { - return ['bottom_level' => strval(ZoomLevels::BOTTOM_LEVEL_DEFAULT), 'top_level' => strval(ZoomLevels::TOP_LEVEL_DEFAULT)]; - } - - $levels = $pyramid['type_infos']['levels']; - usort($levels, function (string $a, string $b) { - return intval($a) - intval($b); - }); - - return ['bottom_level' => end($levels), 'top_level' => reset($levels)]; - } } diff --git a/src/Controller/Entrepot/ServiceController.php b/src/Controller/Entrepot/ServiceController.php index 1162f5dd..452fbfd4 100644 --- a/src/Controller/Entrepot/ServiceController.php +++ b/src/Controller/Entrepot/ServiceController.php @@ -5,6 +5,7 @@ use App\Constants\EntrepotApi\CommonTags; use App\Constants\EntrepotApi\PermissionTypes; use App\Constants\EntrepotApi\Sandbox; +use App\Constants\EntrepotApi\ZoomLevels; use App\Controller\ApiControllerInterface; use App\Exception\ApiException; use App\Exception\CartesApiException; @@ -170,4 +171,23 @@ protected function addPermissionForCurrentCommunity(string $datastoreId, array $ $this->datastoreApiService->addPermission($datastoreId, $permissionRequestBody); } + + /** + * @param array $pyramid + * + * @return array + */ + protected function getPyramidZoomLevels(array $pyramid) + { + if (!isset($pyramid['type_infos']) || !isset($pyramid['type_infos']['levels'])) { + return ['bottom_level' => strval(ZoomLevels::BOTTOM_LEVEL_DEFAULT), 'top_level' => strval(ZoomLevels::TOP_LEVEL_DEFAULT)]; + } + + $levels = $pyramid['type_infos']['levels']; + usort($levels, function (string $a, string $b) { + return intval($a) - intval($b); + }); + + return ['bottom_level' => end($levels), 'top_level' => reset($levels)]; + } } diff --git a/src/Controller/Entrepot/UserController.php b/src/Controller/Entrepot/UserController.php index a82cff7f..e3e301cd 100644 --- a/src/Controller/Entrepot/UserController.php +++ b/src/Controller/Entrepot/UserController.php @@ -34,11 +34,11 @@ public function getCurrentUser(): JsonResponse /** @var User */ $user = $this->getUser(); - $apiUserInfo = $this->userApiService->getMe(); + // $apiUserInfo = $this->userApiService->getMe(); - if (array_key_exists('communities_member', $apiUserInfo)) { - $user->setCommunitiesMember($apiUserInfo['communities_member']); - } + // if (array_key_exists('communities_member', $apiUserInfo)) { + // $user->setCommunitiesMember($apiUserInfo['communities_member']); + // } return $this->json($user); } diff --git a/src/Dto/Pyramid/AddPyramidDTO.php b/src/Dto/PyramidVector/AddPyramidDTO.php similarity index 96% rename from src/Dto/Pyramid/AddPyramidDTO.php rename to src/Dto/PyramidVector/AddPyramidDTO.php index ffe9abdb..89aa100c 100644 --- a/src/Dto/Pyramid/AddPyramidDTO.php +++ b/src/Dto/PyramidVector/AddPyramidDTO.php @@ -1,6 +1,6 @@ 'onKernelResponse', + ]; + } + + public function onKernelResponse(ResponseEvent $event): void + { + if (!$this->kernel->isDebug()) { + return; + } + + $request = $event->getRequest(); + if (!$request->isXmlHttpRequest()) { + return; + } + + $response = $event->getResponse(); + $response->headers->set('Symfony-Debug-Toolbar-Replace', '1'); + } +} diff --git a/src/Services/CapabilitiesService.php b/src/Services/CapabilitiesService.php index 92ba92b1..5a2bfb38 100644 --- a/src/Services/CapabilitiesService.php +++ b/src/Services/CapabilitiesService.php @@ -2,7 +2,6 @@ namespace App\Services; -use App\Constants\EntrepotApi\CommonTags; use App\Constants\EntrepotApi\ConfigurationTypes; use App\Services\EntrepotApi\AnnexeApiService; use App\Services\EntrepotApi\ConfigurationApiService; @@ -48,21 +47,21 @@ public function createOrUpdate(string $datastoreId, array $endpoint, string $url return null; } - if (!in_array($endpoint['type'], [ConfigurationTypes::WFS, ConfigurationTypes::WMSVECTOR])) { - return null; - } - - $allOfferings = $this->configurationApiService->getAllOfferings($datastoreId, ['endpoint' => $endpoint['_id']]); - $xmlStr = null; switch ($endpoint['type']) { case ConfigurationTypes::WFS: - $xmlStr = $this->filterWFSCapabilities($endpoint, $url, $allOfferings); + $xmlStr = $this->filterWFSCapabilities($datastoreId, $endpoint, $url); break; case ConfigurationTypes::WMSVECTOR: - $xmlStr = $this->filterWMSCapabilities($endpoint, $url, $allOfferings); + case ConfigurationTypes::WMSRASTER: + $xmlStr = $this->filterWMSCapabilities($datastoreId, $endpoint, $url); + break; + case ConfigurationTypes::WMTSTMS: + // uniquement pour le WMTS + $xmlStr = $this->filterWMTSCapabilities($datastoreId, $endpoint, $url); break; - default: break; + default: + return null; } $uuid = uniqid(); @@ -82,8 +81,6 @@ public function createOrUpdate(string $datastoreId, array $endpoint, string $url $annexe = $this->annexeApiService->add($datastoreId, $filePath, [$path], ['type=capabilities']); } - $this->fs->remove($filePath); - return $annexe; } @@ -94,8 +91,10 @@ public function getGetCapUrl(string $endpointUrl, string $offeringUrl, string $s return sprintf('%s?SERVICE=%s&VERSION=%s&request=GetCapabilities', $endpointUrl, $serviceType, $version); } - private function filterWFSCapabilities(mixed $endpoint, string $url, mixed $allOfferings): string + private function filterWFSCapabilities(string $datastoreId, mixed $endpoint, string $url): string { + $allOfferings = $this->configurationApiService->getAllOfferings($datastoreId, ['endpoint' => $endpoint['_id']]); + // Les couches liees aux offerings $layerNames = []; foreach ($allOfferings as $offering) { @@ -139,8 +138,10 @@ private function filterWFSCapabilities(mixed $endpoint, string $url, mixed $allO return $doc->saveXML(); } - private function filterWMSCapabilities(mixed $endpoint, string $url, mixed $allOfferings): string + private function filterWMSCapabilities(string $datastoreId, mixed $endpoint, string $url): string { + $allOfferings = $this->configurationApiService->getAllOfferings($datastoreId, ['endpoint' => $endpoint['_id']]); + // Les couches liees aux offerings $layerNames = []; foreach ($allOfferings as $offering) { @@ -177,6 +178,50 @@ private function filterWMSCapabilities(mixed $endpoint, string $url, mixed $allO return $doc->saveXML(); } + private function filterWMTSCapabilities(string $datastoreId, mixed $endpoint, string $url): string + { + $allOfferings = $this->configurationApiService->getAllOfferings($datastoreId, ['endpoint' => $endpoint['_id']]); + + $layerNames = []; + foreach ($allOfferings as $offering) { + $layerNames[] = $offering['layer_name']; + } + + $endpointUrl = array_values(array_filter($endpoint['urls'], fn ($url) => 'WMTS' === $url['type']))[0] ?? null; + if (null === $endpointUrl) { + throw new \Exception('Endpoint URL not found'); + } + + $getCapUrl = $this->getGetCapUrl($endpointUrl['url'], $url, 'WMTS'); + + $response = $this->httpClient->request('GET', $getCapUrl); + if (JsonResponse::HTTP_OK != $response->getStatusCode()) { + throw new \Exception('Request GetCapabilities failed'); + } + + $doc = new \DOMDocument(); + $loaded = $doc->loadXML($response->getContent()); + if (!$loaded) { + throw new \Exception('Parsing GetCapabilities failed'); + } + + $contents = $doc->getElementsByTagName('Contents'); + $layers = $contents[0]?->getElementsByTagName('Layer'); + + $index = $layers->count() - 1; + while ($index >= 0) { + $child = $layers->item($index); + $name = $child->getElementsByTagName('Identifier')[0]->textContent; + + if (!in_array($name, $layerNames)) { + $child->remove(); + } + --$index; + } + + return $doc->saveXML(); + } + private function getServiceVersion(string $url): string { $res = []; diff --git a/src/Services/EntrepotApi/CartesMetadataApiService.php b/src/Services/EntrepotApi/CartesMetadataApiService.php index 20b69954..804615a8 100644 --- a/src/Services/EntrepotApi/CartesMetadataApiService.php +++ b/src/Services/EntrepotApi/CartesMetadataApiService.php @@ -3,7 +3,9 @@ namespace App\Services\EntrepotApi; use App\Constants\EntrepotApi\CommonTags; +use App\Constants\EntrepotApi\ConfigurationStatuses; use App\Constants\EntrepotApi\ConfigurationTypes; +use App\Constants\EntrepotApi\StoredDataTypes; use App\Entity\CswMetadata\CswCapabilitiesFile; use App\Entity\CswMetadata\CswDocument; use App\Entity\CswMetadata\CswHierarchyLevel; @@ -33,6 +35,7 @@ public function __construct( private ConfigurationApiService $configurationApiService, private CswMetadataHelper $cswMetadataHelper, private CartesServiceApiService $cartesServiceApiService, + private StoredDataApiService $storedDataApiService ) { } @@ -89,7 +92,7 @@ public function updateLayers(string $datastoreId, string $datasheetName): void $cswMetadata = $this->cswMetadataHelper->fromXml($apiMetadataXml); $cswMetadata->layers = $this->getMetadataLayers($datastoreId, $datasheetName); $cswMetadata->styleFiles = $this->getStyleFiles($datastoreId, $datasheetName); - $cswMetadata->capabilitiesFiles = $this->getCapabilitiesFiles($datastoreId, $cswMetadata->layers); + $cswMetadata->capabilitiesFiles = $this->getCapabilitiesFiles($datastoreId, $datasheetName); $xmlFilePath = $this->cswMetadataHelper->saveToFile($cswMetadata); $this->metadataApiService->replaceFile($datastoreId, $apiMetadata['_id'], $xmlFilePath); @@ -259,7 +262,7 @@ private function getNewCswMetadata(string $datastoreId, string $datasheetName, ? $newCswMetadata->styleFiles = $this->getStyleFiles($datastoreId, $datasheetName); // Doit-être calculé après la récupération des layers - $newCswMetadata->capabilitiesFiles = $this->getCapabilitiesFiles($datastoreId, $layers); + $newCswMetadata->capabilitiesFiles = $this->getCapabilitiesFiles($datastoreId, $datasheetName); } return $newCswMetadata; @@ -295,23 +298,39 @@ private function getMetadataLayers(string $datastoreId, string $datasheetName): break; + case ConfigurationTypes::WMSRASTER: case ConfigurationTypes::WMSVECTOR: $layerName = $offering['layer_name']; - $endpointType = 'OGC:WMS'; + $endpointTypeOgc = 'OGC:WMS'; $endpointUrl = $serviceEndpoint['endpoint']['urls'][0]['url']; - $layers[] = new CswMetadataLayer($layerName, $endpointType, $endpointUrl, $offering['_id']); + $layers[] = new CswMetadataLayer($layerName, $endpointTypeOgc, $endpointUrl, $offering['_id']); break; case ConfigurationTypes::WMTSTMS: $layerName = $offering['layer_name']; - $endpointType = 'OGC:TMS'; - $tmsEndpoints = array_filter($serviceEndpoint['endpoint']['urls'], fn (array $url) => 'TMS' === $url['type']); - $tmsEndpoints = array_values($tmsEndpoints); + if (!isset($configuration['type_infos']['used_data'][0]['stored_data'])) { + break; + } + $pyramid = $this->storedDataApiService->get($datastoreId, $configuration['type_infos']['used_data'][0]['stored_data']); + + $endpointTypeOgc = null; + $actualType = null; + + if (StoredDataTypes::ROK4_PYRAMID_VECTOR === $pyramid['type']) { + $endpointTypeOgc = 'OGC:TMS'; + $actualType = 'TMS'; + } elseif (StoredDataTypes::ROK4_PYRAMID_RASTER === $pyramid['type']) { + $endpointTypeOgc = 'OGC:WMTS'; + $actualType = 'WMTS'; + } - if (count($tmsEndpoints) > 0) { - $layers[] = new CswMetadataLayer($layerName, $endpointType, $tmsEndpoints[0]['url'], $offering['_id']); + $endpoints = array_filter($serviceEndpoint['endpoint']['urls'], fn (array $url) => $actualType === $url['type']); + $endpoints = array_values($endpoints); + + if (count($endpoints) > 0) { + $layers[] = new CswMetadataLayer($layerName, $endpointTypeOgc, $endpoints[0]['url'], $offering['_id']); } break; @@ -382,24 +401,18 @@ private function getStyleFiles(string $datastoreId, string $datasheetName): arra return $styleFiles; } - /** - * @param array $layers - */ - private function getCapabilitiesFiles(string $datastoreId, array $layers): array + private function getCapabilitiesFiles(string $datastoreId, string $datasheetName): array { $datastore = $this->datastoreApiService->get($datastoreId); $datastoreName = $datastore['technical_name']; - // Mapping entre le type d'une layer et le type du endpoint correspondant - $mapping = ['OGC:WFS' => 'WFS', 'OGC:WMS' => 'WMS-VECTOR']; - $layerTypes = []; - - foreach ($layers as $layer) { - $type = $layer->endpointType; - if (array_key_exists($type, $mapping)) { - $layerTypes[] = $mapping[$type]; - } - } + $configurationsList = $this->configurationApiService->getAll($datastoreId, [ + 'tags' => [ + CommonTags::DATASHEET_NAME => $datasheetName, + ], + 'status' => ConfigurationStatuses::PUBLISHED, + ]); + $layerTypes = array_map(fn ($config) => $config['type'], $configurationsList); $layerTypes = array_values(array_unique($layerTypes)); $annexeUrl = $this->parameterBag->get('annexes_url'); diff --git a/src/Services/EntrepotApi/CartesServiceApiService.php b/src/Services/EntrepotApi/CartesServiceApiService.php index 8bb194b2..f25f44c4 100644 --- a/src/Services/EntrepotApi/CartesServiceApiService.php +++ b/src/Services/EntrepotApi/CartesServiceApiService.php @@ -4,6 +4,7 @@ use App\Constants\EntrepotApi\ConfigurationStatuses; use App\Constants\EntrepotApi\OfferingTypes; +use App\Constants\EntrepotApi\StoredDataTypes; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -21,6 +22,7 @@ public function __construct( private AnnexeApiService $annexeApiService, private DatastoreApiService $datastoreApiService, private StaticApiService $staticApiService, + private StoredDataApiService $storedDataApiService, HttpClientInterface $httpClient, ) { $this->httpClient = $httpClient->withOptions([ @@ -35,19 +37,26 @@ public function getService(string $datastoreId, string $offeringId): array $offering = $this->configurationApiService->getOffering($datastoreId, $offeringId); $offering['configuration'] = $this->configurationApiService->get($datastoreId, $offering['configuration']['_id']); - // Metadatas (TMS) + // traitement spécial pour WMTS-TMS if (OfferingTypes::WMTSTMS === $offering['type']) { - $urls = array_values(array_filter($offering['urls'], static function ($url) { - return 'TMS' == $url['type']; - })); - $url = $urls[0]['url'].'/metadata.json'; - - try { - $response = $this->httpClient->request('GET', $url); - if (Response::HTTP_OK === $response->getStatusCode()) { - $offering['tms_metadata'] = $response->toArray(); + $storedData = $this->storedDataApiService->get($datastoreId, $offering['configuration']['type_infos']['used_data'][0]['stored_data']); + $offering['configuration']['pyramid'] = $storedData; + + // TMS + if (StoredDataTypes::ROK4_PYRAMID_VECTOR === $storedData['type']) { + // Metadatas (TMS) + $urls = array_values(array_filter($offering['urls'], static function ($url) { + return 'TMS' == $url['type']; + })); + $url = $urls[0]['url'].'/metadata.json'; + + try { + $response = $this->httpClient->request('GET', $url); + if (Response::HTTP_OK === $response->getStatusCode()) { + $offering['tms_metadata'] = $response->toArray(); + } + } catch (\Throwable $th) { } - } catch (\Throwable $th) { } } @@ -97,6 +106,7 @@ public function getShareUrl(string $datastoreId, array $offering): ?string switch ($offering['type']) { case OfferingTypes::WFS: case OfferingTypes::WMSVECTOR: + case OfferingTypes::WMSRASTER: $annexeUrl = $this->params->get('annexes_url'); $shareUrl = join('/', [$annexeUrl, $datastore['technical_name'], $endpoint['endpoint']['technical_name'], 'capabilities.xml']); break; @@ -105,6 +115,12 @@ public function getShareUrl(string $datastoreId, array $offering): ?string if (isset($offering['tms_metadata']['tiles'][0])) { $shareUrl = $offering['tms_metadata']['tiles'][0]; } + + if (isset($offering['configuration']['pyramid']['type']) && StoredDataTypes::ROK4_PYRAMID_RASTER === $offering['configuration']['pyramid']['type']) { + $annexeUrl = $this->params->get('annexes_url'); + $shareUrl = join('/', [$annexeUrl, $datastore['technical_name'], $endpoint['endpoint']['technical_name'], 'capabilities.xml']); + } + break; default: @@ -123,11 +139,13 @@ public function unpublish(string $datastoreId, string $offeringId): void $this->wfsUnpublish($datastoreId, $offering); break; case OfferingTypes::WMTSTMS: - $this->tmsUnpublish($datastoreId, $offering); + $this->wmtsTmsUnpublish($datastoreId, $offering); break; case OfferingTypes::WMSVECTOR: $this->wmsVectorUnpublish($datastoreId, $offering); break; + case OfferingTypes::WMSRASTER: + $this->wmsRasterUnpublish($datastoreId, $offering); } } @@ -192,7 +210,28 @@ public function wmsVectorUnpublish(string $datastoreId, array $offering, bool $r /** * @param array $offering */ - public function tmsUnpublish(string $datastoreId, array $offering, bool $removeStyleFiles = true): void + public function wmsRasterUnpublish(string $datastoreId, array $offering): void + { + // suppression de l'offering + $this->configurationApiService->removeOffering($datastoreId, $offering['_id']); + $configurationId = $offering['configuration']['_id']; + + // suppression de la configuration + // la suppression de l'offering nécessite quelques instants, et tant que la suppression de l'offering n'est pas faite, on ne peut pas demander la suppression de la configuration + while (1) { + sleep(3); + $configuration = $this->configurationApiService->get($datastoreId, $configurationId); + if (ConfigurationStatuses::UNPUBLISHED === $configuration['status']) { + break; + } + } + $this->configurationApiService->remove($datastoreId, $configurationId); + } + + /** + * @param array $offering + */ + public function wmtsTmsUnpublish(string $datastoreId, array $offering, bool $removeStyleFiles = true): void { // suppression de l'offering $this->configurationApiService->removeOffering($datastoreId, $offering['_id']); @@ -215,25 +254,24 @@ public function tmsUnpublish(string $datastoreId, array $offering, bool $removeS } /** - * Suppression des styles lies à une configuration - * + * Suppression des styles lies à une configuration. */ - private function removeStyleFiles(string $datastoreId, string $configurationId) : void + private function removeStyleFiles(string $datastoreId, string $configurationId): void { $path = "/configuration/$configurationId/styles.json"; - + $styleAnnexes = $this->annexeApiService->getAll($datastoreId, null, $path); - if (count($styleAnnexes) === 0) { + if (0 === count($styleAnnexes)) { return; } $content = $this->annexeApiService->download($datastoreId, $styleAnnexes[0]['_id']); - + $styles = json_decode($content, true); - foreach($styles as $style) { + foreach ($styles as $style) { if (array_key_exists('layers', $style)) { - foreach($style['layers'] as $layer) { - $this->annexeApiService->remove($datastoreId, $layer['annexe_id']); + foreach ($style['layers'] as $layer) { + $this->annexeApiService->remove($datastoreId, $layer['annexe_id']); } } } diff --git a/src/Services/SandboxService.php b/src/Services/SandboxService.php index 043cb22e..4474d667 100644 --- a/src/Services/SandboxService.php +++ b/src/Services/SandboxService.php @@ -46,7 +46,7 @@ public function getProcIntegrateVectorFilesInBase(string $datastoreId): string return $processings['int_vect_files_db']; } - public function getProcGeneratePyramid(string $datastoreId): string + public function getProcGeneratePyramidVector(string $datastoreId): string { $apiEntrepot = $this->isSandboxDatastore($datastoreId) ? 'sandbox' : 'api_entrepot'; $processings = $this->parameterBag->get($apiEntrepot)['processings']; @@ -54,6 +54,14 @@ public function getProcGeneratePyramid(string $datastoreId): string return $processings['create_vect_pyr']; } + public function getProcGeneratePyramidRaster(string $datastoreId): string + { + $apiEntrepot = $this->isSandboxDatastore($datastoreId) ? 'sandbox' : 'api_entrepot'; + $processings = $this->parameterBag->get($apiEntrepot)['processings']; + + return $processings['create_rast_pyr']; + } + public function getSandboxCommunityId(): ?string { return $this->sandboxCommunityId;