From 38f9fcd951d6e814d1aff407369f6c35480444b9 Mon Sep 17 00:00:00 2001 From: Florent MILLOT <75525996+flomillot@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:22:45 +0200 Subject: [PATCH 1/2] Allow to share a composite modification from a study When a single composite modification is selected, the save dialog offers to store it in GridExplore as a shared modification: the composite itself leaves the study and is replaced in the node by a reference to it. The option is greyed out for any other selection, an already shared modification not being shareable again. Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com> --- .../network-modification-node-editor.tsx | 41 +++++++++++++++++++ src/services/study/network-modifications.ts | 19 +++++++++ src/translations/messages-en.ts | 9 ++-- src/translations/messages-fr.ts | 9 ++-- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx b/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx index 8bae19fc0c..49625dde5d 100644 --- a/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx +++ b/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx @@ -94,6 +94,7 @@ import { assembleModificationsIntoComposite, fetchExcludedNetworkModifications, fetchNetworkModifications, + shareCompositeModification, stashModifications, } from '../../../../services/study/network-modifications'; import { @@ -991,6 +992,37 @@ const NetworkModificationNodeEditor = () => { }); }; + const doShareCompositeModificationElement = ({ + name, + description, + folderName, + folderId, + }: IElementCreationDialog) => { + const compositeToShare = selectedNetworkModifications[0]; + // the selection may have been emptied by a refresh while the dialog was open + if (!compositeToShare) { + return; + } + + setSaveInProgress(true); + shareCompositeModification(studyUuid, currentNode?.id, compositeToShare.uuid, name, description, folderId) + .then(() => { + snackInfo({ + headerId: 'infoShareModificationMsg', + headerValues: { + item: name, + directory: folderName, + }, + }); + }) + .catch((error) => { + snackWithFallback(snackError, error, { headerId: 'errShareModificationMsg' }); + }) + .finally(() => { + setSaveInProgress(false); + }); + }; + const doUpdateCompositeModificationsElements = ({ id, name, @@ -1149,6 +1181,12 @@ const NetworkModificationNodeEditor = () => { ? (JSON.parse(selectedNetworkModifications[0]?.messageValues)?.name ?? null) : null; + // sharing moves the selected composite itself into gridexplore : it needs exactly one composite, and an + // already shared one (a reference) cannot be shared again + const isSharingAvailable = + selectedNetworkModifications.length === 1 && + selectedNetworkModifications[0].type === ModificationType.COMPOSITE_MODIFICATION; + const renderNetworkModificationsTable = () => { if (isRootNode) { return ( @@ -1204,6 +1242,8 @@ const NetworkModificationNodeEditor = () => { setCreateCompositeModificationDialogOpen(false)} type={ElementType.MODIFICATION} @@ -1213,6 +1253,7 @@ const NetworkModificationNodeEditor = () => { studyUuid={studyUuid} selectorTitleId="SelectCompositeModificationTitle" createLabelId="CreateCompositeModificationLabel" + createSharedLabelId="ShareCompositeModificationLabel" updateLabelId="UpdateCompositeModificationLabel" /> ) diff --git a/src/services/study/network-modifications.ts b/src/services/study/network-modifications.ts index 94a8854320..b2d6206bbc 100644 --- a/src/services/study/network-modifications.ts +++ b/src/services/study/network-modifications.ts @@ -1803,6 +1803,25 @@ export function assembleModificationsIntoComposite( }); } +/** + * Moves a composite modification of the node out of the study into a directory of GridExplore, + * and replaces it in the node by a reference to the newly shared composite modification. + */ +export function shareCompositeModification( + studyUuid: UUID | null, + nodeUuid: UUID | undefined, + modificationUuid: UUID, + name: string, + description: string, + parentDirectoryUuid: UUID +) { + console.info('Sharing composite modification'); + const url = `${getNetworkModificationUrl(studyUuid, nodeUuid)}/${safeEncodeURIComponent( + modificationUuid + )}/share?${new URLSearchParams({ name, description, parentDirectoryUuid }).toString()}`; + return backendFetch(url, { method: 'POST' }); +} + export function getNetworkModificationsFromComposite( compositeModificationUuids: string[], onlyMetadata: boolean = true diff --git a/src/translations/messages-en.ts b/src/translations/messages-en.ts index af04f7f73c..2374f71843 100644 --- a/src/translations/messages-en.ts +++ b/src/translations/messages-en.ts @@ -464,10 +464,11 @@ const messages_en = { SubstationList: 'Substation list', NetworkModificationTree: 'Network modification tree', RestoreModifications: 'Restore modifications', - CreateCompositeModification: 'Create composite network modification', + CreateCompositeModification: 'Create a composite modification', SelectCompositeModificationTitle: 'Composite network modification', - CreateCompositeModificationLabel: 'Create a new composite modification', - UpdateCompositeModificationLabel: 'Replace an existing composite modification', + CreateCompositeModificationLabel: 'New', + ShareCompositeModificationLabel: 'New (shared)', + UpdateCompositeModificationLabel: 'Replace an existing one', 'importComposites.title': 'Import composite modifications', 'importComposites.organizationLabel': 'Modifications organisation: ', 'importComposites.action.split': 'Same', @@ -1024,6 +1025,8 @@ const messages_en = { 'Composite modification of {nbModifications} unitary network modifications created in {directory}', infoUpdateModificationsMsg: 'Composite modification {item} updated with {nbModifications} unitary network modifications', + errShareModificationMsg: 'Composite modification sharing error', + infoShareModificationMsg: 'Composite modification "{item}" shared in directory "{directory}"', 'idSelector.idNeeded': 'Please select an ID', SpreadsheetFetchError: 'An error occurred while fetching equipments in the spreadsheet', partialCopyShuntCompensator: 'Partial copy : copying sections is not supported for this shunt compensator', diff --git a/src/translations/messages-fr.ts b/src/translations/messages-fr.ts index f789dac7f8..6b1f77a24a 100644 --- a/src/translations/messages-fr.ts +++ b/src/translations/messages-fr.ts @@ -470,10 +470,11 @@ const messages_fr = { SubstationList: 'Liste des postes', NetworkModificationTree: 'Arbre décisionnel', RestoreModifications: 'Restaurer les modifications', - CreateCompositeModification: 'Créer une modification composite de réseau', + CreateCompositeModification: 'Créer une modification composite', SelectCompositeModificationTitle: 'Modification composite de réseau', - CreateCompositeModificationLabel: 'Créer une nouvelle modification composite', - UpdateCompositeModificationLabel: 'Remplacer une modification composite existante', + CreateCompositeModificationLabel: 'Nouvelle', + ShareCompositeModificationLabel: 'Nouvelle (partagée)', + UpdateCompositeModificationLabel: 'Remplacer une existante', 'importComposites.title': 'Importer des modifications composites', 'importComposites.organizationLabel': 'Organisation des modifications : ', 'importComposites.action.split': 'Identiques', @@ -1037,6 +1038,8 @@ const messages_fr = { "Création d'une modification composite de {nbModifications} modifications unitaires dans {directory}", infoUpdateModificationsMsg: 'Mise à jour de la modification composite {item} avec {nbModifications} modifications unitaires', + errShareModificationMsg: 'Une erreur est survenue lors du partage de la modification composite', + infoShareModificationMsg: 'Partage de la modification composite "{item}" dans le dossier "{directory}"', 'idSelector.idNeeded': 'Veuillez sélectionner un ID', SpreadsheetFetchError: 'Une erreur est survenue lors du chargement des ouvrages dans le tableur', partialCopyShuntCompensator: From 316f32d1da9cc244fb6e40cb8cdf637c041b9c3e Mon Sep 17 00:00:00 2001 From: Florent MILLOT <75525996+flomillot@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:29:18 +0200 Subject: [PATCH 2/2] Do not offer to share a composite modification nested in another one Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com> --- .../network-modification-node-editor.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx b/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx index 49625dde5d..25ddc42f80 100644 --- a/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx +++ b/src/components/graph/menus/network-modifications/network-modification-node-editor.tsx @@ -1181,11 +1181,13 @@ const NetworkModificationNodeEditor = () => { ? (JSON.parse(selectedNetworkModifications[0]?.messageValues)?.name ?? null) : null; - // sharing moves the selected composite itself into gridexplore : it needs exactly one composite, and an - // already shared one (a reference) cannot be shared again + // Sharing moves the selected composite itself into gridexplore : it needs exactly one composite, and an + // already shared one (a reference) cannot be shared again. Only a composite of the node itself can be shared, + // not one nested in another composite, so the third condition: the modifications list holds the modifications of the node only const isSharingAvailable = selectedNetworkModifications.length === 1 && - selectedNetworkModifications[0].type === ModificationType.COMPOSITE_MODIFICATION; + selectedNetworkModifications[0].type === ModificationType.COMPOSITE_MODIFICATION && + modifications.some((modification) => modification.uuid === selectedNetworkModifications[0].uuid); const renderNetworkModificationsTable = () => { if (isRootNode) {