Skip to content

Commit

Permalink
feat: ajoute possibilité dépublier un service
Browse files Browse the repository at this point in the history
  • Loading branch information
ocruze committed Oct 4, 2023
1 parent 5163901 commit 8d9f5f4
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 97 deletions.
12 changes: 11 additions & 1 deletion assets/api/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ const getOfferings = (datastoreId: string) => {
return jsonFetch<OfferingListResponseDto[]>(url);
};

const service = { get, getOfferings };
/**
* Demande la suppression de l'offering et puis la configuration
*/
const unpublish = (datastoreId: string, offeringId: string) => {
const url = SymfonyRouting.generate("cartesgouvfr_api_service_unpublish", { datastoreId, offeringId });
return jsonFetch(url, {
method: "DELETE",
});
};

const service = { get, getOfferings, unpublish };

export default service;
2 changes: 1 addition & 1 deletion assets/pages/datasheet/DatasheetView/DatasheetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { type CartesApiException } from "../../../modules/jsonFetch";
import { routes, useRoute } from "../../../router/router";
import { Datasheet, type DatasheetDetailed } from "../../../types/app";
import DatasetListTab from "./DatasetListTab/DatasetListTab";
import ServicesListTab from "./ServicesListTab";
import ServicesListTab from "./ServiceListTab/ServicesListTab";

import "../../../sass/components/spinner.scss";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { fr } from "@codegouvfr/react-dsfr";
import Badge from "@codegouvfr/react-dsfr/Badge";
import Button from "@codegouvfr/react-dsfr/Button";
import { createModal } from "@codegouvfr/react-dsfr/Modal";
import { useQueryClient } from "@tanstack/react-query";
import { FC } from "react";
import { createPortal } from "react-dom";
import { symToStr } from "tsafe/symToStr";

import api from "../../../../api";
import MenuList from "../../../../components/Utils/MenuList";
import functions from "../../../../functions";
import RQKeys from "../../../../modules/RQKeys";
import { routes } from "../../../../router/router";
import { Service } from "../../../../types/app";

const unpublishServiceConfirmModal = createModal({
id: "unpublish-service-confirm-modal",
isOpenedByDefault: false,
});

type ServicesListItemProps = {
service: Service;
datastoreId: string;
datasheetName: string;
};
const ServicesListItem: FC<ServicesListItemProps> = ({ service, datasheetName, datastoreId }) => {
const queryClient = useQueryClient();

const handleUnpublishService = () => {
api.service
.unpublish(datastoreId, service._id)
.then(() => {
queryClient.refetchQueries({ queryKey: RQKeys.datastore_datasheet(datastoreId, datasheetName) });
})
.catch((error) => {
console.error(error);
});
};

return (
<>
<div key={service._id} className={fr.cx("fr-grid-row", "fr-grid-row--middle", "fr-mt-2v")}>
<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>
<Button iconId="ri-add-box-fill" title="Voir les données liées" className={fr.cx("fr-mr-2v")} />
{service.configuration.name}
</div>
</div>

<div className={fr.cx("fr-col")}>
<div className={fr.cx("fr-grid-row", "fr-grid-row--right", "fr-grid-row--middle")}>
{/* TODO : afficher le bon type de service */}
<Badge>Web Feature Service</Badge>
<p className={fr.cx("fr-m-auto", "fr-mr-2v")}>
{service?.configuration?.last_event?.date && functions.date.format(service?.configuration?.last_event?.date)}
</p>
<i className={fr.cx("fr-mr-2v", service.open ? "fr-icon-lock-unlock-fill" : "fr-icon-lock-fill")} />

<Button
className={fr.cx("fr-mr-2v")}
linkProps={routes.datastore_service_view({ datastoreId, offeringId: service._id, datasheetName: datasheetName }).link}
>
Visualiser
</Button>
<MenuList
menuOpenButtonProps={{
iconId: "fr-icon-menu-2-fill",
title: "Autres actions",
}}
items={[
{
text: "Copier l'URL de diffusion",
iconId: "ri-file-copy-2-line",
onClick: () => console.warn("Action non implémentée"),
},
{
text: "Gérer les styles",
iconId: "ri-flashlight-line",
onClick: () => console.warn("Action non implémentée"),
},
{
text: "Mettre à jour la légende",
iconId: "ri-list-check",
onClick: () => console.warn("Action non implémentée"),
},
{
text: "Modifier les informations de publication",
iconId: "ri-edit-box-line",
onClick: () => console.warn("Action non implémentée"),
},
{
text: "Remplacer les données",
iconId: "fr-icon-refresh-line",
onClick: () => console.warn("Action non implémentée"),
},
{
text: "Dépublier",
iconId: "ri-arrow-go-back-line",
onClick: () => unpublishServiceConfirmModal.open(),
},
]}
/>
</div>
</div>
</div>

{createPortal(
<unpublishServiceConfirmModal.Component
title={`Êtes-vous sûr de dépublier le service ${service.type} ?`}
buttons={[
{
children: "Non, annuler",
doClosesModal: true,
priority: "secondary",
},
{
children: "Oui, supprimer",
onClick: () => handleUnpublishService(),
doClosesModal: true,
priority: "primary",
},
]}
>
<strong>Les éléments suivants seront supprimés :</strong>
<ul>
<li>1 offre ({service._id})</li>
<li>1 configuration ({service.configuration._id})</li>
</ul>
</unpublishServiceConfirmModal.Component>,
document.body
)}
</>
);
};
ServicesListItem.displayName = symToStr({ ServicesListItem });

export default ServicesListItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { fr } from "@codegouvfr/react-dsfr";
import { FC } from "react";
import { symToStr } from "tsafe/symToStr";

import Translator from "../../../../modules/Translator";
import { DatasheetDetailed } from "../../../../types/app";
import ServicesListItem from "./ServicesListItem";

type ServicesListTabProps = {
datasheet?: DatasheetDetailed;
datastoreId: string;
};
const ServicesListTab: FC<ServicesListTabProps> = ({ datastoreId, datasheet }) => {
if (!datasheet?.service_list || datasheet?.service_list.length === 0) {
return (
<div className={fr.cx("fr-grid-row", "fr-grid-row--middle")}>
<p>{Translator.trans("datasheet.view.services.no_service")}</p>
</div>
);
}

return datasheet?.service_list?.map((service) => (
<ServicesListItem key={service._id} service={service} datasheetName={datasheet.name} datastoreId={datastoreId} />
));
};
ServicesListTab.displayName = symToStr({ ServicesListTab });

export default ServicesListTab;
92 changes: 0 additions & 92 deletions assets/pages/datasheet/DatasheetView/ServicesListTab.tsx

This file was deleted.

35 changes: 32 additions & 3 deletions src/Controller/Api/ServiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

namespace App\Controller\Api;

use App\Constants\EntrepotApi\ConfigurationStatuses;
use App\Exception\CartesApiException;
use App\Exception\EntrepotApiException;
use App\Services\EntrepotApiService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Route(
'/api/datastores/{datastoreId}',
'/api/datastores/{datastoreId}/offerings',
name: 'cartesgouvfr_api_service_',
options: ['expose' => true],
condition: 'request.isXmlHttpRequest()'
Expand All @@ -22,18 +24,19 @@ public function __construct(
) {
}

#[Route('/offerings', name: 'get_offerings', methods: ['GET'])]
#[Route('', name: 'get_offerings', methods: ['GET'])]
public function getOfferings(string $datastoreId): JsonResponse
{
try {
$offerings = $this->entrepotApiService->configuration->getAllOfferings($datastoreId);

return $this->json($offerings);
} catch (EntrepotApiException $ex) {
throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex);
}
}

#[Route('/offering/{offeringId}', name: 'get_offering', methods: ['GET'])]
#[Route('/{offeringId}', name: 'get_offering', methods: ['GET'])]
public function getOffering(string $datastoreId, string $offeringId): JsonResponse
{
try {
Expand All @@ -45,4 +48,30 @@ public function getOffering(string $datastoreId, string $offeringId): JsonRespon
throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex);
}
}

#[Route('/{offeringId}', name: 'unpublish', methods: ['DELETE'])]
public function unpublish(string $datastoreId, string $offeringId): Response
{
try {
$offering = $this->entrepotApiService->configuration->getOffering($datastoreId, $offeringId);
$offering['configuration'] = $this->entrepotApiService->configuration->get($datastoreId, $offering['configuration']['_id']);

$this->entrepotApiService->configuration->removeOffering($datastoreId, $offering['_id']);
$configurationId = $offering['configuration']['_id'];

// 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->entrepotApiService->configuration->get($datastoreId, $configurationId);
if (ConfigurationStatuses::UNPUBLISHED === $configuration['status']) {
break;
}
}
$this->entrepotApiService->configuration->remove($datastoreId, $configurationId);

return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (EntrepotApiException $ex) {
throw new CartesApiException($ex->getMessage(), $ex->getStatusCode(), $ex->getDetails(), $ex);
}
}
}

0 comments on commit 8d9f5f4

Please sign in to comment.