Skip to content

Commit

Permalink
Visualize composite modifications (#538)
Browse files Browse the repository at this point in the history
Signed-off-by: Mathieu DEHARBE <[email protected]>
  • Loading branch information
Mathieu-Deharbe authored Nov 13, 2024
1 parent fe600d3 commit ce0e79a
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/components/app-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
LIGHT_THEME,
loginEn,
loginFr,
networkModificationsEn,
networkModificationsFr,
multipleSelectionDialogEn,
multipleSelectionDialogFr,
SnackbarProvider,
Expand Down Expand Up @@ -190,6 +192,7 @@ function getMuiTheme(theme: GsTheme, locale: GsLangUser) {
const messages: Record<GsLangUser, IntlConfig['messages']> = {
en: {
...messages_en,
...networkModificationsEn,
...importParamsEn,
...exportParamsEn,
...loginEn,
Expand All @@ -212,6 +215,7 @@ const messages: Record<GsLangUser, IntlConfig['messages']> = {
},
fr: {
...messages_fr,
...networkModificationsFr,
...importParamsFr,
...exportParamsFr,
...loginFr,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { SyntheticEvent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import { useForm } from 'react-hook-form';
import { useIntl } from 'react-intl';
import { List, ListItem } from '@mui/material';
import {
CustomMuiDialog,
FieldConstants,
NetworkModificationMetadata,
NO_SELECTION_FOR_COPY,
unscrollableDialogStyles,
useModificationLabelComputer,
useSnackMessage,
yupConfig as yup,
} from '@gridsuite/commons-ui';
import { yupResolver } from '@hookform/resolvers/yup';
import { AppState } from '../../../../redux/types';
import { useParameterState } from '../../use-parameters-dialog';
import { PARAM_LANGUAGE } from '../../../../utils/config-params';
import { fetchCompositeModificationContent, saveCompositeModification } from '../../../../utils/rest-api';
import CompositeModificationForm from './composite-modification-form';
import { setSelectionForCopy } from '../../../../redux/actions';

const schema = yup.object().shape({
[FieldConstants.NAME]: yup.string().trim().required('nameEmpty'),
});

const emptyFormData = (name?: string) => ({
[FieldConstants.NAME]: name,
});

interface FormData {
[FieldConstants.NAME]: string;
}

interface CompositeModificationDialogProps {
compositeModificationId: string;
open: boolean;
onClose: (event?: SyntheticEvent) => void;
titleId: string;
name: string;
broadcastChannel: BroadcastChannel;
}

export default function CompositeModificationDialog({
compositeModificationId,
open,
onClose,
titleId,
name,
broadcastChannel,
}: Readonly<CompositeModificationDialogProps>) {
const intl = useIntl();
const [languageLocal] = useParameterState(PARAM_LANGUAGE);
const [isFetching, setIsFetching] = useState(!!compositeModificationId);
const { snackError } = useSnackMessage();
const selectionForCopy = useSelector((state: AppState) => state.selectionForCopy);
const [modifications, setModifications] = useState<NetworkModificationMetadata[]>([]);
const dispatch = useDispatch();

const methods = useForm<FormData>({
defaultValues: emptyFormData(name),
resolver: yupResolver(schema),
});

const { computeLabel } = useModificationLabelComputer();
const getModificationLabel = (modif: NetworkModificationMetadata) => {
if (!modif) {
return null;
}
const labelData = {
...modif,
...computeLabel(modif),
};
return intl.formatMessage({ id: `network_modifications.${modif.type}` }, labelData);
};

const generateNetworkModificationsList = () => {
return (
<List sx={unscrollableDialogStyles.scrollableContent}>
{modifications &&
modifications.map((modification: NetworkModificationMetadata) => (
<Box key={modification.uuid}>
<ListItem>
<Box>{getModificationLabel(modification)}</Box>
</ListItem>
<Divider component="li" />
</Box>
))}
</List>
);
};

useEffect(() => {
setIsFetching(true);
fetchCompositeModificationContent(compositeModificationId)
.then((response) => {
if (response) {
setModifications(response);
}
})
.catch((error) => {
snackError({
messageTxt: error.message,
headerId: 'retrieveCompositeModificationError',
});
})
.finally(() => setIsFetching(false));
}, [compositeModificationId, name, snackError]);

const closeAndClear = (event?: SyntheticEvent) => {
onClose(event);
};

const onSubmit = (formData: FormData) => {
saveCompositeModification(compositeModificationId, formData[FieldConstants.NAME])
.then(() => {
if (selectionForCopy.sourceItemUuid === compositeModificationId) {
dispatch(setSelectionForCopy(NO_SELECTION_FOR_COPY));
broadcastChannel.postMessage({
NO_SELECTION_FOR_COPY,
});
}
closeAndClear();
})
.catch((errorMessage) => {
snackError({
messageTxt: errorMessage,
headerId: 'compositeModificationEditingError',
headerValues: { name },
});
});
};

return (
<CustomMuiDialog
open={open}
onClose={closeAndClear}
titleId={titleId}
onSave={onSubmit}
removeOptional
isDataFetching={isFetching}
language={languageLocal}
formSchema={schema}
formMethods={methods}
unscrollableFullHeight
>
{!isFetching && (
<Box sx={unscrollableDialogStyles.unscrollableContainer}>
<CompositeModificationForm />
{generateNetworkModificationsList()}
</Box>
)}
</CustomMuiDialog>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { UniqueNameInput, ElementType, FieldConstants } from '@gridsuite/commons-ui';
import { elementExists } from 'utils/rest-api';
import { useSelector } from 'react-redux';
import { AppState } from 'redux/types';
import Box from '@mui/material/Box';

export default function CompositeModificationForm() {
const activeDirectory = useSelector((state: AppState) => state.activeDirectory);
return (
<Box>
<UniqueNameInput
name={FieldConstants.NAME}
label="nameProperty"
elementType={ElementType.MODIFICATION}
activeDirectory={activeDirectory}
elementExists={elementExists}
/>
</Box>
);
}
34 changes: 31 additions & 3 deletions src/components/directory-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import {
import { Add as AddIcon } from '@mui/icons-material';
import { AgGridReact } from 'ag-grid-react';
import { SelectionForCopy } from '@gridsuite/commons-ui/dist/components/filter/filter.type';
import { setActiveDirectory, setSelectionForCopy } from '../redux/actions';
import { ContingencyListType, FilterType, NetworkModificationType } from '../utils/elementType';
import * as constants from '../utils/UIconstants';
import { ContingencyListType, FilterType } from '../utils/elementType';
import { setActiveDirectory, setSelectionForCopy } from '../redux/actions';
import { elementExists, getFilterById, updateElement } from '../utils/rest-api';
import ContentContextualMenu from './menus/content-contextual-menu';
import ContentToolbar from './toolbars/content-toolbar';
Expand All @@ -47,6 +47,7 @@ import NoContentDirectory from './no-content-directory';
import { CUSTOM_ROW_CLASS, DirectoryContentTable } from './directory-content-table';
import { useHighlightSearchedElement } from './search/use-highlight-searched-element';
import EmptyDirectory from './empty-directory';
import CompositeModificationDialog from './dialogs/network-modification/composite-modification/composite-modification-dialog';
import { AppState } from '../redux/types';

const circularProgressSize = '70px';
Expand Down Expand Up @@ -193,7 +194,17 @@ export default function DirectoryContent() {
setElementName('');
};

/** Filters dialog: window status value to edit Expert filters */
const [currentNetworkModificationId, setCurrentNetworkModificationId] = useState(null);
const handleCloseCompositeModificationDialog = () => {
setOpenDialog(constants.DialogsId.NONE);
setCurrentNetworkModificationId(null);
setActiveElement(null);
setElementName('');
};

/**
* Filters dialog: window status value to edit Expert filters
*/
const [currentExpertFilterId, setCurrentExpertFilterId] = useState(null);
const handleCloseExpertFilterDialog = () => {
setOpenDialog(constants.DialogsId.NONE);
Expand Down Expand Up @@ -375,6 +386,12 @@ export default function DirectoryContent() {
setOpenDialog(subtype);
}
break;
case ElementType.MODIFICATION:
if (subtype === NetworkModificationType.COMPOSITE.id) {
setCurrentNetworkModificationId(event.data.elementUuid);
setOpenDialog(subtype);
}
break;
default:
break;
}
Expand Down Expand Up @@ -496,6 +513,17 @@ export default function DirectoryContent() {
// TODO openDialog should also be aware of the dialog's type, not only its subtype, because
// if/when two different dialogs have the same subtype, this function will display the wrong dialog.
switch (openDialog) {
case NetworkModificationType.COMPOSITE.id:
return (
<CompositeModificationDialog
open
titleId="MODIFICATION"
compositeModificationId={currentNetworkModificationId ?? ''}
onClose={handleCloseCompositeModificationDialog}
name={name}
broadcastChannel={broadcastChannel}
/>
);
case ContingencyListType.CRITERIA_BASED.id:
return (
<CriteriaBasedEditionDialog
Expand Down
2 changes: 2 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"CSVFileCommentContingencyList4": "# N-2 composite,ID_line1|ID_generator1",
"contingencyListCreationError": "An error occurred while creating the contingency list: {name}",
"contingencyListEditingError": "An error occurred while editing the contingency list: {name}",
"compositeModificationEditingError": "An error occurred while editing the composite modification: {name}",
"contingencyListCreation": "contingencyListCreation",
"equipmentID": "Equipment ID",
"equipments": "Equipments",
Expand All @@ -112,6 +113,7 @@
"Min": "Min",
"Max": "Max",
"cannotRetrieveContingencyList": "Could not retrieve contingency list: ",
"retrieveCompositeModificationError": "Could not retrieve composite modification content: ",
"AddDescription": "Add a description (optional)",
"PropertyName": "Property name",
"getAppLinkError": "Error getting application link for type = {type}",
Expand Down
6 changes: 4 additions & 2 deletions src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
"edit": "Modifier",
"createNewContingencyList": "Créer une liste d'aléas",
"FORM": "Formulaire",
"editContingencyList": "Editer la liste d'aléas",
"editFilter": "Editer le filtre",
"editContingencyList": "Éditer la liste d'aléas",
"editFilter": "Éditer le filtre",
"STUDY": "Étude",
"SPREADSHEET_CONFIG": "Modèle de tableur",
"VOLTAGE_INIT_PARAMETERS": "Paramètres (Initialisation du plan de tension)",
Expand All @@ -89,6 +89,7 @@
"CSVFileCommentContingencyList4": "# N-2 mixte,ID_ligne1|ID_groupe1",
"contingencyListCreationError": "Une erreur est survenue lors de la création de la liste d'aléas : {name}",
"contingencyListEditingError": "Une erreur est survenue lors de l'édition de la liste d'aléas : {name}",
"compositeModificationEditingError": "Une erreur est survenue lors de l'édition de la modification composite : {name}",
"contingencyListCreation": "creationListeAleas",
"equipmentID": "ID d'ouvrage",
"equipments": "Ouvrages",
Expand All @@ -111,6 +112,7 @@
"Min": "Min",
"Max": "Max",
"cannotRetrieveContingencyList": "Erreur d'accès à la liste d'aléas : ",
"retrieveCompositeModificationError": "Erreur d'accès au contenu de la modification composite : ",
"AddDescription": "Ajouter une description (optionnel)",
"PropertyName": "Nom de la propriété",
"getAppLinkError": "Erreur lors de la récupération du lien vers l'application pour le type = {type}",
Expand Down
4 changes: 4 additions & 0 deletions src/utils/elementType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export const FilterType = {
EXPERT: { id: 'EXPERT', label: 'filter.expert' },
};

export const NetworkModificationType = {
COMPOSITE: { id: 'COMPOSITE_MODIFICATION', label: 'MODIFICATION' },
};

export const ContingencyListType = {
CRITERIA_BASED: { id: 'FORM', label: 'contingencyList.criteriaBased' },
EXPLICIT_NAMING: {
Expand Down
23 changes: 23 additions & 0 deletions src/utils/rest-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,29 @@ export interface CriteriaBasedEditionFormData {
[FieldConstants.CRITERIA_BASED]?: CriteriaBasedData;
}

/**
* Get the basic data of the network modifications contained in a composite modification
*/
export function fetchCompositeModificationContent(id: string) {
const url: string = `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/composite-modification/${id}/network-modifications`;

return backendFetchJson(url, {
method: 'get',
});
}

export function saveCompositeModification(id: string, name: string) {
const urlSearchParams = new URLSearchParams();
urlSearchParams.append('name', name);

const url: string = `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/composite-modification/${id}?${urlSearchParams.toString()}`;

return backendFetch(url, {
method: 'put',
headers: { 'Content-Type': 'application/json' },
});
}

/**
* Saves a Filter contingency list
* @returns {Promise<Response>}
Expand Down

0 comments on commit ce0e79a

Please sign in to comment.