diff --git a/package-lock.json b/package-lock.json index 09849c46..d696b240 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.74.0", + "@gridsuite/commons-ui": "0.75.0", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -3015,9 +3015,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.74.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.74.0.tgz", - "integrity": "sha512-EYJ2ReqlcPT8TvZEPrUkwZHz2a5Iq16u9padCLxZfXVo3kBvDPYth6P4lCIc5+kSqq2ZeSU3Bi+qE0BqNlvxgA==", + "version": "0.75.0", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.75.0.tgz", + "integrity": "sha512-yjBjVZZkftRUCV3ZWWYNyfL5uu0a0+PstberxcC7QUulrwGbZuSzYh3F5Nr29qHgNTWxGi/jaz+E+ANkpOGWdw==", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", @@ -3034,6 +3034,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", "react-virtualized": "^9.22.5", + "reconnecting-websocket": "^4.4.0", "uuid": "^9.0.1" }, "engines": { diff --git a/package.json b/package.json index f6b43ec7..8e20dfe2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.74.0", + "@gridsuite/commons-ui": "0.75.0", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", diff --git a/src/components/directory-content-table.tsx b/src/components/directory-content-table.tsx index f54df8ca..5513b71a 100644 --- a/src/components/directory-content-table.tsx +++ b/src/components/directory-content-table.tsx @@ -50,6 +50,7 @@ const getClickableRowStyle = (cellData: RowClassParams) => { ElementType.VOLTAGE_INIT_PARAMETERS, ElementType.SHORT_CIRCUIT_PARAMETERS, ElementType.SPREADSHEET_CONFIG, + ElementType.SPREADSHEET_CONFIG_COLLECTION, ].includes(cellData.data.type) ) { style.cursor = 'pointer'; diff --git a/src/components/directory-content.tsx b/src/components/directory-content.tsx index 1d46f19b..d77fc641 100644 --- a/src/components/directory-content.tsx +++ b/src/components/directory-content.tsx @@ -10,7 +10,6 @@ import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from 'react-intl'; import { Box, Button, CircularProgress, Grid, SxProps, Theme } from '@mui/material'; import { - CriteriaBasedFilterEditionDialog, DescriptionModificationDialog, ElementAttributes, ElementType, @@ -153,15 +152,6 @@ export default function DirectoryContent() { setElementName(''); }; - /** Filters dialog: window status value to edit CriteriaBased filters */ - const [currentCriteriaBasedFilterId, setCurrentCriteriaBasedFilterId] = useState(null); - const handleCloseCriteriaBasedFilterDialog = () => { - setOpenDialog(constants.DialogsId.NONE); - setCurrentCriteriaBasedFilterId(null); - setActiveElement(null); - setElementName(''); - }; - const [currentExplicitNamingFilterId, setCurrentExplicitNamingFilterId] = useState(null); /** Filters dialog: window status value to edit ExplicitNaming filters */ const handleCloseExplicitNamingFilterDialog = () => { @@ -365,9 +355,6 @@ export default function DirectoryContent() { if (subtype === FilterType.EXPLICIT_NAMING.id) { setCurrentExplicitNamingFilterId(event.data.elementUuid); setOpenDialog(subtype); - } else if (subtype === FilterType.CRITERIA_BASED.id) { - setCurrentCriteriaBasedFilterId(event.data.elementUuid); - setOpenDialog(subtype); } else if (subtype === FilterType.EXPERT.id) { setCurrentExpertFilterId(event.data.elementUuid); setOpenDialog(subtype); @@ -552,24 +539,6 @@ export default function DirectoryContent() { language={languageLocal} /> ); - case FilterType.CRITERIA_BASED.id: - return ( - - ); case FilterType.EXPERT.id: return ( { + handleDuplicateError(error.message); + }); + break; default: { handleLastError(intl.formatMessage({ id: 'unsupportedItem' })); } @@ -409,6 +416,7 @@ export default function ContentContextualMenu(props: Readonly { - const allowedTypes = [ElementType.CASE, ElementType.SPREADSHEET_CONFIG]; + const allowedTypes = [ + ElementType.CASE, + ElementType.SPREADSHEET_CONFIG, + ElementType.SPREADSHEET_CONFIG_COLLECTION, + ]; // if selectedElements contains at least one of the allowed types return selectedElements.some((element) => allowedTypes.includes(element.type)) && noCreationInProgress(); }, [selectedElements, noCreationInProgress]); diff --git a/src/components/menus/directory-tree-contextual-menu.tsx b/src/components/menus/directory-tree-contextual-menu.tsx index 5ec3baea..f40d7098 100644 --- a/src/components/menus/directory-tree-contextual-menu.tsx +++ b/src/components/menus/directory-tree-contextual-menu.tsx @@ -34,6 +34,7 @@ import { deleteElement, duplicateElement, duplicateSpreadsheetConfig, + duplicateSpreadsheetConfigCollection, elementExists, insertDirectory, insertRootDirectory, @@ -172,6 +173,11 @@ export default function DirectoryTreeContextualMenu(props: Readonly handlePasteError(error) + ); + break; default: handleError( intl.formatMessage({ diff --git a/src/components/toolbars/content-toolbar.tsx b/src/components/toolbars/content-toolbar.tsx index d88ebb89..b47e6e9f 100644 --- a/src/components/toolbars/content-toolbar.tsx +++ b/src/components/toolbars/content-toolbar.tsx @@ -120,7 +120,11 @@ export default function ContentToolbar(props: Readonly) { ); const allowsDownload = useMemo(() => { - const allowedTypes = [ElementType.CASE, ElementType.SPREADSHEET_CONFIG]; + const allowedTypes = [ + ElementType.CASE, + ElementType.SPREADSHEET_CONFIG, + ElementType.SPREADSHEET_CONFIG_COLLECTION, + ]; // if selectedElements contains at least one of the allowed types return selectedElements.some((element) => allowedTypes.includes(element.type)) && noCreationInProgress; }, [selectedElements, noCreationInProgress]); diff --git a/src/components/utils/downloadUtils.ts b/src/components/utils/downloadUtils.ts index d31654f1..e17ac4cb 100644 --- a/src/components/utils/downloadUtils.ts +++ b/src/components/utils/downloadUtils.ts @@ -9,7 +9,13 @@ import { useIntl } from 'react-intl'; import { ElementAttributes, ElementType, useSnackMessage } from '@gridsuite/commons-ui'; import { useCallback, useState } from 'react'; import { UUID } from 'crypto'; -import { downloadCase, downloadSpreadsheetConfig, fetchConvertedCase, getCaseOriginalName } from '../../utils/rest-api'; +import { + downloadCase, + downloadSpreadsheetConfig, + downloadSpreadsheetConfigCollection, + fetchConvertedCase, + getCaseOriginalName, +} from '../../utils/rest-api'; interface DownloadData { blob: Blob; @@ -56,6 +62,23 @@ const downloadStrategies: { [key in ElementType]?: (element: ElementAttributes) return { blob: await result.blob(), filename }; } }, + [ElementType.SPREADSHEET_CONFIG_COLLECTION]: async (element: ElementAttributes) => { + const result = await downloadSpreadsheetConfigCollection(element.elementUuid); + const filename = `${element.elementName}.json`; + try { + // Parse the JSON to ensure it's valid + const jsonContent = await result.json(); + // Stringify with indentation for pretty formatting + const prettyJson = JSON.stringify(jsonContent, null, 2); + // Create a new Blob with the pretty-formatted JSON + const blob = new Blob([prettyJson], { type: 'application/json' }); + return { blob, filename }; + } catch (error) { + // If parsing fails, fall back to the original blob + console.error('Error parsing JSON:', error); + return { blob: await result.blob(), filename }; + } + }, }; export function useDownloadUtils() { diff --git a/src/translations/en.json b/src/translations/en.json index e8838dba..047f9277 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -75,6 +75,7 @@ "editFilter": "Edit filter", "STUDY": "Study", "SPREADSHEET_CONFIG": "Spreadsheet model", + "SPREADSHEET_CONFIG_COLLECTION": "Spreadsheet model collection", "VOLTAGE_INIT_PARAMETERS": "Settings (Voltage profile initialization)", "SECURITY_ANALYSIS_PARAMETERS": "Settings (Security analysis)", "SENSITIVITY_PARAMETERS": "Settings (Sensitivity analysis)", diff --git a/src/translations/external/backend-locale-en.ts b/src/translations/external/backend-locale-en.ts index 2a5adc61..bf2062b4 100644 --- a/src/translations/external/backend-locale-en.ts +++ b/src/translations/external/backend-locale-en.ts @@ -14,14 +14,13 @@ const BackendLocaleEn = { MATPOWER: 'MATPOWER', POWERFACTORY: 'POWERFACTORY', 'IEEE CDF': 'IEEE CDF', - EXPERT_FILTER: 'Filter (Expert)', + EXPERT_FILTER: 'Filter (Criteria based)', FORM_FILTER: 'Filter (Form)', IDENTIFIERS_CONTINGENCY_LIST: 'Contingency list (Explicit naming)', FORM_CONTINGENCY_LIST: 'Contingency list (Criteria based)', SCRIPT_FILTER: 'Filter (Script)', SCRIPT_CONTINGENCY_LIST: 'Contingency list (Script)', IDENTIFIER_LIST_FILTER: 'Filter (Explicit naming)', - CRITERIA_FILTER: 'Filter (Criteria based)', // spreadsheet config metadata Load: 'Load', diff --git a/src/translations/external/backend-locale-fr.ts b/src/translations/external/backend-locale-fr.ts index 7e2e1457..5681be8a 100644 --- a/src/translations/external/backend-locale-fr.ts +++ b/src/translations/external/backend-locale-fr.ts @@ -14,14 +14,13 @@ const BackendLocaleFr = { MATPOWER: 'MATPOWER', POWERFACTORY: 'POWERFACTORY', 'IEEE CDF': 'IEEE CDF', - EXPERT_FILTER: 'Filtre (Expert)', + EXPERT_FILTER: 'Filtre (Par critères)', FORM_FILTER: 'Filtre (Formulaire)', IDENTIFIERS_CONTINGENCY_LIST: "Liste d'aléas (Par nommage)", FORM_CONTINGENCY_LIST: "Liste d'aléas (Par critères)", SCRIPT_FILTER: 'Filtre (Script)', SCRIPT_CONTINGENCY_LIST: "Liste d'aléas (Script)", IDENTIFIER_LIST_FILTER: 'Filtre (Par nommage)', - CRITERIA_FILTER: 'Filtre (Par critères)', // spreadsheet config metadata Load: 'Consommation', diff --git a/src/translations/fr.json b/src/translations/fr.json index 6cc2f379..cb3db4cc 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -74,6 +74,7 @@ "editFilter": "Éditer le filtre", "STUDY": "Étude", "SPREADSHEET_CONFIG": "Modèle de tableur", + "SPREADSHEET_CONFIG_COLLECTION": "Modèles de tableur", "VOLTAGE_INIT_PARAMETERS": "Paramètres (Initialisation du plan de tension)", "SECURITY_ANALYSIS_PARAMETERS": "Paramètres (Analyse de sécurité)", "SENSITIVITY_PARAMETERS": "Paramètres (Analyse de sensibilité)", diff --git a/src/utils/elementType.ts b/src/utils/elementType.ts index e17bf1de..e96e8536 100644 --- a/src/utils/elementType.ts +++ b/src/utils/elementType.ts @@ -6,7 +6,6 @@ */ export const FilterType = { - CRITERIA_BASED: { id: 'CRITERIA', label: 'filter.criteriaBased' }, EXPLICIT_NAMING: { id: 'IDENTIFIER_LIST', label: 'filter.explicitNaming' }, EXPERT: { id: 'EXPERT', label: 'filter.expert' }, }; diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts index 88971c23..447aad5d 100644 --- a/src/utils/rest-api.ts +++ b/src/utils/rest-api.ts @@ -436,6 +436,22 @@ export function duplicateSpreadsheetConfig(sourceCaseUuid: UUID, parentDirectory }); } +export function duplicateSpreadsheetConfigCollection(sourceCaseUuid: UUID, parentDirectoryUuid?: UUID) { + console.info('Duplicating a spreadsheet config collection...'); + const queryParams = new URLSearchParams(); + queryParams.append('duplicateFrom', sourceCaseUuid); + if (parentDirectoryUuid) { + queryParams.append('parentDirectoryUuid', parentDirectoryUuid); + } + const url = `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/spreadsheet-config-collections/duplicate?${queryParams.toString()}`; + + console.debug(url); + + return backendFetch(url, { + method: 'post', + }); +} + export function downloadSpreadsheetConfig(configId: string) { console.info(`Downloading spreadsheet config with id: ${configId}`); const fetchUrl = `${PREFIX_SPREADSHEET_CONFIG_QUERIES}/v1/spreadsheet-configs/${configId}`; @@ -448,6 +464,18 @@ export function downloadSpreadsheetConfig(configId: string) { }); } +export function downloadSpreadsheetConfigCollection(collectionId: string) { + console.info(`Downloading spreadsheet config collection with id: ${collectionId}`); + const fetchUrl = `${PREFIX_SPREADSHEET_CONFIG_QUERIES}/v1/spreadsheet-config-collections/${collectionId}`; + + return backendFetch(fetchUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); +} + export function elementExists(directoryUuid: UUID | null | undefined, elementName: string, type: string) { const elementNameEncoded = encodeURIComponent(elementName); const existsElementUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements/${elementNameEncoded}/types/${type}`;