Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visualize composite modifications #538

Merged
merged 26 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
208daf7
visualize
Mathieu-Deharbe Oct 8, 2024
79600b0
better css list and scroll system
Mathieu-Deharbe Oct 9, 2024
0f08acd
better display of the network modification label
Mathieu-Deharbe Oct 9, 2024
75e8df4
respect spaces and strong anchors
Mathieu-Deharbe Oct 9, 2024
86831c6
correct localisation imports
Mathieu-Deharbe Oct 10, 2024
5cca28c
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Oct 21, 2024
8be793a
post conflict solving in commons-ui
Mathieu-Deharbe Oct 21, 2024
0982dfa
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Oct 24, 2024
19b39f1
post auto review
Mathieu-Deharbe Oct 24, 2024
a3b92e0
allows to change composite modification name
Mathieu-Deharbe Oct 24, 2024
00b9ff9
change url
Mathieu-Deharbe Oct 24, 2024
62703c9
prettier
Mathieu-Deharbe Oct 24, 2024
9a373bc
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Oct 30, 2024
e212512
adapt to the new eslint standard
Mathieu-Deharbe Oct 30, 2024
dd6c719
corrections post Ghazwa review
Mathieu-Deharbe Oct 30, 2024
14107da
some renaming
Mathieu-Deharbe Oct 30, 2024
f263799
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Oct 31, 2024
ab6cad7
handle null case
Mathieu-Deharbe Oct 31, 2024
68d5494
better test syntax
Mathieu-Deharbe Oct 31, 2024
ca1f475
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Oct 31, 2024
618d372
updated endpoint
Mathieu-Deharbe Oct 31, 2024
0e06eeb
corrects getModificationLabel
Mathieu-Deharbe Oct 31, 2024
2ae94b8
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Nov 6, 2024
cd382e5
revert MODIFICATION localization
Mathieu-Deharbe Nov 6, 2024
3a62a60
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Nov 12, 2024
068a7c1
Merge branch 'main' into visualize-composite-modifications
Mathieu-Deharbe Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why those changes ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is how french must be written, it took me 5s, and it can't cause any bug.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok to keep it. But for lisibility of PR, tracability of the code, it's always harmful to change things that have nothing to do with the PR's subject

"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
Loading