From 74c351f9c0653b14832bf0aab18f3db863015613 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Thu, 21 Mar 2024 09:18:58 +0100 Subject: [PATCH 01/35] draft get /profiles ok --- src/components/App/app-top-bar.tsx | 12 +- src/pages/index.ts | 1 + src/pages/profiles/ProfilesPage.tsx | 239 ++++++++++++++++++++++++++++ src/pages/profiles/index.ts | 8 + src/pages/users/UsersPage.tsx | 16 ++ src/routes/router.tsx | 10 +- src/services/user-admin.ts | 18 +++ src/translations/en.json | 10 +- src/translations/fr.json | 10 +- 9 files changed, 318 insertions(+), 6 deletions(-) create mode 100644 src/pages/profiles/ProfilesPage.tsx create mode 100644 src/pages/profiles/index.ts diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index 56a95b9..33621bb 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -14,7 +14,7 @@ import { useState, } from 'react'; import { capitalize, Tab, TabProps, Tabs, useTheme } from '@mui/material'; -import { PeopleAlt } from '@mui/icons-material'; +import { ManageAccounts, PeopleAlt } from '@mui/icons-material'; import { logout, TopBar } from '@gridsuite/commons-ui'; import { useParameterState } from '../parameters'; import { @@ -56,6 +56,16 @@ const tabs = new Map([ key={`tab-${MainPaths.users}`} />, ], + [ + MainPaths.profiles, + } + label={} + href={`/${MainPaths.profiles}`} + value={MainPaths.profiles} + key={`tab-${MainPaths.profiles}`} + />, + ], ]); const AppTopBar: FunctionComponent = () => { diff --git a/src/pages/index.ts b/src/pages/index.ts index 10001a3..c748275 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -6,3 +6,4 @@ */ export * from './users'; +export * from './profiles'; diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx new file mode 100644 index 0000000..f6f0f39 --- /dev/null +++ b/src/pages/profiles/ProfilesPage.tsx @@ -0,0 +1,239 @@ +/* + * 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 { + FunctionComponent, + useCallback, + useMemo, + useRef, + useState, +} from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Grid, + InputAdornment, + Paper, + PaperProps, + TextField, +} from '@mui/material'; +import { AccountCircle, ManageAccounts } from '@mui/icons-material'; +import { + GridButton, + GridButtonDelete, + GridTable, + GridTableRef, +} from '../../components/Grid'; +import { UserAdminSrv, UserProfile } from '../../services'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { GetRowIdParams } from 'ag-grid-community/dist/lib/interfaces/iCallbackParams'; +import { TextFilterParams } from 'ag-grid-community/dist/lib/filter/provided/text/textFilter'; +import { ColDef } from 'ag-grid-community'; +import { SelectionChangedEvent } from 'ag-grid-community/dist/lib/events'; + +const defaultColDef: ColDef = { + editable: false, + resizable: true, + minWidth: 50, + cellRenderer: 'agAnimateSlideCellRenderer', //'agAnimateShowChangeCellRenderer' + showDisabledCheckboxes: true, + rowDrag: false, + sortable: true, +}; + +function getRowId(params: GetRowIdParams): string { + return params.data.name; +} + +const ProfilesPage: FunctionComponent = () => { + const intl = useIntl(); + const { snackError } = useSnackMessage(); + const gridRef = useRef>(null); + const gridContext = gridRef.current?.context; + + const columns = useMemo( + (): ColDef[] => [ + { + field: 'name', + cellDataType: 'text', + editable: true, + flex: 3, + lockVisible: true, + filter: true, + headerName: intl.formatMessage({ id: 'table.id' }), + headerTooltip: intl.formatMessage({ + id: 'profiles.table.id.description', + }), + headerCheckboxSelection: true, + filterParams: { + caseSensitive: false, + trimInput: true, + } as TextFilterParams, + }, + ], + [intl] + ); + + const [rowsSelection, setRowsSelection] = useState([]); + const deleteUsers = useCallback((): Promise | undefined => { + let subs = rowsSelection.map((user) => user.name); + return UserAdminSrv.deleteUsers(subs) + .catch((error) => + snackError({ + messageTxt: `Error while deleting user "${JSON.stringify( + subs + )}"${error.message && ':\n' + error.message}`, + headerId: 'users.table.error.delete', + }) + ) + .then(() => gridContext?.refresh?.()); + }, [gridContext, rowsSelection, snackError]); + const deleteUsersDisabled = useMemo( + () => rowsSelection.length <= 0, + [rowsSelection.length] + ); + + const addUser = useCallback( + (id: string) => { + UserAdminSrv.addUser(id) + .catch((error) => + snackError({ + messageTxt: `Error while adding user "${id}"${ + error.message && ':\n' + error.message + }`, + headerId: 'users.table.error.add', + }) + ) + .then(() => gridContext?.refresh?.()); + }, + [gridContext, snackError] + ); + const { handleSubmit, control, reset, clearErrors } = useForm<{ + user: string; + }>({ + defaultValues: { user: '' }, //need default not undefined value for html input, else react error at runtime + }); + const [open, setOpen] = useState(false); + const handleClose = () => { + setOpen(false); + reset(); + clearErrors(); + }; + const onSubmit: SubmitHandler<{ user: string }> = (data) => { + addUser(data.user.trim()); + handleClose(); + }; + const onSubmitForm = handleSubmit(onSubmit); + + return ( + + + + ref={gridRef} + dataLoader={UserAdminSrv.fetchProfiles} + columnDefs={columns} + defaultColDef={defaultColDef} + gridId="table-users" + getRowId={getRowId} + rowSelection="multiple" + onSelectionChanged={useCallback( + (event: SelectionChangedEvent) => + setRowsSelection(event.api.getSelectedRows() ?? []), + [] + )} + > + } + color="primary" + onClick={useCallback(() => setOpen(true), [])} + /> + + + ( + + )} + > + + + + + + + + ( + + } + type="text" + fullWidth + variant="standard" + inputMode="text" + InputProps={{ + startAdornment: ( + + + + ), + }} + error={fieldState?.invalid} + helperText={fieldState?.error?.message} + /> + )} + /> + + + + + + + + + ); +}; +export default ProfilesPage; + +/* + * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, + * so we must trick typescript check with a cast + */ +const PaperForm: FunctionComponent< + PaperProps<'form'> & { untypedProps?: PaperProps } +> = (props, context) => { + const { untypedProps, ...formProps } = props; + const othersProps = untypedProps as PaperProps<'form'>; //trust me ts + return ; +}; diff --git a/src/pages/profiles/index.ts b/src/pages/profiles/index.ts new file mode 100644 index 0000000..bff8823 --- /dev/null +++ b/src/pages/profiles/index.ts @@ -0,0 +1,8 @@ +/* + * 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/. + */ + +export { default as Profiles } from './ProfilesPage'; diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 0d1074f..65c83c3 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -79,6 +79,22 @@ const UsersPage: FunctionComponent = () => { trimInput: true, } as TextFilterParams, }, + { + field: 'profileName', + cellDataType: 'text', + flex: 1, + filter: true, + headerName: intl.formatMessage({ + id: 'users.table.profileName', + }), + headerTooltip: intl.formatMessage({ + id: 'users.table.profileName.description', + }), + filterParams: { + caseSensitive: false, + trimInput: true, + } as TextFilterParams, + }, { field: 'isAdmin', cellDataType: 'boolean', diff --git a/src/routes/router.tsx b/src/routes/router.tsx index f86f420..6da779b 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -33,7 +33,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { AppState } from '../redux/reducer'; import { AppsMetadataSrv, UserAdminSrv } from '../services'; import { App } from '../components/App'; -import { Users } from '../pages'; +import { Users, Profiles } from '../pages'; import ErrorPage from './ErrorPage'; import { updateUserManagerDestructured } from '../redux/actions'; import HomePage from './HomePage'; @@ -41,6 +41,7 @@ import { getErrorMessage } from '../utils/error'; export enum MainPaths { users = 'users', + profiles = 'profiles', } export function appRoutes(): RouteObject[] { @@ -60,6 +61,13 @@ export function appRoutes(): RouteObject[] { appBar_tab: MainPaths.users, }, }, + { + path: `/${MainPaths.profiles}`, + element: , + handle: { + appBar_tab: MainPaths.profiles, + }, + }, ], }, { diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 05079d2..4501bd5 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -43,6 +43,7 @@ export function fetchValidateUser(user: User): Promise { export type UserInfos = { sub: string; + profileName: string; isAdmin: boolean; }; @@ -95,3 +96,20 @@ export function addUser(sub: string): Promise { throw reason; }); } + +export type UserProfile = { + name: string; +}; + +export function fetchProfiles(): Promise { + console.debug(`Fetching list of profiles...`); + return backendFetchJson(`${USER_ADMIN_URL}/profiles`, { + headers: { + Accept: 'application/json', + }, + cache: 'default', + }).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} diff --git a/src/translations/en.json b/src/translations/en.json index ddc62de..14cdb60 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -10,6 +10,7 @@ "paramsRetrievingError": "An error occurred while retrieving the parameters", "appBar.tabs.users": "Users", + "appBar.tabs.profiles": "Profiles", "appBar.tabs.connections": "Connections", "table.noRows": "No data", @@ -25,14 +26,19 @@ "table.bool.no": "No", "table.bool.unknown": "Unknown", - "users.table.id.description": "Identifiant de l'utilisateur", + "users.table.id.description": "User identifier", "users.table.isAdmin": "Admin", "users.table.isAdmin.description": "The users is an administrator of GridSuite", + "users.table.profileName": "Profile", + "users.table.profileName.description": "The user's profile", "users.table.error.delete": "Error while deleting user", "users.table.error.add": "Error while adding user", "users.table.toolbar.add": "Add user", "users.table.toolbar.add.label": "Add a user", "users.form.title": "Add a user", "users.form.content": "Please fill in new user data.", - "users.form.field.username.label": "User ID" + "users.form.field.username.label": "User ID", + + "profiles.table.toolbar.add": "Add profile", + "profiles.table.id.description": "Profile name" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 8f67967..4c07ae1 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -10,6 +10,7 @@ "paramsRetrievingError": "Une erreur est survenue lors de la récupération des paramètres", "appBar.tabs.users": "Utilisateurs", + "appBar.tabs.profiles": "Profils", "appBar.tabs.connections": "Connexions", "table.noRows": "No data", @@ -28,11 +29,16 @@ "users.table.id.description": "Identifiant de l'utilisateur", "users.table.isAdmin": "Admin", "users.table.isAdmin.description": "L'utilisateur est administrateur de GridSuite", + "users.table.profileName": "Profil", + "users.table.profileName.description": "Nom du profil associé à l'utilisateur", "users.table.error.delete": "Erreur pendant la suppression de l'utilisateur", "users.table.error.add": "Erreur pendant l'ajout de l'utilisateur", - "users.table.toolbar.add": "Ajouter utilisateur", "users.table.toolbar.add.label": "Ajouter un utilisateur", + "users.table.toolbar.add": "Ajouter utilisateur", "users.form.title": "Ajouter un utilisateur", "users.form.content": "Veuillez renseigner les informations de l'utilisateur.", - "users.form.field.username.label": "ID utilisateur" + "users.form.field.username.label": "ID utilisateur", + + "profiles.table.toolbar.add": "Ajouter profil", + "profiles.table.id.description": "Nom du profil" } From 4b8af241bb693c1ce12abf03494e474465d08574 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Sun, 24 Mar 2024 17:35:13 +0100 Subject: [PATCH 02/35] add/remove profile ok, update with pbs --- package-lock.json | 20 ++- package.json | 2 +- src/components/common/custom-mui-dialog.tsx | 120 ++++++++++++++ src/components/common/yup-config.js | 23 +++ src/components/modify-element-selection.tsx | 151 ++++++++++++++++++ src/pages/profiles/ProfilesPage.tsx | 93 +++++++---- .../profile-modification-dialog.jsx | 122 ++++++++++++++ .../profile-modification-form.jsx | 76 +++++++++ src/pages/users/UsersPage.tsx | 3 +- src/services/directory.ts | 74 +++++++++ src/services/explore.ts | 44 +++++ src/services/user-admin.ts | 77 +++++++++ src/translations/en.json | 22 ++- src/translations/fr.json | 21 ++- 14 files changed, 808 insertions(+), 40 deletions(-) create mode 100644 src/components/common/custom-mui-dialog.tsx create mode 100644 src/components/common/yup-config.js create mode 100644 src/components/modify-element-selection.tsx create mode 100644 src/pages/profiles/modification/profile-modification-dialog.jsx create mode 100644 src/pages/profiles/modification/profile-modification-form.jsx create mode 100644 src/services/directory.ts create mode 100644 src/services/explore.ts diff --git a/package-lock.json b/package-lock.json index b839112..0dff4f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@gridsuite/commons-ui": "^0.46.0", + "@gridsuite/commons-ui": "^0.50.0", "@hookform/resolvers": "^3.3.1", "@mui/icons-material": "^5.5.1", "@mui/lab": "^5.0.0-alpha.75", @@ -2484,9 +2484,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.46.0.tgz", - "integrity": "sha512-0Av3FNXXo7SXwtDqnwah/2AVBtff/hVVZTKpIB8LvklGFQvQNv8UN1av3UzBja54dd51TXYyZisG/+qG1uw+1w==", + "version": "0.50.1", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.50.1.tgz", + "integrity": "sha512-uJOBThra+6U9eMGml3z2QYuthldkSpH4MPi3+xxw50/gKKZLmflY2+o4DD3hcxLqwUQbdDbciuV2TXVdKM2dvg==", "dependencies": { "autosuggest-highlight": "^3.2.0", "clsx": "^1.0.4", @@ -2496,7 +2496,8 @@ "prop-types": "^15.7.2", "react-csv-downloader": "^2.7.0", "react-request-fullscreen": "^1.1.2", - "react-virtualized": "^9.21.2" + "react-virtualized": "^9.22.5", + "uuid": "^3.4.0" }, "engines": { "node": ">=18", @@ -2518,6 +2519,15 @@ "yup": "^1.0.0" } }, + "node_modules/@gridsuite/commons-ui/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/@hookform/resolvers": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.1.tgz", diff --git a/package.json b/package.json index 3b146c9..2f5920b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", - "@gridsuite/commons-ui": "^0.46.0", + "@gridsuite/commons-ui": "^0.50.0", "@hookform/resolvers": "^3.3.1", "@mui/icons-material": "^5.5.1", "@mui/lab": "^5.0.0-alpha.75", diff --git a/src/components/common/custom-mui-dialog.tsx b/src/components/common/custom-mui-dialog.tsx new file mode 100644 index 0000000..5f8cf61 --- /dev/null +++ b/src/components/common/custom-mui-dialog.tsx @@ -0,0 +1,120 @@ +/** + * 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/. + */ + +// TODO: copy from grid-explore => move it to commons-ui + +import React, { FunctionComponent } from 'react'; +import { FieldErrors, FormProvider } from 'react-hook-form'; +import { FormattedMessage } from 'react-intl'; +import { + DialogActions, + DialogContent, + Grid, + LinearProgress, + Dialog, + DialogTitle, +} from '@mui/material'; +import { CancelButton, SubmitButton } from '@gridsuite/commons-ui'; + +interface ICustomMuiDialog { + open: boolean; + formSchema: any; + formMethods: any; + onClose: (event: React.MouseEvent) => void; + onSave: (data: any) => void; + onValidationError?: (errors: FieldErrors) => void; + titleId: string; + disabledSave?: boolean; + removeOptional?: boolean; + onCancel?: () => void; + children: React.ReactNode; + isDataFetching?: boolean; +} + +const styles = { + dialogPaper: { + '.MuiDialog-paper': { + width: 'auto', + minWidth: '800px', + margin: 'auto', + }, + }, +}; + +const CustomMuiDialog: FunctionComponent = ({ + open, + formSchema, + formMethods, + onClose, + onSave, + isDataFetching = false, + onValidationError, + titleId, + disabledSave, + removeOptional = false, + onCancel, + children, +}) => { + const { handleSubmit } = formMethods; + + const handleCancel = (event: React.MouseEvent) => { + onCancel && onCancel(); + onClose(event); + }; + + const handleClose = (event: React.MouseEvent, reason?: string) => { + if (reason === 'backdropClick' && onCancel) { + onCancel(); + } + onClose(event); + }; + + const handleValidate = (data: any) => { + onSave(data); + onClose(data); + }; + + const handleValidationError = (errors: FieldErrors) => { + onValidationError && onValidationError(errors); + }; + + return ( + + + {isDataFetching && } + + + + + + {children} + + + + + + + ); +}; + +export default CustomMuiDialog; diff --git a/src/components/common/yup-config.js b/src/components/common/yup-config.js new file mode 100644 index 0000000..b918c6a --- /dev/null +++ b/src/components/common/yup-config.js @@ -0,0 +1,23 @@ +/** + * 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 * as yup from 'yup'; + +yup.setLocale({ + mixed: { + required: 'YupRequired', + notType: ({ type }) => { + if (type === 'number') { + return 'YupNotTypeNumber'; + } else { + return 'YupNotTypeDefault'; + } + }, + }, +}); + +export default yup; diff --git a/src/components/modify-element-selection.tsx b/src/components/modify-element-selection.tsx new file mode 100644 index 0000000..2a234ca --- /dev/null +++ b/src/components/modify-element-selection.tsx @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2023, 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/. + */ + +// TODO: copy from grid-explore => move it to commons-ui + +import React, { useEffect, useState } from 'react'; +import { Button, Grid, Typography } from '@mui/material'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { DirectoryItemSelector } from '@gridsuite/commons-ui'; +import { useController } from 'react-hook-form'; +import { + fetchDirectoryContent, + fetchPath, + fetchRootFolders, +} from 'services/directory'; +import { fetchElementsInfos } from 'services/explore'; + +// TODO remove when available in commons-ui +export enum ElementType { + DIRECTORY = 'DIRECTORY', + STUDY = 'STUDY', + FILTER = 'FILTER', + CONTINGENCY_LIST = 'CONTINGENCY_LIST', + MODIFICATION = 'MODIFICATION', + CASE = 'CASE', + VOLTAGE_INIT_PARAMETERS = 'VOLTAGE_INIT_PARAMETERS', + SECURITY_ANALYSIS_PARAMETERS = 'SECURITY_ANALYSIS_PARAMETERS', + LOADFLOW_PARAMETERS = 'LOADFLOW_PARAMETERS', + SENSITIVITY_PARAMETERS = 'SENSITIVITY_PARAMETERS', +} + +export interface ModifyElementSelectionProps { + elementType: ElementType; + formParamId: string; + formParamFullName: string; + dialogOpeningButtonLabel: string; + dialogTitleLabel: string; + dialogMessageLabel: string; + noElementMessageLabel?: string; +} + +const ModifyElementSelection: React.FunctionComponent< + ModifyElementSelectionProps +> = (props) => { + const intl = useIntl(); + + const [open, setOpen] = useState(false); + const [selectedElementName, setSelectedElementName] = useState(''); + + const ctlParamId = useController({ + name: props.formParamId, + }); + const ctlParamFullName = useController({ + name: props.formParamFullName, + }); + + useEffect(() => { + console.log('DBR useEff elementUuid=', ctlParamId.field.value); + if (ctlParamId.field.value) { + fetchPath(ctlParamId.field.value).then((res: any) => { + console.log('DBR useEff fetchPath res=', res); + setSelectedElementName( + res + .map((element: any) => element.elementName.trim()) + .reverse() + .join('/') + ); + }); + //.catch((error) => setSelectedElementName('error')); + } + }, [ctlParamId.field.value]); + + const handleSelectFolder = () => { + setOpen(true); + }; + + const handleClose = (selection: any) => { + console.log('DBR handleClose selected=', selection, selectedElementName); + if (selection.length) { + console.log( + 'DBR handleClose onChange=', + selection[0]?.id, + selectedElementName + ); + ctlParamId.field.onChange(selection[0]?.id); + ctlParamFullName.field.onChange(selectedElementName); + } + setOpen(false); + }; + + return ( + + + + {selectedElementName + ? selectedElementName + : props?.noElementMessageLabel + ? intl.formatMessage({ + id: props.noElementMessageLabel, + }) + : ''} + + + + ); +}; + +export default ModifyElementSelection; diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index f6f0f39..13ba2fa 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -26,7 +26,7 @@ import { PaperProps, TextField, } from '@mui/material'; -import { AccountCircle, ManageAccounts } from '@mui/icons-material'; +import { ManageAccounts } from '@mui/icons-material'; import { GridButton, GridButtonDelete, @@ -39,7 +39,12 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { GetRowIdParams } from 'ag-grid-community/dist/lib/interfaces/iCallbackParams'; import { TextFilterParams } from 'ag-grid-community/dist/lib/filter/provided/text/textFilter'; import { ColDef } from 'ag-grid-community'; -import { SelectionChangedEvent } from 'ag-grid-community/dist/lib/events'; +import { + RowDoubleClickedEvent, + SelectionChangedEvent, +} from 'ag-grid-community/dist/lib/events'; +import ProfileModificationDialog from './modification/profile-modification-dialog'; +import { UUID } from 'crypto'; const defaultColDef: ColDef = { editable: false, @@ -49,10 +54,11 @@ const defaultColDef: ColDef = { showDisabledCheckboxes: true, rowDrag: false, sortable: true, + //enableCellChangeFlash: false, }; function getRowId(params: GetRowIdParams): string { - return params.data.name; + return params.data.id; } const ProfilesPage: FunctionComponent = () => { @@ -60,17 +66,19 @@ const ProfilesPage: FunctionComponent = () => { const { snackError } = useSnackMessage(); const gridRef = useRef>(null); const gridContext = gridRef.current?.context; + const [openProfileModificationDialog, setOpenProfileModificationDialog] = + useState(false); + const [editingProfileId, setEditingProfileId] = useState(); const columns = useMemo( (): ColDef[] => [ { field: 'name', cellDataType: 'text', - editable: true, flex: 3, lockVisible: true, filter: true, - headerName: intl.formatMessage({ id: 'table.id' }), + headerName: intl.formatMessage({ id: 'profiles.table.id' }), headerTooltip: intl.formatMessage({ id: 'profiles.table.id.description', }), @@ -79,39 +87,40 @@ const ProfilesPage: FunctionComponent = () => { caseSensitive: false, trimInput: true, } as TextFilterParams, + editable: false, }, ], [intl] ); const [rowsSelection, setRowsSelection] = useState([]); - const deleteUsers = useCallback((): Promise | undefined => { - let subs = rowsSelection.map((user) => user.name); - return UserAdminSrv.deleteUsers(subs) + const deleteProfiles = useCallback((): Promise | undefined => { + let profileNames = rowsSelection.map((userProfile) => userProfile.name); + return UserAdminSrv.deleteProfiles(profileNames) .catch((error) => snackError({ - messageTxt: `Error while deleting user "${JSON.stringify( - subs + messageTxt: `Error while deleting profiles "${JSON.stringify( + profileNames )}"${error.message && ':\n' + error.message}`, - headerId: 'users.table.error.delete', + headerId: 'profiles.table.error.delete', }) ) .then(() => gridContext?.refresh?.()); }, [gridContext, rowsSelection, snackError]); - const deleteUsersDisabled = useMemo( + const deleteProfilesDisabled = useMemo( () => rowsSelection.length <= 0, [rowsSelection.length] ); - const addUser = useCallback( - (id: string) => { - UserAdminSrv.addUser(id) + const addProfile = useCallback( + (name: string) => { + UserAdminSrv.addProfile(name) .catch((error) => snackError({ - messageTxt: `Error while adding user "${id}"${ + messageTxt: `Error while adding profile "${name}"${ error.message && ':\n' + error.message }`, - headerId: 'users.table.error.add', + headerId: 'profiles.table.error.add', }) ) .then(() => gridContext?.refresh?.()); @@ -119,9 +128,9 @@ const ProfilesPage: FunctionComponent = () => { [gridContext, snackError] ); const { handleSubmit, control, reset, clearErrors } = useForm<{ - user: string; + name: string; }>({ - defaultValues: { user: '' }, //need default not undefined value for html input, else react error at runtime + defaultValues: { name: '' }, //need default not undefined value for html input, else react error at runtime }); const [open, setOpen] = useState(false); const handleClose = () => { @@ -129,23 +138,47 @@ const ProfilesPage: FunctionComponent = () => { reset(); clearErrors(); }; - const onSubmit: SubmitHandler<{ user: string }> = (data) => { - addUser(data.user.trim()); + const onSubmit: SubmitHandler<{ name: string }> = (data) => { + addProfile(data.name.trim()); handleClose(); }; const onSubmitForm = handleSubmit(onSubmit); + const handleCloseProfileModificationDialog = () => { + setOpenProfileModificationDialog(false); + setEditingProfileId(undefined); + reset(); + //gridContext?.refresh?.() + }; + + const onRowDoubleClicked = useCallback( + (event: RowDoubleClickedEvent) => { + if (event.data) { + setEditingProfileId(event.data.id); + setOpenProfileModificationDialog(true); + } + }, + [] + ); + return ( + ref={gridRef} dataLoader={UserAdminSrv.fetchProfiles} columnDefs={columns} defaultColDef={defaultColDef} - gridId="table-users" + gridId="table-profiles" getRowId={getRowId} rowSelection="multiple" + onRowDoubleClicked={onRowDoubleClicked} onSelectionChanged={useCallback( (event: SelectionChangedEvent) => setRowsSelection(event.api.getSelectedRows() ?? []), @@ -153,15 +186,15 @@ const ProfilesPage: FunctionComponent = () => { )} > } color="primary" onClick={useCallback(() => setOpen(true), [])} /> { )} > - + - + ( @@ -192,7 +225,7 @@ const ProfilesPage: FunctionComponent = () => { required margin="dense" label={ - + } type="text" fullWidth @@ -201,7 +234,7 @@ const ProfilesPage: FunctionComponent = () => { InputProps={{ startAdornment: ( - + ), }} diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.jsx new file mode 100644 index 0000000..fae6665 --- /dev/null +++ b/src/pages/profiles/modification/profile-modification-dialog.jsx @@ -0,0 +1,122 @@ +/* + * 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 ProfileModificationForm, { + LF_PARAM_FULL_NAME, + LF_PARAM_ID, + PROFILE_NAME, +} from './profile-modification-form'; +import yup from 'components/common/yup-config'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useForm } from 'react-hook-form'; +import { useEffect, useState } from 'react'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import { getProfile, modifyProfile } from 'services/user-admin'; +import PropTypes from 'prop-types'; +import CustomMuiDialog from '../../../components/common/custom-mui-dialog'; + +export const FetchStatus = { + IDLE: 'IDLE', + FETCHING: 'FETCHING', + FETCH_SUCCESS: 'FETCH_SUCCESS', + FETCH_ERROR: 'FETCH_ERROR', +}; + +const ProfileModificationDialog = ({ + profileId, + open, + onClose, + gridContext, +}) => { + const { snackError } = useSnackMessage(); + const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + + const formSchema = yup + .object() + .shape({ + [PROFILE_NAME]: yup.string().trim().required('nameEmpty'), + [LF_PARAM_ID]: yup.string().optional(), + [LF_PARAM_FULL_NAME]: yup.string().optional(), + }) + .required(); + + const formMethods = useForm({ + resolver: yupResolver(formSchema), + }); + + const { reset } = formMethods; + + const onSubmit = (profileFormData) => { + console.log('DBR submit', profileFormData); + modifyProfile( + profileId, + profileFormData[PROFILE_NAME], + profileFormData[LF_PARAM_ID], + profileFormData[LF_PARAM_FULL_NAME] + ? profileFormData[LF_PARAM_FULL_NAME] + : 'TODO' + ) + .catch((error) => { + snackError({ + messageTxt: error.message, + headerId: 'profiles.form.modification.updateError', + }); + }) + .then(gridContext?.refresh?.()); + }; + + useEffect(() => { + if (profileId && open) { + setDataFetchStatus(FetchStatus.FETCHING); + getProfile(profileId) + .then((response) => { + console.log('DBR getProfile', response); + setDataFetchStatus(FetchStatus.FETCH_SUCCESS); + reset({ + [PROFILE_NAME]: response.name, + [LF_PARAM_ID]: response.loadFlowParameter?.parameterId, + [LF_PARAM_FULL_NAME]: + response.loadFlowParameter?.fullName, + }); + }) + .catch((error) => { + setDataFetchStatus(FetchStatus.FETCH_ERROR); + snackError({ + messageTxt: error.message, + headerId: 'profiles.form.modification.readError', + }); + }); + } + }, [profileId, open, reset, snackError]); + + //const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; + + //{isDataReady && } + + return ( + + + + ); +}; + +ProfileModificationDialog.propTypes = { + profileId: PropTypes.string, + open: PropTypes.bool, + onClose: PropTypes.func.isRequired, +}; + +export default ProfileModificationDialog; diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.jsx new file mode 100644 index 0000000..5bde8de --- /dev/null +++ b/src/pages/profiles/modification/profile-modification-form.jsx @@ -0,0 +1,76 @@ +/* + * 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 { TextInput } from '@gridsuite/commons-ui'; +import Grid from '@mui/material/Grid'; +import ModifyElementSelection, { + ElementType, +} from '../../../components/modify-element-selection'; +import { FormattedMessage } from 'react-intl'; +import { styled } from '@mui/system'; + +export const PROFILE_NAME = 'name'; +export const LF_PARAM_ID = 'lfParamId'; +export const LF_PARAM_FULL_NAME = 'lfFullName'; + +export const GridSection = ({ + title, + heading = '3', + size = 12, + customStyle = {}, +}) => { + const CustomTag = styled(`h${heading}`)(customStyle); + return ( + + + + + + + + ); +}; + +const ProfileModificationForm = () => { + return ( + <> + + + + + + + + + + + + + ); +}; + +export default ProfileModificationForm; diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 65c83c3..7349816 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -69,7 +69,7 @@ const UsersPage: FunctionComponent = () => { flex: 3, lockVisible: true, filter: true, - headerName: intl.formatMessage({ id: 'table.id' }), + headerName: intl.formatMessage({ id: 'users.table.id' }), headerTooltip: intl.formatMessage({ id: 'users.table.id.description', }), @@ -94,6 +94,7 @@ const UsersPage: FunctionComponent = () => { caseSensitive: false, trimInput: true, } as TextFilterParams, + editable: true, }, { field: 'isAdmin', diff --git a/src/services/directory.ts b/src/services/directory.ts new file mode 100644 index 0000000..a708eac --- /dev/null +++ b/src/services/directory.ts @@ -0,0 +1,74 @@ +/* + * 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 { backendFetchJson, getRestBase } from '../utils/api-rest'; +import { UUID } from 'crypto'; +import { ElementType } from '../components/modify-element-selection'; + +const DIRECTORY_URL = `${getRestBase()}/directory/v1`; + +export type ElementAttributes = { + elementUuid: UUID; + elementName: string; + type: string; +}; + +export function fetchPath(elementUuid: UUID): Promise { + console.debug(`Fetching element and its parents info...`); + return backendFetchJson(`${DIRECTORY_URL}/elements/${elementUuid}/path`, { + headers: { + Accept: 'application/json', + }, + cache: 'default', + }).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} + +export function fetchRootFolders( + types: ElementType[] +): Promise { + console.info('Fetching Root Directories...'); + const urlSearchParams = new URLSearchParams( + types?.length ? types.map((param) => ['elementTypes', param]) : [] + ); + return backendFetchJson( + `${DIRECTORY_URL}/root-directories?${urlSearchParams}`, + { + headers: { + Accept: 'application/json', + }, + cache: 'default', + } + ).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} + +export function fetchDirectoryContent( + directoryUuid: UUID, + types: ElementType[] +): Promise { + console.info('Fetching Directory content...'); + const urlSearchParams = new URLSearchParams( + types?.length ? types.map((param) => ['elementTypes', param]) : [] + ); + return backendFetchJson( + `${DIRECTORY_URL}/directories/${directoryUuid}/elements?${urlSearchParams}`, + { + headers: { + Accept: 'application/json', + }, + cache: 'default', + } + ).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} diff --git a/src/services/explore.ts b/src/services/explore.ts new file mode 100644 index 0000000..c309237 --- /dev/null +++ b/src/services/explore.ts @@ -0,0 +1,44 @@ +/* + * 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 { backendFetchJson, getRestBase } from '../utils/api-rest'; +import { UUID } from 'crypto'; +import { ElementType } from '../components/modify-element-selection'; + +const EXPLORE_URL = `${getRestBase()}/explore/v1`; + +export type ElementAttributes = { + elementUuid: UUID; + elementName: string; + type: string; +}; + +export function fetchElementsInfos( + ids: UUID[], + elementTypes: ElementType[] +): Promise { + console.info('Fetching elements metadata...'); + const tmp = ids?.filter((id) => id); + const idsParams = tmp?.length ? tmp.map((id) => ['ids', id]) : []; + const elementTypesParams = elementTypes?.length + ? elementTypes.map((type) => ['elementTypes', type]) + : []; + const params = [...idsParams, ...elementTypesParams]; + const urlSearchParams = new URLSearchParams(params).toString(); + return backendFetchJson( + `${EXPLORE_URL}/explore/elements/metadata?${urlSearchParams}`, + { + headers: { + Accept: 'application/json', + }, + cache: 'default', + } + ).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 4501bd5..47c9346 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -8,6 +8,7 @@ import { backendFetch, backendFetchJson, getRestBase } from '../utils/api-rest'; import { extractUserSub, getToken, getUser } from '../utils/api'; import { User } from '../utils/auth'; +import { UUID } from 'crypto'; const USER_ADMIN_URL = `${getRestBase()}/user-admin/v1`; @@ -97,8 +98,16 @@ export function addUser(sub: string): Promise { }); } +export type ParameterInfos = { + id: UUID; + parameterId: UUID; + fullName: string; +}; + export type UserProfile = { + id: UUID; name: string; + loadFlowParameter: ParameterInfos; }; export function fetchProfiles(): Promise { @@ -113,3 +122,71 @@ export function fetchProfiles(): Promise { throw reason; }) as Promise; } + +export function getProfile(profileId: UUID): Promise { + console.debug(`Fetching a profile...`); + return backendFetchJson(`${USER_ADMIN_URL}/profiles/${profileId}`, { + headers: { + Accept: 'application/json', + }, + cache: 'default', + }).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} + +export function modifyProfile( + profileId: UUID, + name: string, + lfParamId: UUID, + lfParamFullName: string +) { + console.debug(`Updating a profile...`); + + return backendFetch(`${USER_ADMIN_URL}/profiles/${profileId}`, { + method: 'PUT', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: profileId, + name: name, + loadFlowParameter: lfParamId + ? { + parameterId: lfParamId, + fullName: lfParamFullName, + } + : null, + }), + }); +} + +export function addProfile(name: string): Promise { + console.debug(`Creating user profile "${name}"...`); + return backendFetch(`${USER_ADMIN_URL}/profiles/${name}`, { + method: 'post', + }) + .then((response: Response) => undefined) + .catch((reason) => { + console.error(`Error while pushing the data : ${reason}`); + throw reason; + }); +} + +export function deleteProfiles(names: string[]): Promise { + console.debug(`Deleting profiles "${JSON.stringify(names)}"...`); + return backendFetch(`${USER_ADMIN_URL}/profiles`, { + method: 'delete', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(names), + }) + .then((response: Response) => undefined) + .catch((reason) => { + console.error(`Error while deleting the servers data : ${reason}`); + throw reason; + }); +} diff --git a/src/translations/en.json b/src/translations/en.json index 14cdb60..193511c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5,7 +5,9 @@ "close": "Close", "ok": "OK", "cancel": "Cancel", + "validate": "Validate", "parameters": "Parameters", + "nameEmpty": "The name is empty", "paramsChangingError": "An error occurred when changing the parameters", "paramsRetrievingError": "An error occurred while retrieving the parameters", @@ -14,7 +16,6 @@ "appBar.tabs.connections": "Connections", "table.noRows": "No data", - "table.id": "ID", "table.error.retrieve": "Error while retrieving data", "table.toolbar.reset": "Reset", "table.toolbar.reset.label": "Reset table columns & filters", @@ -26,6 +27,7 @@ "table.bool.no": "No", "table.bool.unknown": "Unknown", + "users.table.id": "ID", "users.table.id.description": "User identifier", "users.table.isAdmin": "Admin", "users.table.isAdmin.description": "The users is an administrator of GridSuite", @@ -40,5 +42,21 @@ "users.form.field.username.label": "User ID", "profiles.table.toolbar.add": "Add profile", - "profiles.table.id.description": "Profile name" + "profiles.table.toolbar.add.label": "Add a profile", + "profiles.form.title": "Add a profile", + "profiles.form.content": "Please fill in new profile data.", + "profiles.form.field.profilename.label": "Profile name", + "profiles.table.id": "Name", + "profiles.table.id.description": "Profile name", + "profiles.form.modification.title": "Edit profile", + "profiles.table.error.add": "Error while adding profile", + "profiles.table.error.delete": "Error while adding profile", + + "profiles.form.modification.noSelectedParameter": "No parameter selected.", + "profiles.form.modification.parameterSelectionButton": "Choose parameter", + "profiles.form.modification.parameterSelection.dialog.title": "Choose a parameter", + "profiles.form.modification.parameterSelection.dialog.message": "Please choose a parameter", + "profiles.form.modification.loadFlowSectionName": "Loadflow parameter", + "profiles.form.modification.readError": "Error while reading the profile", + "profiles.form.modification.updateError": "Error while updating the profile" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 4c07ae1..eb2c14c 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -5,7 +5,9 @@ "close": "Fermer", "ok": "OK", "cancel": "Annuler", + "validate": "Valider", "parameters": "Paramètres", + "nameEmpty": "Le nom est vide", "paramsChangingError": "Une erreur est survenue lors de la modification des paramètres", "paramsRetrievingError": "Une erreur est survenue lors de la récupération des paramètres", @@ -26,6 +28,7 @@ "table.bool.no": "Non", "table.bool.unknown": "Inconnu", + "users.table.id": "ID", "users.table.id.description": "Identifiant de l'utilisateur", "users.table.isAdmin": "Admin", "users.table.isAdmin.description": "L'utilisateur est administrateur de GridSuite", @@ -40,5 +43,21 @@ "users.form.field.username.label": "ID utilisateur", "profiles.table.toolbar.add": "Ajouter profil", - "profiles.table.id.description": "Nom du profil" + "profiles.table.toolbar.add.label": "Ajouter un profil", + "profiles.form.title": "Ajouter un profil", + "profiles.form.content": "Veuillez renseigner les informations du profil.", + "profiles.form.field.profilename.label": "Nom du profil", + "profiles.table.id": "Nom", + "profiles.table.id.description": "Nom du profil", + "profiles.table.error.add": "Erreur pendant l'ajout du profil", + "profiles.table.error.delete": "Erreur pendant la suppression de profil", + + "profiles.form.modification.title": "Modifier profil", + "profiles.form.modification.noSelectedParameter": "Auncun paramètre selectionné.", + "profiles.form.modification.parameterSelectionButton": "Choisir paramètre", + "profiles.form.modification.parameterSelection.dialog.title": "Choisir un ", + "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir un paramètre", + "profiles.form.modification.loadFlowSectionName": "Paramètre de loadflow", + "profiles.form.modification.readError": "Erreur lors de la lecture du profil", + "profiles.form.modification.updateError": "Erreur lors de la modification du profil" } From 0ff936d5fb28907444b96ad648a0e31fa4b2d2f7 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Sun, 24 Mar 2024 21:14:50 +0100 Subject: [PATCH 03/35] set/reset profile to user --- src/components/modify-element-selection.tsx | 6 ++- src/pages/users/UsersPage.tsx | 54 +++++++++++++++++++-- src/services/user-admin.ts | 13 +++++ src/translations/en.json | 1 + src/translations/fr.json | 1 + 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/components/modify-element-selection.tsx b/src/components/modify-element-selection.tsx index 2a234ca..3fa9ccb 100644 --- a/src/components/modify-element-selection.tsx +++ b/src/components/modify-element-selection.tsx @@ -79,7 +79,11 @@ const ModifyElementSelection: React.FunctionComponent< }; const handleClose = (selection: any) => { - console.log('DBR handleClose selected=', selection, selectedElementName); + console.log( + 'DBR handleClose selected=', + selection, + selectedElementName + ); if (selection.length) { console.log( 'DBR handleClose onChange=', diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 7349816..dac5fc8 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -8,6 +8,7 @@ import { FunctionComponent, useCallback, + useEffect, useMemo, useRef, useState, @@ -33,13 +34,16 @@ import { GridTable, GridTableRef, } from '../../components/Grid'; -import { UserAdminSrv, UserInfos } from '../../services'; +import { UserAdminSrv, UserInfos, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { GetRowIdParams } from 'ag-grid-community/dist/lib/interfaces/iCallbackParams'; import { TextFilterParams } from 'ag-grid-community/dist/lib/filter/provided/text/textFilter'; import { ColDef, ICheckboxCellRendererParams } from 'ag-grid-community'; -import { SelectionChangedEvent } from 'ag-grid-community/dist/lib/events'; +import { + CellEditingStoppedEvent, + SelectionChangedEvent, +} from 'ag-grid-community/dist/lib/events'; const defaultColDef: ColDef = { editable: false, @@ -60,6 +64,22 @@ const UsersPage: FunctionComponent = () => { const { snackError } = useSnackMessage(); const gridRef = useRef>(null); const gridContext = gridRef.current?.context; + const [profileNameOptions, setprofileNameOptions] = useState([]); + + useEffect(() => { + UserAdminSrv.fetchProfiles().then((allProfiles: UserProfile[]) => { + console.log('DBR useEff fetchProfiles', allProfiles); + let profiles: string[] = [ + intl.formatMessage({ id: 'users.table.profile.none' }), + ]; + if (allProfiles?.length) { + profiles = profiles.concat( + allProfiles.map((p: UserProfile) => p.name) + ); + } + setprofileNameOptions(profiles); + }); + }, [intl]); const columns = useMemo( (): ColDef[] => [ @@ -95,6 +115,12 @@ const UsersPage: FunctionComponent = () => { trimInput: true, } as TextFilterParams, editable: true, + cellEditor: 'agSelectCellEditor', + cellEditorParams: () => { + return { + values: profileNameOptions, + }; + }, }, { field: 'isAdmin', @@ -116,7 +142,7 @@ const UsersPage: FunctionComponent = () => { initialSort: 'asc', }, ], - [intl] + [intl, profileNameOptions] ); const [rowsSelection, setRowsSelection] = useState([]); @@ -170,6 +196,27 @@ const UsersPage: FunctionComponent = () => { }; const onSubmitForm = handleSubmit(onSubmit); + const handleCellEditingStopped = useCallback( + (event: CellEditingStoppedEvent) => { + console.log('DBR handleCellEditingStopped', event); + if (event.valueChanged && event.data) { + UserAdminSrv.udpateUser(event.data) + .catch((error) => + snackError({ + messageTxt: `Error while updating user "${ + event.data?.sub + }" ${error.message && ':\n' + error.message}`, + headerId: 'users.table.error.update', + }) + ) + .then(() => gridContext?.refresh?.()); + } else { + gridContext?.refresh?.(); + } + }, + [gridContext, snackError] + ); + return ( @@ -178,6 +225,7 @@ const UsersPage: FunctionComponent = () => { dataLoader={UserAdminSrv.fetchUsers} columnDefs={columns} defaultColDef={defaultColDef} + onCellEditingStopped={handleCellEditingStopped} gridId="table-users" getRowId={getRowId} rowSelection="multiple" diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 47c9346..80d2925 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -62,6 +62,19 @@ export function fetchUsers(): Promise { }) as Promise; } +export function udpateUser(userInfos: UserInfos) { + console.debug(`Updating a user...`); + + return backendFetch(`${USER_ADMIN_URL}/users/${userInfos.sub}`, { + method: 'PUT', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userInfos), + }); +} + export function deleteUser(sub: string): Promise { console.debug(`Deleting sub user "${sub}"...`); return backendFetch(`${USER_ADMIN_URL}/users/${sub}`, { method: 'delete' }) diff --git a/src/translations/en.json b/src/translations/en.json index 193511c..ac12f97 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -36,6 +36,7 @@ "users.table.error.delete": "Error while deleting user", "users.table.error.add": "Error while adding user", "users.table.toolbar.add": "Add user", + "users.table.profile.none": "", "users.table.toolbar.add.label": "Add a user", "users.form.title": "Add a user", "users.form.content": "Please fill in new user data.", diff --git a/src/translations/fr.json b/src/translations/fr.json index eb2c14c..23567d9 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -38,6 +38,7 @@ "users.table.error.add": "Erreur pendant l'ajout de l'utilisateur", "users.table.toolbar.add.label": "Ajouter un utilisateur", "users.table.toolbar.add": "Ajouter utilisateur", + "users.table.profile.none": "", "users.form.title": "Ajouter un utilisateur", "users.form.content": "Veuillez renseigner les informations de l'utilisateur.", "users.form.field.username.label": "ID utilisateur", From 69d2ab5288299dc8e669c4a70f176d3ccefd7f71 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 25 Mar 2024 15:51:14 +0100 Subject: [PATCH 04/35] add validity column --- src/pages/profiles/ProfilesPage.tsx | 37 ++++++++++++++++--- .../profile-modification-dialog.jsx | 6 ++- src/services/user-admin.ts | 1 + src/translations/en.json | 2 + src/translations/fr.json | 2 + 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 13ba2fa..e0edec2 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -33,12 +33,12 @@ import { GridTable, GridTableRef, } from '../../components/Grid'; -import { UserAdminSrv, UserProfile } from '../../services'; +import { UserAdminSrv, UserInfos, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { GetRowIdParams } from 'ag-grid-community/dist/lib/interfaces/iCallbackParams'; import { TextFilterParams } from 'ag-grid-community/dist/lib/filter/provided/text/textFilter'; -import { ColDef } from 'ag-grid-community'; +import { ColDef, ICheckboxCellRendererParams } from 'ag-grid-community'; import { RowDoubleClickedEvent, SelectionChangedEvent, @@ -54,7 +54,6 @@ const defaultColDef: ColDef = { showDisabledCheckboxes: true, rowDrag: false, sortable: true, - //enableCellChangeFlash: false, }; function getRowId(params: GetRowIdParams): string { @@ -89,6 +88,24 @@ const ProfilesPage: FunctionComponent = () => { } as TextFilterParams, editable: false, }, + { + field: 'validity', + cellDataType: 'boolean', + cellRendererParams: { + disabled: true, + } as ICheckboxCellRendererParams, + flex: 1, + headerName: intl.formatMessage({ + id: 'profiles.table.validity', + }), + headerTooltip: intl.formatMessage({ + id: 'profiles.table.validity.description', + }), + sortable: true, + filter: true, + initialSortIndex: 1, + initialSort: 'asc', + }, ], [intl] ); @@ -148,7 +165,17 @@ const ProfilesPage: FunctionComponent = () => { setOpenProfileModificationDialog(false); setEditingProfileId(undefined); reset(); - //gridContext?.refresh?.() + }; + + const handleUpdateProfileModificationDialog = ( + profileFormData: UserProfile + ) => { + console.log( + 'DBR handleUpdateProfileModificationDialog', + profileFormData + ); + gridContext?.refresh?.(); + handleCloseProfileModificationDialog(); }; const onRowDoubleClicked = useCallback( @@ -168,7 +195,7 @@ const ProfilesPage: FunctionComponent = () => { profileId={editingProfileId} open={openProfileModificationDialog} onClose={handleCloseProfileModificationDialog} - gridContext={gridContext} + onUpdate={handleUpdateProfileModificationDialog} /> ref={gridRef} diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.jsx index fae6665..99d5fad 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.jsx +++ b/src/pages/profiles/modification/profile-modification-dialog.jsx @@ -30,7 +30,7 @@ const ProfileModificationDialog = ({ profileId, open, onClose, - gridContext, + onUpdate, }) => { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -66,7 +66,9 @@ const ProfileModificationDialog = ({ headerId: 'profiles.form.modification.updateError', }); }) - .then(gridContext?.refresh?.()); + .then(() => { + onUpdate(profileFormData); + }); }; useEffect(() => { diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 80d2925..b177abe 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -120,6 +120,7 @@ export type ParameterInfos = { export type UserProfile = { id: UUID; name: string; + validity: boolean; loadFlowParameter: ParameterInfos; }; diff --git a/src/translations/en.json b/src/translations/en.json index ac12f97..0980ce8 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -49,6 +49,8 @@ "profiles.form.field.profilename.label": "Profile name", "profiles.table.id": "Name", "profiles.table.id.description": "Profile name", + "profiles.table.validity": "Validity", + "profiles.table.validity.description": "Parameters links validity", "profiles.form.modification.title": "Edit profile", "profiles.table.error.add": "Error while adding profile", "profiles.table.error.delete": "Error while adding profile", diff --git a/src/translations/fr.json b/src/translations/fr.json index 23567d9..76c546a 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -50,6 +50,8 @@ "profiles.form.field.profilename.label": "Nom du profil", "profiles.table.id": "Nom", "profiles.table.id.description": "Nom du profil", + "profiles.table.validity": "Validité", + "profiles.table.validity.description": "Validité des liens vers les paramètres", "profiles.table.error.add": "Erreur pendant l'ajout du profil", "profiles.table.error.delete": "Erreur pendant la suppression de profil", From 8af82cf24319553f087c5db1198b606e650e0d03 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 25 Mar 2024 21:41:48 +0100 Subject: [PATCH 05/35] add validity in form --- ...-selection.tsx => parameter-selection.tsx} | 102 +++++++++++------- .../profile-modification-dialog.jsx | 7 +- .../profile-modification-form.jsx | 19 +--- src/services/directory.ts | 2 +- src/services/explore.ts | 2 +- src/translations/en.json | 2 + src/translations/fr.json | 4 +- 7 files changed, 77 insertions(+), 61 deletions(-) rename src/components/{modify-element-selection.tsx => parameter-selection.tsx} (57%) diff --git a/src/components/modify-element-selection.tsx b/src/components/parameter-selection.tsx similarity index 57% rename from src/components/modify-element-selection.tsx rename to src/components/parameter-selection.tsx index 3fa9ccb..d6a8131 100644 --- a/src/components/modify-element-selection.tsx +++ b/src/components/parameter-selection.tsx @@ -8,10 +8,10 @@ // TODO: copy from grid-explore => move it to commons-ui import React, { useEffect, useState } from 'react'; -import { Button, Grid, Typography } from '@mui/material'; +import { Button, Grid, Typography, useTheme } from '@mui/material'; import { FormattedMessage, useIntl } from 'react-intl'; import { DirectoryItemSelector } from '@gridsuite/commons-ui'; -import { useController } from 'react-hook-form'; +import { useController, useWatch } from 'react-hook-form'; import { fetchDirectoryContent, fetchPath, @@ -36,48 +36,56 @@ export enum ElementType { export interface ModifyElementSelectionProps { elementType: ElementType; formParamId: string; - formParamFullName: string; - dialogOpeningButtonLabel: string; - dialogTitleLabel: string; - dialogMessageLabel: string; - noElementMessageLabel?: string; } -const ModifyElementSelection: React.FunctionComponent< +const ParameterSelection: React.FunctionComponent< ModifyElementSelectionProps > = (props) => { const intl = useIntl(); + const theme = useTheme(); const [open, setOpen] = useState(false); - const [selectedElementName, setSelectedElementName] = useState(''); - - const ctlParamId = useController({ + const [selectedElementName, setSelectedElementName] = useState(); + const [validity, setValidity] = useState(); + const watchParamId = useWatch({ name: props.formParamId, }); - const ctlParamFullName = useController({ - name: props.formParamFullName, + const ctlParamId = useController({ + name: props.formParamId, }); useEffect(() => { - console.log('DBR useEff elementUuid=', ctlParamId.field.value); - if (ctlParamId.field.value) { - fetchPath(ctlParamId.field.value).then((res: any) => { - console.log('DBR useEff fetchPath res=', res); - setSelectedElementName( - res - .map((element: any) => element.elementName.trim()) - .reverse() - .join('/') - ); - }); - //.catch((error) => setSelectedElementName('error')); + console.log('DBR useEff elementUuid=', watchParamId); + if (!watchParamId) { + setSelectedElementName(undefined); + setValidity(undefined); + } else { + fetchPath(watchParamId) + .then((res: any) => { + console.log('DBR useEff fetchPath res=', res); + setValidity(true); + setSelectedElementName( + res + .map((element: any) => element.elementName.trim()) + .reverse() + .join('/') + ); + }) + .catch(() => { + setSelectedElementName(undefined); + setValidity(false); + }); } - }, [ctlParamId.field.value]); + }, [watchParamId]); const handleSelectFolder = () => { setOpen(true); }; + const handleResetParameter = () => { + ctlParamId.field.onChange(undefined); + }; + const handleClose = (selection: any) => { console.log( 'DBR handleClose selected=', @@ -91,7 +99,6 @@ const ModifyElementSelection: React.FunctionComponent< selectedElementName ); ctlParamId.field.onChange(selection[0]?.id); - ctlParamFullName.field.onChange(selectedElementName); } setOpen(false); }; @@ -104,6 +111,22 @@ const ModifyElementSelection: React.FunctionComponent< alignItems: 'center', }} > + {selectedElementName ? selectedElementName - : props?.noElementMessageLabel - ? intl.formatMessage({ - id: props.noElementMessageLabel, - }) - : ''} + : intl.formatMessage({ + id: + validity === false + ? 'profiles.form.modification.invalidParameter' + : 'profiles.form.modification.noSelectedParameter', + })} { +const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.jsx index 5bde8de..2aaa76a 100644 --- a/src/pages/profiles/modification/profile-modification-form.jsx +++ b/src/pages/profiles/modification/profile-modification-form.jsx @@ -7,9 +7,9 @@ import { TextInput } from '@gridsuite/commons-ui'; import Grid from '@mui/material/Grid'; -import ModifyElementSelection, { +import ParameterSelection, { ElementType, -} from '../../../components/modify-element-selection'; +} from '../../../components/parameter-selection'; import { FormattedMessage } from 'react-intl'; import { styled } from '@mui/system'; @@ -50,22 +50,9 @@ const ProfileModificationForm = () => { - diff --git a/src/services/directory.ts b/src/services/directory.ts index a708eac..e7d4234 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -7,7 +7,7 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../components/modify-element-selection'; +import { ElementType } from '../components/parameter-selection'; const DIRECTORY_URL = `${getRestBase()}/directory/v1`; diff --git a/src/services/explore.ts b/src/services/explore.ts index c309237..346606d 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -7,7 +7,7 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../components/modify-element-selection'; +import { ElementType } from '../components/parameter-selection'; const EXPLORE_URL = `${getRestBase()}/explore/v1`; diff --git a/src/translations/en.json b/src/translations/en.json index 0980ce8..abb8f73 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -56,7 +56,9 @@ "profiles.table.error.delete": "Error while adding profile", "profiles.form.modification.noSelectedParameter": "No parameter selected.", + "profiles.form.modification.invalidParameter": "Invalid parameter.", "profiles.form.modification.parameterSelectionButton": "Choose parameter", + "profiles.form.modification.parameterSelectionResetButton": "Reset", "profiles.form.modification.parameterSelection.dialog.title": "Choose a parameter", "profiles.form.modification.parameterSelection.dialog.message": "Please choose a parameter", "profiles.form.modification.loadFlowSectionName": "Loadflow parameter", diff --git a/src/translations/fr.json b/src/translations/fr.json index 76c546a..913c59a 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -57,8 +57,10 @@ "profiles.form.modification.title": "Modifier profil", "profiles.form.modification.noSelectedParameter": "Auncun paramètre selectionné.", + "profiles.form.modification.invalidParameter": "Paramètre invalide.", "profiles.form.modification.parameterSelectionButton": "Choisir paramètre", - "profiles.form.modification.parameterSelection.dialog.title": "Choisir un ", + "profiles.form.modification.parameterSelectionResetButton": "Reset", + "profiles.form.modification.parameterSelection.dialog.title": "Choisir un paramètre", "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir un paramètre", "profiles.form.modification.loadFlowSectionName": "Paramètre de loadflow", "profiles.form.modification.readError": "Erreur lors de la lecture du profil", From aacbb379c0ae6e7a22c24326b4defb569fdf76ca Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 26 Mar 2024 14:54:25 +0100 Subject: [PATCH 06/35] sounds ok --- src/components/parameter-selection.tsx | 169 ++++++++---------- src/pages/profiles/ProfilesPage.tsx | 8 +- .../profile-modification-dialog.jsx | 4 +- .../profile-modification-form.jsx | 58 +++--- src/pages/users/UsersPage.tsx | 2 - src/translations/en.json | 7 +- src/translations/fr.json | 9 +- 7 files changed, 110 insertions(+), 147 deletions(-) diff --git a/src/components/parameter-selection.tsx b/src/components/parameter-selection.tsx index d6a8131..fed076e 100644 --- a/src/components/parameter-selection.tsx +++ b/src/components/parameter-selection.tsx @@ -5,11 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// TODO: copy from grid-explore => move it to commons-ui - import React, { useEffect, useState } from 'react'; -import { Button, Grid, Typography, useTheme } from '@mui/material'; -import { FormattedMessage, useIntl } from 'react-intl'; +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; +import FolderIcon from '@mui/icons-material/Folder'; +import { Grid, IconButton, Tooltip, Typography, useTheme } from '@mui/material'; +import { useIntl } from 'react-intl'; import { DirectoryItemSelector } from '@gridsuite/commons-ui'; import { useController, useWatch } from 'react-hook-form'; import { @@ -35,7 +35,8 @@ export enum ElementType { export interface ModifyElementSelectionProps { elementType: ElementType; - formParamId: string; + parameterFormId: string; + parameterNameKey: string; } const ParameterSelection: React.FunctionComponent< @@ -48,21 +49,19 @@ const ParameterSelection: React.FunctionComponent< const [selectedElementName, setSelectedElementName] = useState(); const [validity, setValidity] = useState(); const watchParamId = useWatch({ - name: props.formParamId, + name: props.parameterFormId, }); const ctlParamId = useController({ - name: props.formParamId, + name: props.parameterFormId, }); useEffect(() => { - console.log('DBR useEff elementUuid=', watchParamId); if (!watchParamId) { setSelectedElementName(undefined); setValidity(undefined); } else { fetchPath(watchParamId) .then((res: any) => { - console.log('DBR useEff fetchPath res=', res); setValidity(true); setSelectedElementName( res @@ -87,97 +86,83 @@ const ParameterSelection: React.FunctionComponent< }; const handleClose = (selection: any) => { - console.log( - 'DBR handleClose selected=', - selection, - selectedElementName - ); if (selection.length) { - console.log( - 'DBR handleClose onChange=', - selection[0]?.id, - selectedElementName - ); ctlParamId.field.onChange(selection[0]?.id); } setOpen(false); }; return ( - - - - - {selectedElementName - ? selectedElementName - : intl.formatMessage({ - id: - validity === false - ? 'profiles.form.modification.invalidParameter' - : 'profiles.form.modification.noSelectedParameter', - })} - - + ); }; diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index e0edec2..23cafc7 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -167,13 +167,7 @@ const ProfilesPage: FunctionComponent = () => { reset(); }; - const handleUpdateProfileModificationDialog = ( - profileFormData: UserProfile - ) => { - console.log( - 'DBR handleUpdateProfileModificationDialog', - profileFormData - ); + const handleUpdateProfileModificationDialog = () => { gridContext?.refresh?.(); handleCloseProfileModificationDialog(); }; diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.jsx index cdf1a4e..d081745 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.jsx +++ b/src/pages/profiles/modification/profile-modification-dialog.jsx @@ -46,7 +46,6 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { const { reset } = formMethods; const onSubmit = (profileFormData) => { - console.log('DBR submit', profileFormData); modifyProfile( profileId, profileFormData[PROFILE_NAME], @@ -62,7 +61,7 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { }); }) .then(() => { - onUpdate(profileFormData); + onUpdate(); }); }; @@ -71,7 +70,6 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { setDataFetchStatus(FetchStatus.FETCHING); getProfile(profileId) .then((response) => { - console.log('DBR getProfile', response); setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ [PROFILE_NAME]: response.name, diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.jsx index 2aaa76a..ca14df5 100644 --- a/src/pages/profiles/modification/profile-modification-form.jsx +++ b/src/pages/profiles/modification/profile-modification-form.jsx @@ -11,52 +11,38 @@ import ParameterSelection, { ElementType, } from '../../../components/parameter-selection'; import { FormattedMessage } from 'react-intl'; -import { styled } from '@mui/system'; export const PROFILE_NAME = 'name'; export const LF_PARAM_ID = 'lfParamId'; export const LF_PARAM_FULL_NAME = 'lfFullName'; -export const GridSection = ({ - title, - heading = '3', - size = 12, - customStyle = {}, -}) => { - const CustomTag = styled(`h${heading}`)(customStyle); - return ( - - - - - - - - ); -}; - const ProfileModificationForm = () => { return ( - <> - - - - + + + - - - - +

+ - +

- + + + +
); }; diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index dac5fc8..cb6809a 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -68,7 +68,6 @@ const UsersPage: FunctionComponent = () => { useEffect(() => { UserAdminSrv.fetchProfiles().then((allProfiles: UserProfile[]) => { - console.log('DBR useEff fetchProfiles', allProfiles); let profiles: string[] = [ intl.formatMessage({ id: 'users.table.profile.none' }), ]; @@ -198,7 +197,6 @@ const UsersPage: FunctionComponent = () => { const handleCellEditingStopped = useCallback( (event: CellEditingStoppedEvent) => { - console.log('DBR handleCellEditingStopped', event); if (event.valueChanged && event.data) { UserAdminSrv.udpateUser(event.data) .catch((error) => diff --git a/src/translations/en.json b/src/translations/en.json index abb8f73..8f438d3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -57,11 +57,12 @@ "profiles.form.modification.noSelectedParameter": "No parameter selected.", "profiles.form.modification.invalidParameter": "Invalid parameter.", - "profiles.form.modification.parameterSelectionButton": "Choose parameter", - "profiles.form.modification.parameterSelectionResetButton": "Reset", + "profiles.form.modification.parameter.choose.tooltip": "Choose parameter", + "profiles.form.modification.parameter.reset.tooltip": "Set undefined parameter", "profiles.form.modification.parameterSelection.dialog.title": "Choose a parameter", "profiles.form.modification.parameterSelection.dialog.message": "Please choose a parameter", - "profiles.form.modification.loadFlowSectionName": "Loadflow parameter", + "profiles.form.modification.defaultParameters": "Default parameters", + "profiles.form.modification.loadflow.name": "Loadflow", "profiles.form.modification.readError": "Error while reading the profile", "profiles.form.modification.updateError": "Error while updating the profile" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 913c59a..b47e1ce 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -56,13 +56,14 @@ "profiles.table.error.delete": "Erreur pendant la suppression de profil", "profiles.form.modification.title": "Modifier profil", - "profiles.form.modification.noSelectedParameter": "Auncun paramètre selectionné.", + "profiles.form.modification.noSelectedParameter": "Aucun paramètre selectionné.", "profiles.form.modification.invalidParameter": "Paramètre invalide.", - "profiles.form.modification.parameterSelectionButton": "Choisir paramètre", - "profiles.form.modification.parameterSelectionResetButton": "Reset", + "profiles.form.modification.parameter.choose.tooltip": "Choisir paramètre", + "profiles.form.modification.parameter.reset.tooltip": "Mettre le paramètre indéfini", "profiles.form.modification.parameterSelection.dialog.title": "Choisir un paramètre", "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir un paramètre", - "profiles.form.modification.loadFlowSectionName": "Paramètre de loadflow", + "profiles.form.modification.defaultParameters": "Paramètres par défaut", + "profiles.form.modification.loadflow.name": "Calcul de répartition", "profiles.form.modification.readError": "Erreur lors de la lecture du profil", "profiles.form.modification.updateError": "Erreur lors de la modification du profil" } From 1d103924aca9ce1db5089137ce1c1bb560a788d1 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 26 Mar 2024 15:39:03 +0100 Subject: [PATCH 07/35] move some files --- src/components/{common => }/custom-mui-dialog.tsx | 0 .../profiles/modification}/parameter-selection.tsx | 0 .../profiles/modification/profile-modification-dialog.jsx | 4 ++-- src/pages/profiles/modification/profile-modification-form.jsx | 4 +--- src/services/directory.ts | 2 +- src/services/explore.ts | 2 +- src/{components/common => utils}/yup-config.js | 0 7 files changed, 5 insertions(+), 7 deletions(-) rename src/components/{common => }/custom-mui-dialog.tsx (100%) rename src/{components => pages/profiles/modification}/parameter-selection.tsx (100%) rename src/{components/common => utils}/yup-config.js (100%) diff --git a/src/components/common/custom-mui-dialog.tsx b/src/components/custom-mui-dialog.tsx similarity index 100% rename from src/components/common/custom-mui-dialog.tsx rename to src/components/custom-mui-dialog.tsx diff --git a/src/components/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx similarity index 100% rename from src/components/parameter-selection.tsx rename to src/pages/profiles/modification/parameter-selection.tsx diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.jsx index d081745..17af802 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.jsx +++ b/src/pages/profiles/modification/profile-modification-dialog.jsx @@ -10,14 +10,14 @@ import ProfileModificationForm, { LF_PARAM_ID, PROFILE_NAME, } from './profile-modification-form'; -import yup from 'components/common/yup-config'; +import yup from 'utils/yup-config'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { useEffect, useState } from 'react'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { getProfile, modifyProfile } from 'services/user-admin'; import PropTypes from 'prop-types'; -import CustomMuiDialog from '../../../components/common/custom-mui-dialog'; +import CustomMuiDialog from '../../../components/custom-mui-dialog'; export const FetchStatus = { IDLE: 'IDLE', diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.jsx index ca14df5..d1a7ffb 100644 --- a/src/pages/profiles/modification/profile-modification-form.jsx +++ b/src/pages/profiles/modification/profile-modification-form.jsx @@ -7,9 +7,7 @@ import { TextInput } from '@gridsuite/commons-ui'; import Grid from '@mui/material/Grid'; -import ParameterSelection, { - ElementType, -} from '../../../components/parameter-selection'; +import ParameterSelection, { ElementType } from './parameter-selection'; import { FormattedMessage } from 'react-intl'; export const PROFILE_NAME = 'name'; diff --git a/src/services/directory.ts b/src/services/directory.ts index e7d4234..8a402f5 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -7,7 +7,7 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../components/parameter-selection'; +import { ElementType } from '../pages/profiles/modification/parameter-selection'; const DIRECTORY_URL = `${getRestBase()}/directory/v1`; diff --git a/src/services/explore.ts b/src/services/explore.ts index 346606d..3dd8634 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -7,7 +7,7 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../components/parameter-selection'; +import { ElementType } from '../pages/profiles/modification/parameter-selection'; const EXPLORE_URL = `${getRestBase()}/explore/v1`; diff --git a/src/components/common/yup-config.js b/src/utils/yup-config.js similarity index 100% rename from src/components/common/yup-config.js rename to src/utils/yup-config.js From c0f0db93123cfc23ff028a30c1af780f33453181 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 26 Mar 2024 17:49:45 +0100 Subject: [PATCH 08/35] clean code --- src/components/custom-mui-dialog.tsx | 2 +- .../modification/parameter-selection.tsx | 2 +- .../profile-modification-dialog.jsx | 20 ++++++----------- .../profile-modification-form.jsx | 3 ++- src/services/user-admin.ts | 22 +++---------------- src/utils/yup-config.js | 2 ++ 6 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/components/custom-mui-dialog.tsx b/src/components/custom-mui-dialog.tsx index 5f8cf61..740622d 100644 --- a/src/components/custom-mui-dialog.tsx +++ b/src/components/custom-mui-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// TODO: copy from grid-explore => move it to commons-ui +// TODO: this file is going to be available soon in commons-ui import React, { FunctionComponent } from 'react'; import { FieldErrors, FormProvider } from 'react-hook-form'; diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index fed076e..4581f0b 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -19,7 +19,7 @@ import { } from 'services/directory'; import { fetchElementsInfos } from 'services/explore'; -// TODO remove when available in commons-ui +// TODO remove ElementType when available in commons-ui (available soon) export enum ElementType { DIRECTORY = 'DIRECTORY', STUDY = 'STUDY', diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.jsx index 17af802..86ffb7d 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.jsx +++ b/src/pages/profiles/modification/profile-modification-dialog.jsx @@ -6,7 +6,6 @@ */ import ProfileModificationForm, { - LF_PARAM_FULL_NAME, LF_PARAM_ID, PROFILE_NAME, } from './profile-modification-form'; @@ -19,6 +18,7 @@ import { getProfile, modifyProfile } from 'services/user-admin'; import PropTypes from 'prop-types'; import CustomMuiDialog from '../../../components/custom-mui-dialog'; +// TODO remove FetchStatus when available in commons-ui (available soon) export const FetchStatus = { IDLE: 'IDLE', FETCHING: 'FETCHING', @@ -35,7 +35,6 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { .shape({ [PROFILE_NAME]: yup.string().trim().required('nameEmpty'), [LF_PARAM_ID]: yup.string().optional(), - [LF_PARAM_FULL_NAME]: yup.string().optional(), }) .required(); @@ -49,10 +48,7 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { modifyProfile( profileId, profileFormData[PROFILE_NAME], - profileFormData[LF_PARAM_ID], - profileFormData[LF_PARAM_FULL_NAME] - ? profileFormData[LF_PARAM_FULL_NAME] - : 'TODO' + profileFormData[LF_PARAM_ID] ) .catch((error) => { snackError({ @@ -73,9 +69,9 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ [PROFILE_NAME]: response.name, - [LF_PARAM_ID]: response.loadFlowParameter?.parameterId, - [LF_PARAM_FULL_NAME]: - response.loadFlowParameter?.fullName, + [LF_PARAM_ID]: response.loadFlowParameterId + ? response.loadFlowParameterId + : undefined, }); }) .catch((error) => { @@ -88,9 +84,7 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { } }, [profileId, open, reset, snackError]); - //const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; - - //{isDataReady && } + const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; return ( { removeOptional={true} isDataFetching={dataFetchStatus === FetchStatus.FETCHING} > - + {isDataReady && } ); }; diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.jsx index d1a7ffb..b48734f 100644 --- a/src/pages/profiles/modification/profile-modification-form.jsx +++ b/src/pages/profiles/modification/profile-modification-form.jsx @@ -12,7 +12,8 @@ import { FormattedMessage } from 'react-intl'; export const PROFILE_NAME = 'name'; export const LF_PARAM_ID = 'lfParamId'; -export const LF_PARAM_FULL_NAME = 'lfFullName'; + +// TODO: For PROFILE_NAME we could use unique-name-input (available soon in commons-ui) const ProfileModificationForm = () => { return ( diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index b177abe..5e519c3 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -111,17 +111,11 @@ export function addUser(sub: string): Promise { }); } -export type ParameterInfos = { - id: UUID; - parameterId: UUID; - fullName: string; -}; - export type UserProfile = { id: UUID; name: string; validity: boolean; - loadFlowParameter: ParameterInfos; + loadFlowParameterId: UUID; }; export function fetchProfiles(): Promise { @@ -150,12 +144,7 @@ export function getProfile(profileId: UUID): Promise { }) as Promise; } -export function modifyProfile( - profileId: UUID, - name: string, - lfParamId: UUID, - lfParamFullName: string -) { +export function modifyProfile(profileId: UUID, name: string, lfParamId: UUID) { console.debug(`Updating a profile...`); return backendFetch(`${USER_ADMIN_URL}/profiles/${profileId}`, { @@ -167,12 +156,7 @@ export function modifyProfile( body: JSON.stringify({ id: profileId, name: name, - loadFlowParameter: lfParamId - ? { - parameterId: lfParamId, - fullName: lfParamFullName, - } - : null, + loadFlowParameterId: lfParamId, }), }); } diff --git a/src/utils/yup-config.js b/src/utils/yup-config.js index b918c6a..6bf1807 100644 --- a/src/utils/yup-config.js +++ b/src/utils/yup-config.js @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// TODO: this file is going to be available soon in commons-ui + import * as yup from 'yup'; yup.setLocale({ From 8b3f79840ae0e7dab32d5504dabd8199f9401418 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 27 Mar 2024 13:59:28 +0100 Subject: [PATCH 09/35] change parameters translations --- src/translations/en.json | 13 ++++++------- src/translations/fr.json | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 8f438d3..e79dc8b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -55,13 +55,12 @@ "profiles.table.error.add": "Error while adding profile", "profiles.table.error.delete": "Error while adding profile", - "profiles.form.modification.noSelectedParameter": "No parameter selected.", - "profiles.form.modification.invalidParameter": "Invalid parameter.", - "profiles.form.modification.parameter.choose.tooltip": "Choose parameter", - "profiles.form.modification.parameter.reset.tooltip": "Set undefined parameter", - "profiles.form.modification.parameterSelection.dialog.title": "Choose a parameter", - "profiles.form.modification.parameterSelection.dialog.message": "Please choose a parameter", - "profiles.form.modification.defaultParameters": "Default parameters", + "profiles.form.modification.noSelectedParameter": "no parameters selected.", + "profiles.form.modification.invalidParameter": "invalid parameters link.", + "profiles.form.modification.parameter.choose.tooltip": "Choose parameters", + "profiles.form.modification.parameter.reset.tooltip": "Set undefined parameters", + "profiles.form.modification.parameterSelection.dialog.title": "Choose parameters", + "profiles.form.modification.parameterSelection.dialog.message": "Please choose parameters", "profiles.form.modification.loadflow.name": "Loadflow", "profiles.form.modification.readError": "Error while reading the profile", "profiles.form.modification.updateError": "Error while updating the profile" diff --git a/src/translations/fr.json b/src/translations/fr.json index b47e1ce..72c8ce9 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -56,13 +56,12 @@ "profiles.table.error.delete": "Erreur pendant la suppression de profil", "profiles.form.modification.title": "Modifier profil", - "profiles.form.modification.noSelectedParameter": "Aucun paramètre selectionné.", - "profiles.form.modification.invalidParameter": "Paramètre invalide.", - "profiles.form.modification.parameter.choose.tooltip": "Choisir paramètre", - "profiles.form.modification.parameter.reset.tooltip": "Mettre le paramètre indéfini", - "profiles.form.modification.parameterSelection.dialog.title": "Choisir un paramètre", - "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir un paramètre", - "profiles.form.modification.defaultParameters": "Paramètres par défaut", + "profiles.form.modification.noSelectedParameter": "pas de paramètres selectionnés.", + "profiles.form.modification.invalidParameter": "lien vers paramètres invalide.", + "profiles.form.modification.parameter.choose.tooltip": "Choisir paramètres", + "profiles.form.modification.parameter.reset.tooltip": "Ne pas définir de paramètres", + "profiles.form.modification.parameterSelection.dialog.title": "Choisir des paramètres", + "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir des paramètres", "profiles.form.modification.loadflow.name": "Calcul de répartition", "profiles.form.modification.readError": "Erreur lors de la lecture du profil", "profiles.form.modification.updateError": "Erreur lors de la modification du profil" From a3ec558adf718121a139219bcb1b363686fb0761 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 27 Mar 2024 16:13:05 +0100 Subject: [PATCH 10/35] dont use potentially xtra long message in snackbar --- src/pages/profiles/ProfilesPage.tsx | 8 ++------ src/pages/users/UsersPage.tsx | 12 +++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 23cafc7..7a50516 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -116,9 +116,7 @@ const ProfilesPage: FunctionComponent = () => { return UserAdminSrv.deleteProfiles(profileNames) .catch((error) => snackError({ - messageTxt: `Error while deleting profiles "${JSON.stringify( - profileNames - )}"${error.message && ':\n' + error.message}`, + messageTxt: error.message, headerId: 'profiles.table.error.delete', }) ) @@ -134,9 +132,7 @@ const ProfilesPage: FunctionComponent = () => { UserAdminSrv.addProfile(name) .catch((error) => snackError({ - messageTxt: `Error while adding profile "${name}"${ - error.message && ':\n' + error.message - }`, + messageTxt: error.message, headerId: 'profiles.table.error.add', }) ) diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index cb6809a..95f9cfd 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -150,9 +150,7 @@ const UsersPage: FunctionComponent = () => { return UserAdminSrv.deleteUsers(subs) .catch((error) => snackError({ - messageTxt: `Error while deleting user "${JSON.stringify( - subs - )}"${error.message && ':\n' + error.message}`, + messageTxt: error.message, headerId: 'users.table.error.delete', }) ) @@ -168,9 +166,7 @@ const UsersPage: FunctionComponent = () => { UserAdminSrv.addUser(id) .catch((error) => snackError({ - messageTxt: `Error while adding user "${id}"${ - error.message && ':\n' + error.message - }`, + messageTxt: error.message, headerId: 'users.table.error.add', }) ) @@ -201,9 +197,7 @@ const UsersPage: FunctionComponent = () => { UserAdminSrv.udpateUser(event.data) .catch((error) => snackError({ - messageTxt: `Error while updating user "${ - event.data?.sub - }" ${error.message && ':\n' + error.message}`, + messageTxt: error.message, headerId: 'users.table.error.update', }) ) From 60b7501ea047d44faedb23d9eedcd647bce54696 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 27 Mar 2024 16:26:35 +0100 Subject: [PATCH 11/35] add scrollbar for profiles select --- src/pages/users/UsersPage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 95f9cfd..d7cb162 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -118,6 +118,8 @@ const UsersPage: FunctionComponent = () => { cellEditorParams: () => { return { values: profileNameOptions, + valueListMaxHeight: 400, + valueListMaxWidth: 300, }; }, }, From 57e9261c4a2ee2b63992a3a8cb25b1295eec9758 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 27 Mar 2024 16:49:08 +0100 Subject: [PATCH 12/35] validity renderer with images --- src/images/cross-in-circle.png | Bin 0 -> 551 bytes src/images/tick-in-circle.png | Bin 0 -> 619 bytes src/pages/profiles/ProfilesPage.tsx | 19 ++++++++++++++----- src/translations/en.json | 3 ++- src/translations/fr.json | 1 + 5 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/images/cross-in-circle.png create mode 100644 src/images/tick-in-circle.png diff --git a/src/images/cross-in-circle.png b/src/images/cross-in-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..7278d07dcc425d3d29b70d98acf6b246fea9581f GIT binary patch literal 551 zcmV+?0@(eDP)7%nQ&^Lyfv&79 zQLAkMtH7KsKOk~Np8u63xpVn45=PM!@9+D-HsDnoTMU6e1i`Mv@nFxl|>|u2L!2~j3{wItcsI8TaDarr<002ovPDHLkV1il%>1F@` literal 0 HcmV?d00001 diff --git a/src/images/tick-in-circle.png b/src/images/tick-in-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..cb184c9bb4b7b5d7008f88022c0af758f158e4d2 GIT binary patch literal 619 zcmV-x0+juUP)M+KMDZ z+CqYKQmW`6IJAR>NG+~PARPqJO^W2zN}*b)?I5X63Jzj%k>a05w2v;D$V=b*98AhH zB*mWT9`5-azRTrP)G0CD-zCUJh4dhF0H9bA)Ph3Ye>(8wT`;d|hg3clDH(1S;R>MZ zi>vYwb=UNaVLNTRn+2(SDpC$t<^(y}1gzJhxe { { field: 'validity', cellDataType: 'boolean', - cellRendererParams: { - disabled: true, - } as ICheckboxCellRendererParams, + cellRenderer: (params: any) => { + return params.value == null ? ( + - + ) : ( + {'validityImage'} + ); + }, flex: 1, headerName: intl.formatMessage({ id: 'profiles.table.validity', diff --git a/src/translations/en.json b/src/translations/en.json index e79dc8b..ae4221e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -51,10 +51,11 @@ "profiles.table.id.description": "Profile name", "profiles.table.validity": "Validity", "profiles.table.validity.description": "Parameters links validity", - "profiles.form.modification.title": "Edit profile", "profiles.table.error.add": "Error while adding profile", "profiles.table.error.delete": "Error while adding profile", + "profiles.form.modification.title": "Edit profile", + "profiles.form.modification.defaultParameters": "Default parameters", "profiles.form.modification.noSelectedParameter": "no parameters selected.", "profiles.form.modification.invalidParameter": "invalid parameters link.", "profiles.form.modification.parameter.choose.tooltip": "Choose parameters", diff --git a/src/translations/fr.json b/src/translations/fr.json index 72c8ce9..942e185 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -56,6 +56,7 @@ "profiles.table.error.delete": "Erreur pendant la suppression de profil", "profiles.form.modification.title": "Modifier profil", + "profiles.form.modification.defaultParameters": "Paramètres par défaut", "profiles.form.modification.noSelectedParameter": "pas de paramètres selectionnés.", "profiles.form.modification.invalidParameter": "lien vers paramètres invalide.", "profiles.form.modification.parameter.choose.tooltip": "Choisir paramètres", From c0af5ab1d82d41547b9314bfd837763ecb963c04 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Thu, 28 Mar 2024 16:06:05 +0100 Subject: [PATCH 13/35] use tsx for profile-modification --- ...og.jsx => profile-modification-dialog.tsx} | 51 ++++++++++--------- ...form.jsx => profile-modification-form.tsx} | 11 ++-- 2 files changed, 33 insertions(+), 29 deletions(-) rename src/pages/profiles/modification/{profile-modification-dialog.jsx => profile-modification-dialog.tsx} (75%) rename src/pages/profiles/modification/{profile-modification-form.jsx => profile-modification-form.tsx} (83%) diff --git a/src/pages/profiles/modification/profile-modification-dialog.jsx b/src/pages/profiles/modification/profile-modification-dialog.tsx similarity index 75% rename from src/pages/profiles/modification/profile-modification-dialog.jsx rename to src/pages/profiles/modification/profile-modification-dialog.tsx index 86ffb7d..ad26f99 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.jsx +++ b/src/pages/profiles/modification/profile-modification-dialog.tsx @@ -12,11 +12,11 @@ import ProfileModificationForm, { import yup from 'utils/yup-config'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; -import { useEffect, useState } from 'react'; +import { FunctionComponent, useEffect, useState } from 'react'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { getProfile, modifyProfile } from 'services/user-admin'; -import PropTypes from 'prop-types'; import CustomMuiDialog from '../../../components/custom-mui-dialog'; +import { UUID } from 'crypto'; // TODO remove FetchStatus when available in commons-ui (available soon) export const FetchStatus = { @@ -26,7 +26,16 @@ export const FetchStatus = { FETCH_ERROR: 'FETCH_ERROR', }; -const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { +export interface ProfileModificationDialogProps { + profileId: UUID | undefined; + open: boolean; + onClose: () => void; + onUpdate: () => void; +} + +const ProfileModificationDialog: FunctionComponent< + ProfileModificationDialogProps +> = ({ profileId, open, onClose, onUpdate }) => { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -44,21 +53,23 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { const { reset } = formMethods; - const onSubmit = (profileFormData) => { - modifyProfile( - profileId, - profileFormData[PROFILE_NAME], - profileFormData[LF_PARAM_ID] - ) - .catch((error) => { - snackError({ - messageTxt: error.message, - headerId: 'profiles.form.modification.updateError', + const onSubmit = (profileFormData: any) => { + if (profileId) { + modifyProfile( + profileId, + profileFormData[PROFILE_NAME], + profileFormData[LF_PARAM_ID] + ) + .catch((error) => { + snackError({ + messageTxt: error.message, + headerId: 'profiles.form.modification.updateError', + }); + }) + .then(() => { + onUpdate(); }); - }) - .then(() => { - onUpdate(); - }); + } }; useEffect(() => { @@ -102,10 +113,4 @@ const ProfileModificationDialog = ({ profileId, open, onClose, onUpdate }) => { ); }; -ProfileModificationDialog.propTypes = { - profileId: PropTypes.string, - open: PropTypes.bool, - onClose: PropTypes.func.isRequired, -}; - export default ProfileModificationDialog; diff --git a/src/pages/profiles/modification/profile-modification-form.jsx b/src/pages/profiles/modification/profile-modification-form.tsx similarity index 83% rename from src/pages/profiles/modification/profile-modification-form.jsx rename to src/pages/profiles/modification/profile-modification-form.tsx index b48734f..f2f3234 100644 --- a/src/pages/profiles/modification/profile-modification-form.jsx +++ b/src/pages/profiles/modification/profile-modification-form.tsx @@ -9,30 +9,29 @@ import { TextInput } from '@gridsuite/commons-ui'; import Grid from '@mui/material/Grid'; import ParameterSelection, { ElementType } from './parameter-selection'; import { FormattedMessage } from 'react-intl'; +import React, { FunctionComponent } from 'react'; export const PROFILE_NAME = 'name'; export const LF_PARAM_ID = 'lfParamId'; -// TODO: For PROFILE_NAME we could use unique-name-input (available soon in commons-ui) - -const ProfileModificationForm = () => { +const ProfileModificationForm: FunctionComponent = () => { return ( - + - +

- + Date: Thu, 28 Mar 2024 16:07:18 +0100 Subject: [PATCH 14/35] fix typo --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index ae4221e..7199833 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -52,7 +52,7 @@ "profiles.table.validity": "Validity", "profiles.table.validity.description": "Parameters links validity", "profiles.table.error.add": "Error while adding profile", - "profiles.table.error.delete": "Error while adding profile", + "profiles.table.error.delete": "Error while deleting profile", "profiles.form.modification.title": "Edit profile", "profiles.form.modification.defaultParameters": "Default parameters", From 83134d481c933f310962f33b481c1837fafc8a81 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 2 Apr 2024 18:40:46 +0200 Subject: [PATCH 15/35] rev remark: rename validity into allParametersLinksValid --- src/pages/profiles/ProfilesPage.tsx | 2 +- .../profiles/modification/parameter-selection.tsx | 12 ++++++------ src/services/user-admin.ts | 2 +- src/translations/en.json | 4 ++-- src/translations/fr.json | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 467c288..a0d9b8b 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -91,7 +91,7 @@ const ProfilesPage: FunctionComponent = () => { editable: false, }, { - field: 'validity', + field: 'allParametersLinksValid', cellDataType: 'boolean', cellRenderer: (params: any) => { return params.value == null ? ( diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index 4581f0b..d175a3b 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -47,7 +47,7 @@ const ParameterSelection: React.FunctionComponent< const [open, setOpen] = useState(false); const [selectedElementName, setSelectedElementName] = useState(); - const [validity, setValidity] = useState(); + const [parameterLinkValid, setParameterLinkValid] = useState(); const watchParamId = useWatch({ name: props.parameterFormId, }); @@ -58,11 +58,11 @@ const ParameterSelection: React.FunctionComponent< useEffect(() => { if (!watchParamId) { setSelectedElementName(undefined); - setValidity(undefined); + setParameterLinkValid(undefined); } else { fetchPath(watchParamId) .then((res: any) => { - setValidity(true); + setParameterLinkValid(true); setSelectedElementName( res .map((element: any) => element.elementName.trim()) @@ -72,7 +72,7 @@ const ParameterSelection: React.FunctionComponent< }) .catch(() => { setSelectedElementName(undefined); - setValidity(false); + setParameterLinkValid(false); }); } }, [watchParamId]); @@ -125,7 +125,7 @@ const ParameterSelection: React.FunctionComponent< sx={{ fontWeight: 'bold', color: - validity === false + parameterLinkValid === false ? theme.palette.error.main : undefined, }} @@ -138,7 +138,7 @@ const ParameterSelection: React.FunctionComponent< ? selectedElementName : intl.formatMessage({ id: - validity === false + parameterLinkValid === false ? 'profiles.form.modification.invalidParameter' : 'profiles.form.modification.noSelectedParameter', }))} diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 5e519c3..2b1d758 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -114,7 +114,7 @@ export function addUser(sub: string): Promise { export type UserProfile = { id: UUID; name: string; - validity: boolean; + allParametersLinksValid: boolean; loadFlowParameterId: UUID; }; diff --git a/src/translations/en.json b/src/translations/en.json index 7199833..17c9ce1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -49,8 +49,8 @@ "profiles.form.field.profilename.label": "Profile name", "profiles.table.id": "Name", "profiles.table.id.description": "Profile name", - "profiles.table.validity": "Validity", - "profiles.table.validity.description": "Parameters links validity", + "profiles.table.validity": "Parameters links validity", + "profiles.table.validity.description": "Are all parameters links valid?", "profiles.table.error.add": "Error while adding profile", "profiles.table.error.delete": "Error while deleting profile", diff --git a/src/translations/fr.json b/src/translations/fr.json index 942e185..b08e3be 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -50,8 +50,8 @@ "profiles.form.field.profilename.label": "Nom du profil", "profiles.table.id": "Nom", "profiles.table.id.description": "Nom du profil", - "profiles.table.validity": "Validité", - "profiles.table.validity.description": "Validité des liens vers les paramètres", + "profiles.table.validity": "Validité des liens vers les paramètres", + "profiles.table.validity.description": "Tous les liens vers les paramètres sont-ils valides ?", "profiles.table.error.add": "Erreur pendant l'ajout du profil", "profiles.table.error.delete": "Erreur pendant la suppression de profil", From 0219e7dfd08e6dae19c37f0e1b2d4be2a9cf76ef Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 2 Apr 2024 19:28:42 +0200 Subject: [PATCH 16/35] rev remark: avoid getPath called twice (unmount ProfileModificationForm) --- .../profiles/modification/profile-modification-dialog.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/profiles/modification/profile-modification-dialog.tsx b/src/pages/profiles/modification/profile-modification-dialog.tsx index ad26f99..5bb7126 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.tsx +++ b/src/pages/profiles/modification/profile-modification-dialog.tsx @@ -72,6 +72,11 @@ const ProfileModificationDialog: FunctionComponent< } }; + const onDialogClose = () => { + setDataFetchStatus(FetchStatus.IDLE); + onClose(); + }; + useEffect(() => { if (profileId && open) { setDataFetchStatus(FetchStatus.FETCHING); @@ -100,7 +105,7 @@ const ProfileModificationDialog: FunctionComponent< return ( Date: Wed, 3 Apr 2024 16:47:06 +0200 Subject: [PATCH 17/35] use ElementType from commons-ui after merge --- .../modification/parameter-selection.tsx | 16 +--------------- .../modification/profile-modification-form.tsx | 4 ++-- src/services/directory.ts | 5 ++--- src/services/explore.ts | 3 +-- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index d175a3b..12eb236 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -10,7 +10,7 @@ import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import FolderIcon from '@mui/icons-material/Folder'; import { Grid, IconButton, Tooltip, Typography, useTheme } from '@mui/material'; import { useIntl } from 'react-intl'; -import { DirectoryItemSelector } from '@gridsuite/commons-ui'; +import { DirectoryItemSelector, ElementType } from '@gridsuite/commons-ui'; import { useController, useWatch } from 'react-hook-form'; import { fetchDirectoryContent, @@ -19,20 +19,6 @@ import { } from 'services/directory'; import { fetchElementsInfos } from 'services/explore'; -// TODO remove ElementType when available in commons-ui (available soon) -export enum ElementType { - DIRECTORY = 'DIRECTORY', - STUDY = 'STUDY', - FILTER = 'FILTER', - CONTINGENCY_LIST = 'CONTINGENCY_LIST', - MODIFICATION = 'MODIFICATION', - CASE = 'CASE', - VOLTAGE_INIT_PARAMETERS = 'VOLTAGE_INIT_PARAMETERS', - SECURITY_ANALYSIS_PARAMETERS = 'SECURITY_ANALYSIS_PARAMETERS', - LOADFLOW_PARAMETERS = 'LOADFLOW_PARAMETERS', - SENSITIVITY_PARAMETERS = 'SENSITIVITY_PARAMETERS', -} - export interface ModifyElementSelectionProps { elementType: ElementType; parameterFormId: string; diff --git a/src/pages/profiles/modification/profile-modification-form.tsx b/src/pages/profiles/modification/profile-modification-form.tsx index f2f3234..04575a2 100644 --- a/src/pages/profiles/modification/profile-modification-form.tsx +++ b/src/pages/profiles/modification/profile-modification-form.tsx @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { TextInput } from '@gridsuite/commons-ui'; +import { ElementType, TextInput } from '@gridsuite/commons-ui'; import Grid from '@mui/material/Grid'; -import ParameterSelection, { ElementType } from './parameter-selection'; +import ParameterSelection from './parameter-selection'; import { FormattedMessage } from 'react-intl'; import React, { FunctionComponent } from 'react'; diff --git a/src/services/directory.ts b/src/services/directory.ts index 8a402f5..aa9ace8 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -7,7 +7,6 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../pages/profiles/modification/parameter-selection'; const DIRECTORY_URL = `${getRestBase()}/directory/v1`; @@ -31,7 +30,7 @@ export function fetchPath(elementUuid: UUID): Promise { } export function fetchRootFolders( - types: ElementType[] + types: string[] // should be ElementType[] ): Promise { console.info('Fetching Root Directories...'); const urlSearchParams = new URLSearchParams( @@ -53,7 +52,7 @@ export function fetchRootFolders( export function fetchDirectoryContent( directoryUuid: UUID, - types: ElementType[] + types: string[] // should be ElementType[] ): Promise { console.info('Fetching Directory content...'); const urlSearchParams = new URLSearchParams( diff --git a/src/services/explore.ts b/src/services/explore.ts index 3dd8634..c9f5aa8 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -7,7 +7,6 @@ import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementType } from '../pages/profiles/modification/parameter-selection'; const EXPLORE_URL = `${getRestBase()}/explore/v1`; @@ -19,7 +18,7 @@ export type ElementAttributes = { export function fetchElementsInfos( ids: UUID[], - elementTypes: ElementType[] + elementTypes: string[] // should be ElementType[] ): Promise { console.info('Fetching elements metadata...'); const tmp = ids?.filter((id) => id); From f2402b5dc7d718a3c969a2c3577e30d6d99c828c Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 5 Apr 2024 17:56:17 +0200 Subject: [PATCH 18/35] update after merge --- src/pages/profiles/ProfilesPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index a0d9b8b..9b28628 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -36,15 +36,15 @@ import { import { UserAdminSrv, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; -import { GetRowIdParams } from 'ag-grid-community/dist/lib/interfaces/iCallbackParams'; -import { TextFilterParams } from 'ag-grid-community/dist/lib/filter/provided/text/textFilter'; -import { ColDef } from 'ag-grid-community'; -import tickInCircle from 'images/tick-in-circle.png'; -import crossInCircle from 'images/cross-in-circle.png'; import { + ColDef, + GetRowIdParams, RowDoubleClickedEvent, SelectionChangedEvent, -} from 'ag-grid-community/dist/lib/events'; + TextFilterParams, +} from 'ag-grid-community'; +import tickInCircle from 'images/tick-in-circle.png'; +import crossInCircle from 'images/cross-in-circle.png'; import ProfileModificationDialog from './modification/profile-modification-dialog'; import { UUID } from 'crypto'; From 732ba9f0a42a5ff365c2563527ebf7720ff26afe Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 5 Apr 2024 18:15:34 +0200 Subject: [PATCH 19/35] rev remarks: minor changes --- src/pages/profiles/ProfilesPage.tsx | 2 +- src/pages/profiles/modification/parameter-selection.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 9b28628..449fd36 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -52,7 +52,7 @@ const defaultColDef: ColDef = { editable: false, resizable: true, minWidth: 50, - cellRenderer: 'agAnimateSlideCellRenderer', //'agAnimateShowChangeCellRenderer' + cellRenderer: 'agAnimateSlideCellRenderer', showDisabledCheckboxes: true, rowDrag: false, sortable: true, diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index 12eb236..a6df248 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -31,7 +31,8 @@ const ParameterSelection: React.FunctionComponent< const intl = useIntl(); const theme = useTheme(); - const [open, setOpen] = useState(false); + const [openDirectorySelector, setOpenDirectorySelector] = + useState(false); const [selectedElementName, setSelectedElementName] = useState(); const [parameterLinkValid, setParameterLinkValid] = useState(); const watchParamId = useWatch({ @@ -64,7 +65,7 @@ const ParameterSelection: React.FunctionComponent< }, [watchParamId]); const handleSelectFolder = () => { - setOpen(true); + setOpenDirectorySelector(true); }; const handleResetParameter = () => { @@ -75,7 +76,7 @@ const ParameterSelection: React.FunctionComponent< if (selection.length) { ctlParamId.field.onChange(selection[0]?.id); } - setOpen(false); + setOpenDirectorySelector(false); }; return ( @@ -130,7 +131,7 @@ const ParameterSelection: React.FunctionComponent< }))} Date: Fri, 5 Apr 2024 18:53:43 +0200 Subject: [PATCH 20/35] rev remarks: use mui icons rather than png --- src/images/cross-in-circle.png | Bin 551 -> 0 bytes src/images/tick-in-circle.png | Bin 619 -> 0 bytes src/pages/profiles/ProfilesPage.tsx | 22 ++++++++++++++-------- 3 files changed, 14 insertions(+), 8 deletions(-) delete mode 100644 src/images/cross-in-circle.png delete mode 100644 src/images/tick-in-circle.png diff --git a/src/images/cross-in-circle.png b/src/images/cross-in-circle.png deleted file mode 100644 index 7278d07dcc425d3d29b70d98acf6b246fea9581f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 551 zcmV+?0@(eDP)7%nQ&^Lyfv&79 zQLAkMtH7KsKOk~Np8u63xpVn45=PM!@9+D-HsDnoTMU6e1i`Mv@nFxl|>|u2L!2~j3{wItcsI8TaDarr<002ovPDHLkV1il%>1F@` diff --git a/src/images/tick-in-circle.png b/src/images/tick-in-circle.png deleted file mode 100644 index cb184c9bb4b7b5d7008f88022c0af758f158e4d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 619 zcmV-x0+juUP)M+KMDZ z+CqYKQmW`6IJAR>NG+~PARPqJO^W2zN}*b)?I5X63Jzj%k>a05w2v;D$V=b*98AhH zB*mWT9`5-azRTrP)G0CD-zCUJh4dhF0H9bA)Ph3Ye>(8wT`;d|hg3clDH(1S;R>MZ zi>vYwb=UNaVLNTRn+2(SDpC$t<^(y}1gzJhxe { { field: 'allParametersLinksValid', cellDataType: 'boolean', + cellStyle: (params) => ({ + display: 'flex', + alignItems: 'center', + }), cellRenderer: (params: any) => { return params.value == null ? ( - - + + ) : params.value ? ( + ) : ( - {'validityImage'} + ); }, flex: 1, From dda886920af71fe63c7537fe463c61bdf370e98e Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 8 Apr 2024 10:11:39 +0200 Subject: [PATCH 21/35] rev remarks: change post/put on /profiles --- src/pages/profiles/ProfilesPage.tsx | 10 +++++-- .../profile-modification-dialog.tsx | 14 +++++----- src/services/user-admin.ts | 27 ++++++++++--------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index e1ebe80..e92d40d 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -62,7 +62,7 @@ const defaultColDef: ColDef = { }; function getRowId(params: GetRowIdParams): string { - return params.data.id; + return params.data.id ? params.data.id : ''; } const ProfilesPage: FunctionComponent = () => { @@ -144,7 +144,13 @@ const ProfilesPage: FunctionComponent = () => { const addProfile = useCallback( (name: string) => { - UserAdminSrv.addProfile(name) + const profileData: UserProfile = { + id: undefined, + name: name, + loadFlowParameterId: undefined, + allParametersLinksValid: undefined, + }; + UserAdminSrv.addProfile(profileData) .catch((error) => snackError({ messageTxt: error.message, diff --git a/src/pages/profiles/modification/profile-modification-dialog.tsx b/src/pages/profiles/modification/profile-modification-dialog.tsx index 5bb7126..df932ef 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.tsx +++ b/src/pages/profiles/modification/profile-modification-dialog.tsx @@ -14,7 +14,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { FunctionComponent, useEffect, useState } from 'react'; import { useSnackMessage } from '@gridsuite/commons-ui'; -import { getProfile, modifyProfile } from 'services/user-admin'; +import { getProfile, modifyProfile, UserProfile } from 'services/user-admin'; import CustomMuiDialog from '../../../components/custom-mui-dialog'; import { UUID } from 'crypto'; @@ -55,11 +55,13 @@ const ProfileModificationDialog: FunctionComponent< const onSubmit = (profileFormData: any) => { if (profileId) { - modifyProfile( - profileId, - profileFormData[PROFILE_NAME], - profileFormData[LF_PARAM_ID] - ) + const profileData: UserProfile = { + id: profileId, + name: profileFormData[PROFILE_NAME], + loadFlowParameterId: profileFormData[LF_PARAM_ID], + allParametersLinksValid: undefined, + }; + modifyProfile(profileData) .catch((error) => { snackError({ messageTxt: error.message, diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 2b1d758..1be7796 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -112,10 +112,10 @@ export function addUser(sub: string): Promise { } export type UserProfile = { - id: UUID; + id: UUID | undefined; name: string; - allParametersLinksValid: boolean; - loadFlowParameterId: UUID; + allParametersLinksValid: boolean | undefined; + loadFlowParameterId: UUID | undefined; }; export function fetchProfiles(): Promise { @@ -144,27 +144,28 @@ export function getProfile(profileId: UUID): Promise { }) as Promise; } -export function modifyProfile(profileId: UUID, name: string, lfParamId: UUID) { +export function modifyProfile(profileData: UserProfile) { console.debug(`Updating a profile...`); - return backendFetch(`${USER_ADMIN_URL}/profiles/${profileId}`, { + return backendFetch(`${USER_ADMIN_URL}/profiles/${profileData.id}`, { method: 'PUT', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ - id: profileId, - name: name, - loadFlowParameterId: lfParamId, - }), + body: JSON.stringify(profileData), }); } -export function addProfile(name: string): Promise { - console.debug(`Creating user profile "${name}"...`); - return backendFetch(`${USER_ADMIN_URL}/profiles/${name}`, { +export function addProfile(profileData: UserProfile): Promise { + console.debug(`Creating user profile "${profileData.name}"...`); + return backendFetch(`${USER_ADMIN_URL}/profiles`, { method: 'post', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(profileData), }) .then((response: Response) => undefined) .catch((reason) => { From 8d2aabec6f0254f916a976df33adf41e267f1695 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 8 Apr 2024 14:14:26 +0200 Subject: [PATCH 22/35] PO remarks: use RadioButtonUnchecked --- src/pages/profiles/ProfilesPage.tsx | 4 ++-- src/services/user-admin.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index e92d40d..53daff0 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -30,7 +30,7 @@ import { Cancel, CheckCircle, ManageAccounts, - IndeterminateCheckBoxOutlined, + RadioButtonUnchecked, } from '@mui/icons-material'; import { GridButton, @@ -102,7 +102,7 @@ const ProfilesPage: FunctionComponent = () => { }), cellRenderer: (params: any) => { return params.value == null ? ( - + ) : params.value ? ( ) : ( diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 1be7796..02a4903 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -167,7 +167,7 @@ export function addProfile(profileData: UserProfile): Promise { }, body: JSON.stringify(profileData), }) - .then((response: Response) => undefined) + .then(() => undefined) .catch((reason) => { console.error(`Error while pushing the data : ${reason}`); throw reason; @@ -183,7 +183,7 @@ export function deleteProfiles(names: string[]): Promise { }, body: JSON.stringify(names), }) - .then((response: Response) => undefined) + .then(() => undefined) .catch((reason) => { console.error(`Error while deleting the servers data : ${reason}`); throw reason; From 6a02b5034ffbe80c88aa37e426509d7b9f555487 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 8 Apr 2024 14:37:31 +0200 Subject: [PATCH 23/35] rm warnings --- src/pages/profiles/ProfilesPage.tsx | 2 +- src/services/user-admin.ts | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 53daff0..e09246f 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -96,7 +96,7 @@ const ProfilesPage: FunctionComponent = () => { { field: 'allParametersLinksValid', cellDataType: 'boolean', - cellStyle: (params) => ({ + cellStyle: () => ({ display: 'flex', alignItems: 'center', }), diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 02a4903..2831ea2 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -75,16 +75,6 @@ export function udpateUser(userInfos: UserInfos) { }); } -export function deleteUser(sub: string): Promise { - console.debug(`Deleting sub user "${sub}"...`); - return backendFetch(`${USER_ADMIN_URL}/users/${sub}`, { method: 'delete' }) - .then((response: Response) => undefined) - .catch((reason) => { - console.error(`Error while deleting the servers data : ${reason}`); - throw reason; - }); -} - export function deleteUsers(subs: string[]): Promise { console.debug(`Deleting sub users "${JSON.stringify(subs)}"...`); return backendFetch(`${USER_ADMIN_URL}/users`, { @@ -94,7 +84,7 @@ export function deleteUsers(subs: string[]): Promise { }, body: JSON.stringify(subs), }) - .then((response: Response) => undefined) + .then(() => undefined) .catch((reason) => { console.error(`Error while deleting the servers data : ${reason}`); throw reason; @@ -104,7 +94,7 @@ export function deleteUsers(subs: string[]): Promise { export function addUser(sub: string): Promise { console.debug(`Creating sub user "${sub}"...`); return backendFetch(`${USER_ADMIN_URL}/users/${sub}`, { method: 'post' }) - .then((response: Response) => undefined) + .then(() => undefined) .catch((reason) => { console.error(`Error while pushing the data : ${reason}`); throw reason; From 0a63c6086c5e429e24ad6496501213f30602bb1b Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 12 Apr 2024 15:02:20 +0200 Subject: [PATCH 24/35] rev remarks: minor changes --- src/pages/profiles/ProfilesPage.tsx | 14 +++++++------- .../modification/profile-modification-dialog.tsx | 1 - src/pages/users/UsersPage.tsx | 6 +----- src/services/user-admin.ts | 6 +++--- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index e09246f..00f3209 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -145,10 +145,7 @@ const ProfilesPage: FunctionComponent = () => { const addProfile = useCallback( (name: string) => { const profileData: UserProfile = { - id: undefined, name: name, - loadFlowParameterId: undefined, - allParametersLinksValid: undefined, }; UserAdminSrv.addProfile(profileData) .catch((error) => @@ -166,9 +163,9 @@ const ProfilesPage: FunctionComponent = () => { }>({ defaultValues: { name: '' }, //need default not undefined value for html input, else react error at runtime }); - const [open, setOpen] = useState(false); + const [openAddProfileDialog, setOpenAddProfileDialog] = useState(false); const handleClose = () => { - setOpen(false); + setOpenAddProfileDialog(false); reset(); clearErrors(); }; @@ -228,7 +225,10 @@ const ProfilesPage: FunctionComponent = () => { textId="profiles.table.toolbar.add" startIcon={} color="primary" - onClick={useCallback(() => setOpen(true), [])} + onClick={useCallback( + () => setOpenAddProfileDialog(true), + [] + )} /> { /> ( { diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 748d2aa..81e0eaf 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -72,11 +72,7 @@ const UsersPage: FunctionComponent = () => { let profiles: string[] = [ intl.formatMessage({ id: 'users.table.profile.none' }), ]; - if (allProfiles?.length) { - profiles = profiles.concat( - allProfiles.map((p: UserProfile) => p.name) - ); - } + allProfiles?.forEach((p) => profiles.push(p.name)); setprofileNameOptions(profiles); }); }, [intl]); diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index 2831ea2..e3272ab 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -102,10 +102,10 @@ export function addUser(sub: string): Promise { } export type UserProfile = { - id: UUID | undefined; + id?: UUID; name: string; - allParametersLinksValid: boolean | undefined; - loadFlowParameterId: UUID | undefined; + allParametersLinksValid?: boolean; + loadFlowParameterId?: UUID; }; export function fetchProfiles(): Promise { From 4f4f7fd68c4a41eeaacb3a84bb2caed4aa6697e1 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 12 Apr 2024 16:13:47 +0200 Subject: [PATCH 25/35] rev remarks: create ParameterDisplay --- .../modification/parameter-display.tsx | 50 ++++++++++++ .../modification/parameter-selection.tsx | 80 ++++++++----------- .../profile-modification-form.tsx | 3 - 3 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 src/pages/profiles/modification/parameter-display.tsx diff --git a/src/pages/profiles/modification/parameter-display.tsx b/src/pages/profiles/modification/parameter-display.tsx new file mode 100644 index 0000000..3d7f66e --- /dev/null +++ b/src/pages/profiles/modification/parameter-display.tsx @@ -0,0 +1,50 @@ +/** + * 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 React from 'react'; +import { Typography, useTheme } from '@mui/material'; +import { useIntl } from 'react-intl'; + +export interface ParameterDisplayProps { + nameKey: string; + value?: string; + linkValidity?: boolean; +} + +const ParameterDisplay: React.FunctionComponent = ( + props +) => { + const intl = useIntl(); + const theme = useTheme(); + + return ( + + {intl.formatMessage({ + id: props.nameKey, + }) + + ' : ' + + (props.value + ? props.value + : intl.formatMessage({ + id: + props.linkValidity === false + ? 'profiles.form.modification.invalidParameter' + : 'profiles.form.modification.noSelectedParameter', + }))} + + ); +}; + +export default ParameterDisplay; diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index a6df248..3ef88fe 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023, RTE (http://www.rte-france.com) + * 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/. @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import FolderIcon from '@mui/icons-material/Folder'; -import { Grid, IconButton, Tooltip, Typography, useTheme } from '@mui/material'; +import { Grid, IconButton, Tooltip } from '@mui/material'; import { useIntl } from 'react-intl'; import { DirectoryItemSelector, ElementType } from '@gridsuite/commons-ui'; import { useController, useWatch } from 'react-hook-form'; @@ -18,18 +18,17 @@ import { fetchRootFolders, } from 'services/directory'; import { fetchElementsInfos } from 'services/explore'; +import ParameterDisplay from './parameter-display'; export interface ModifyElementSelectionProps { elementType: ElementType; parameterFormId: string; - parameterNameKey: string; } const ParameterSelection: React.FunctionComponent< ModifyElementSelectionProps > = (props) => { const intl = useIntl(); - const theme = useTheme(); const [openDirectorySelector, setOpenDirectorySelector] = useState(false); @@ -79,6 +78,14 @@ const ParameterSelection: React.FunctionComponent< setOpenDirectorySelector(false); }; + const getParameterTranslationKey = () => { + switch (props.elementType) { + case ElementType.LOADFLOW_PARAMETERS: + return 'profiles.form.modification.loadflow.name'; + } + return 'cannot happen'; + }; + return ( @@ -107,49 +114,32 @@ const ParameterSelection: React.FunctionComponent< - - - {intl.formatMessage({ - id: props.parameterNameKey, - }) + - ' : ' + - (selectedElementName - ? selectedElementName - : intl.formatMessage({ - id: - parameterLinkValid === false - ? 'profiles.form.modification.invalidParameter' - : 'profiles.form.modification.noSelectedParameter', - }))} - - + + ); }; diff --git a/src/pages/profiles/modification/profile-modification-form.tsx b/src/pages/profiles/modification/profile-modification-form.tsx index 04575a2..5f01b28 100644 --- a/src/pages/profiles/modification/profile-modification-form.tsx +++ b/src/pages/profiles/modification/profile-modification-form.tsx @@ -35,9 +35,6 @@ const ProfileModificationForm: FunctionComponent = () => { From 87faaaa52f323c53a0c6eadb0eb2f9bae5b05140 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 15 Apr 2024 14:09:10 +0200 Subject: [PATCH 26/35] rev remarks: add ProfilesTable --- src/pages/profiles/ProfilesPage.tsx | 149 ++--------------------- src/pages/profiles/profiles-table.tsx | 163 ++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 139 deletions(-) create mode 100644 src/pages/profiles/profiles-table.tsx diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 00f3209..9f6e614 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -5,14 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - FunctionComponent, - useCallback, - useMemo, - useRef, - useState, -} from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; +import { FunctionComponent, useCallback, useRef, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Button, Dialog, @@ -26,47 +20,17 @@ import { PaperProps, TextField, } from '@mui/material'; -import { - Cancel, - CheckCircle, - ManageAccounts, - RadioButtonUnchecked, -} from '@mui/icons-material'; -import { - GridButton, - GridButtonDelete, - GridTable, - GridTableRef, -} from '../../components/Grid'; +import { ManageAccounts } from '@mui/icons-material'; +import { GridTableRef } from '../../components/Grid'; import { UserAdminSrv, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; -import { - ColDef, - GetRowIdParams, - RowDoubleClickedEvent, - SelectionChangedEvent, - TextFilterParams, -} from 'ag-grid-community'; +import { RowDoubleClickedEvent } from 'ag-grid-community'; import ProfileModificationDialog from './modification/profile-modification-dialog'; import { UUID } from 'crypto'; - -const defaultColDef: ColDef = { - editable: false, - resizable: true, - minWidth: 50, - cellRenderer: 'agAnimateSlideCellRenderer', - showDisabledCheckboxes: true, - rowDrag: false, - sortable: true, -}; - -function getRowId(params: GetRowIdParams): string { - return params.data.id ? params.data.id : ''; -} +import ProfilesTable from './profiles-table'; const ProfilesPage: FunctionComponent = () => { - const intl = useIntl(); const { snackError } = useSnackMessage(); const gridRef = useRef>(null); const gridContext = gridRef.current?.context; @@ -74,74 +38,6 @@ const ProfilesPage: FunctionComponent = () => { useState(false); const [editingProfileId, setEditingProfileId] = useState(); - const columns = useMemo( - (): ColDef[] => [ - { - field: 'name', - cellDataType: 'text', - flex: 3, - lockVisible: true, - filter: true, - headerName: intl.formatMessage({ id: 'profiles.table.id' }), - headerTooltip: intl.formatMessage({ - id: 'profiles.table.id.description', - }), - headerCheckboxSelection: true, - filterParams: { - caseSensitive: false, - trimInput: true, - } as TextFilterParams, - editable: false, - }, - { - field: 'allParametersLinksValid', - cellDataType: 'boolean', - cellStyle: () => ({ - display: 'flex', - alignItems: 'center', - }), - cellRenderer: (params: any) => { - return params.value == null ? ( - - ) : params.value ? ( - - ) : ( - - ); - }, - flex: 1, - headerName: intl.formatMessage({ - id: 'profiles.table.validity', - }), - headerTooltip: intl.formatMessage({ - id: 'profiles.table.validity.description', - }), - sortable: true, - filter: true, - initialSortIndex: 1, - initialSort: 'asc', - }, - ], - [intl] - ); - - const [rowsSelection, setRowsSelection] = useState([]); - const deleteProfiles = useCallback((): Promise | undefined => { - let profileNames = rowsSelection.map((userProfile) => userProfile.name); - return UserAdminSrv.deleteProfiles(profileNames) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'profiles.table.error.delete', - }) - ) - .then(() => gridContext?.refresh?.()); - }, [gridContext, rowsSelection, snackError]); - const deleteProfilesDisabled = useMemo( - () => rowsSelection.length <= 0, - [rowsSelection.length] - ); - const addProfile = useCallback( (name: string) => { const profileData: UserProfile = { @@ -205,36 +101,11 @@ const ProfilesPage: FunctionComponent = () => { onClose={handleCloseProfileModificationDialog} onUpdate={handleUpdateProfileModificationDialog} /> - - ref={gridRef} - dataLoader={UserAdminSrv.fetchProfiles} - columnDefs={columns} - defaultColDef={defaultColDef} - gridId="table-profiles" - getRowId={getRowId} - rowSelection="multiple" + ) => - setRowsSelection(event.api.getSelectedRows() ?? []), - [] - )} - > - } - color="primary" - onClick={useCallback( - () => setOpenAddProfileDialog(true), - [] - )} - /> - - + setOpenAddProfileDialog={setOpenAddProfileDialog} + /> = { + editable: false, + resizable: true, + minWidth: 50, + cellRenderer: 'agAnimateSlideCellRenderer', + showDisabledCheckboxes: true, + rowDrag: false, + sortable: true, +}; + +export interface ProfilesTableProps { + gridRef: RefObject>; + onRowDoubleClicked: (event: RowDoubleClickedEvent) => void; + setOpenAddProfileDialog: (open: boolean) => void; +} + +const ProfilesTable: React.FunctionComponent = (props) => { + const intl = useIntl(); + const { snackError } = useSnackMessage(); + + const [rowsSelection, setRowsSelection] = useState([]); + + function getRowId(params: GetRowIdParams): string { + return params.data.id ? params.data.id : ''; + } + + const onSelectionChanged = useCallback( + (event: SelectionChangedEvent) => + setRowsSelection(event.api.getSelectedRows() ?? []), + [setRowsSelection] + ); + + const onAddButton = useCallback( + () => props.setOpenAddProfileDialog(true), + [props] + ); + + const deleteProfiles = useCallback((): Promise | undefined => { + let profileNames = rowsSelection.map((userProfile) => userProfile.name); + return UserAdminSrv.deleteProfiles(profileNames) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'profiles.table.error.delete', + }) + ) + .then(() => props.gridRef?.current?.context?.refresh?.()); + }, [props.gridRef, rowsSelection, snackError]); + + const deleteProfilesDisabled = useMemo( + () => rowsSelection.length <= 0, + [rowsSelection.length] + ); + + const columns = useMemo( + (): ColDef[] => [ + { + field: 'name', + cellDataType: 'text', + flex: 3, + lockVisible: true, + filter: true, + headerName: intl.formatMessage({ id: 'profiles.table.id' }), + headerTooltip: intl.formatMessage({ + id: 'profiles.table.id.description', + }), + headerCheckboxSelection: true, + filterParams: { + caseSensitive: false, + trimInput: true, + } as TextFilterParams, + editable: false, + }, + { + field: 'allParametersLinksValid', + cellDataType: 'boolean', + cellStyle: () => ({ + display: 'flex', + alignItems: 'center', + }), + cellRenderer: (params: any) => { + return params.value == null ? ( + + ) : params.value ? ( + + ) : ( + + ); + }, + flex: 1, + headerName: intl.formatMessage({ + id: 'profiles.table.validity', + }), + headerTooltip: intl.formatMessage({ + id: 'profiles.table.validity.description', + }), + sortable: true, + filter: true, + initialSortIndex: 1, + initialSort: 'asc', + }, + ], + [intl] + ); + + return ( + + ref={props.gridRef} + dataLoader={UserAdminSrv.fetchProfiles} + columnDefs={columns} + defaultColDef={defaultColDef} + gridId="table-profiles" + getRowId={getRowId} + rowSelection="multiple" + onRowDoubleClicked={props.onRowDoubleClicked} + onSelectionChanged={onSelectionChanged} + > + } + color="primary" + onClick={onAddButton} + /> + + + ); +}; +export default ProfilesTable; From be9bcd643428104fd8ead065d9184a15062e5736 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 15 Apr 2024 15:15:40 +0200 Subject: [PATCH 27/35] rev remarks: add ProfileDialog --- src/pages/profiles/ProfilesPage.tsx | 126 ++----------------- src/pages/profiles/add-profile-dialog.tsx | 142 ++++++++++++++++++++++ 2 files changed, 150 insertions(+), 118 deletions(-) create mode 100644 src/pages/profiles/add-profile-dialog.tsx diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 9f6e614..0c26ce3 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -6,75 +6,28 @@ */ import { FunctionComponent, useCallback, useRef, useState } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { - Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - Grid, - InputAdornment, - Paper, - PaperProps, - TextField, -} from '@mui/material'; -import { ManageAccounts } from '@mui/icons-material'; +import { Grid } from '@mui/material'; import { GridTableRef } from '../../components/Grid'; -import { UserAdminSrv, UserProfile } from '../../services'; -import { useSnackMessage } from '@gridsuite/commons-ui'; -import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { UserProfile } from '../../services'; import { RowDoubleClickedEvent } from 'ag-grid-community'; import ProfileModificationDialog from './modification/profile-modification-dialog'; import { UUID } from 'crypto'; import ProfilesTable from './profiles-table'; +import AddProfileDialog from './add-profile-dialog'; const ProfilesPage: FunctionComponent = () => { - const { snackError } = useSnackMessage(); const gridRef = useRef>(null); const gridContext = gridRef.current?.context; const [openProfileModificationDialog, setOpenProfileModificationDialog] = useState(false); const [editingProfileId, setEditingProfileId] = useState(); - const addProfile = useCallback( - (name: string) => { - const profileData: UserProfile = { - name: name, - }; - UserAdminSrv.addProfile(profileData) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'profiles.table.error.add', - }) - ) - .then(() => gridContext?.refresh?.()); - }, - [gridContext, snackError] - ); - const { handleSubmit, control, reset, clearErrors } = useForm<{ - name: string; - }>({ - defaultValues: { name: '' }, //need default not undefined value for html input, else react error at runtime - }); const [openAddProfileDialog, setOpenAddProfileDialog] = useState(false); - const handleClose = () => { - setOpenAddProfileDialog(false); - reset(); - clearErrors(); - }; - const onSubmit: SubmitHandler<{ name: string }> = (data) => { - addProfile(data.name.trim()); - handleClose(); - }; - const onSubmitForm = handleSubmit(onSubmit); const handleCloseProfileModificationDialog = () => { setOpenProfileModificationDialog(false); setEditingProfileId(undefined); - reset(); + // DBR reset(); }; const handleUpdateProfileModificationDialog = () => { @@ -106,76 +59,13 @@ const ProfilesPage: FunctionComponent = () => { onRowDoubleClicked={onRowDoubleClicked} setOpenAddProfileDialog={setOpenAddProfileDialog} /> - ( - - )} - > - - - - - - - - ( - - } - type="text" - fullWidth - variant="standard" - inputMode="text" - InputProps={{ - startAdornment: ( - - - - ), - }} - error={fieldState?.invalid} - helperText={fieldState?.error?.message} - /> - )} - /> - - - - - - + setOpen={setOpenAddProfileDialog} + />
); }; export default ProfilesPage; - -/* - * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, - * so we must trick typescript check with a cast - */ -const PaperForm: FunctionComponent< - PaperProps<'form'> & { untypedProps?: PaperProps } -> = (props, context) => { - const { untypedProps, ...formProps } = props; - const othersProps = untypedProps as PaperProps<'form'>; //trust me ts - return ; -}; diff --git a/src/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx new file mode 100644 index 0000000..c498820 --- /dev/null +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -0,0 +1,142 @@ +/** + * 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 React, { FunctionComponent, RefObject, useCallback } from 'react'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + InputAdornment, + Paper, + PaperProps, + TextField, +} from '@mui/material'; +import { FormattedMessage } from 'react-intl'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { ManageAccounts } from '@mui/icons-material'; +import { UserAdminSrv, UserProfile } from '../../services'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import { GridTableRef } from '../../components/Grid'; + +export interface AddProfileDialogProps { + gridRef: RefObject>; + open: boolean; + setOpen: (open: boolean) => void; +} + +const AddProfileDialog: React.FunctionComponent = ( + props +) => { + const { snackError } = useSnackMessage(); + + const { handleSubmit, control, reset, clearErrors } = useForm<{ + name: string; + }>({ + defaultValues: { name: '' }, //need default not undefined value for html input, else react error at runtime + }); + + const addProfile = useCallback( + (name: string) => { + const profileData: UserProfile = { + name: name, + }; + UserAdminSrv.addProfile(profileData) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'profiles.table.error.add', + }) + ) + .then(() => props.gridRef?.current?.context?.refresh?.()); + }, + [props.gridRef, snackError] + ); + + const handleClose = () => { + props.setOpen(false); + reset(); + clearErrors(); + }; + + const onSubmit: SubmitHandler<{ name: string }> = (data) => { + addProfile(data.name.trim()); + handleClose(); + }; + const onSubmitForm = handleSubmit(onSubmit); + + return ( + ( + + )} + > + + + + + + + + ( + + } + type="text" + fullWidth + variant="standard" + inputMode="text" + InputProps={{ + startAdornment: ( + + + + ), + }} + error={fieldState?.invalid} + helperText={fieldState?.error?.message} + /> + )} + /> + + + + + + + ); +}; +export default AddProfileDialog; + +/* + * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, + * so we must trick typescript check with a cast + */ +const PaperForm: FunctionComponent< + PaperProps<'form'> & { untypedProps?: PaperProps } +> = (props, context) => { + const { untypedProps, ...formProps } = props; + const othersProps = untypedProps as PaperProps<'form'>; //trust me ts + return ; +}; From f6ab5c624041afed2b883ee42ea5c2f143719a22 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 15 Apr 2024 15:47:30 +0200 Subject: [PATCH 28/35] create common/PaperForm --- src/pages/common/paper-form.tsx | 23 +++++++++++++++++++++++ src/pages/profiles/ProfilesPage.tsx | 1 - src/pages/profiles/add-profile-dialog.tsx | 17 ++--------------- src/pages/users/UsersPage.tsx | 15 +-------------- 4 files changed, 26 insertions(+), 30 deletions(-) create mode 100644 src/pages/common/paper-form.tsx diff --git a/src/pages/common/paper-form.tsx b/src/pages/common/paper-form.tsx new file mode 100644 index 0000000..a71219d --- /dev/null +++ b/src/pages/common/paper-form.tsx @@ -0,0 +1,23 @@ +/** + * 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 React, { FunctionComponent } from 'react'; +import { Paper, PaperProps } from '@mui/material'; + +/* + * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, + * so we must trick typescript check with a cast + */ +const PaperForm: FunctionComponent< + PaperProps<'form'> & { untypedProps?: PaperProps } +> = (props, context) => { + const { untypedProps, ...formProps } = props; + const othersProps = untypedProps as PaperProps<'form'>; //trust me ts + return ; +}; + +export default PaperForm; diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/ProfilesPage.tsx index 0c26ce3..114970b 100644 --- a/src/pages/profiles/ProfilesPage.tsx +++ b/src/pages/profiles/ProfilesPage.tsx @@ -27,7 +27,6 @@ const ProfilesPage: FunctionComponent = () => { const handleCloseProfileModificationDialog = () => { setOpenProfileModificationDialog(false); setEditingProfileId(undefined); - // DBR reset(); }; const handleUpdateProfileModificationDialog = () => { diff --git a/src/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx index c498820..c007ad2 100644 --- a/src/pages/profiles/add-profile-dialog.tsx +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { FunctionComponent, RefObject, useCallback } from 'react'; +import React, { RefObject, useCallback } from 'react'; import { Button, Dialog, @@ -14,8 +14,6 @@ import { DialogContentText, DialogTitle, InputAdornment, - Paper, - PaperProps, TextField, } from '@mui/material'; import { FormattedMessage } from 'react-intl'; @@ -24,6 +22,7 @@ import { ManageAccounts } from '@mui/icons-material'; import { UserAdminSrv, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { GridTableRef } from '../../components/Grid'; +import PaperForm from '../common/paper-form'; export interface AddProfileDialogProps { gridRef: RefObject>; @@ -128,15 +127,3 @@ const AddProfileDialog: React.FunctionComponent = ( ); }; export default AddProfileDialog; - -/* - * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, - * so we must trick typescript check with a cast - */ -const PaperForm: FunctionComponent< - PaperProps<'form'> & { untypedProps?: PaperProps } -> = (props, context) => { - const { untypedProps, ...formProps } = props; - const othersProps = untypedProps as PaperProps<'form'>; //trust me ts - return ; -}; diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index 81e0eaf..b8b4bf6 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -23,8 +23,6 @@ import { DialogTitle, Grid, InputAdornment, - Paper, - PaperProps, TextField, } from '@mui/material'; import { AccountCircle, PersonAdd } from '@mui/icons-material'; @@ -45,6 +43,7 @@ import { SelectionChangedEvent, TextFilterParams, } from 'ag-grid-community'; +import PaperForm from '../common/paper-form'; const defaultColDef: ColDef = { editable: false, @@ -301,15 +300,3 @@ const UsersPage: FunctionComponent = () => { ); }; export default UsersPage; - -/* - * is defined in without generics, which default to `PaperProps => PaperProps<'div'>`, - * so we must trick typescript check with a cast - */ -const PaperForm: FunctionComponent< - PaperProps<'form'> & { untypedProps?: PaperProps } -> = (props, context) => { - const { untypedProps, ...formProps } = props; - const othersProps = untypedProps as PaperProps<'form'>; //trust me ts - return ; -}; From 55a8d4be85e5ba00601516cdbfa3a09e384a2595 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 16 Apr 2024 18:25:18 +0200 Subject: [PATCH 29/35] add missing catch+snackbar --- src/pages/users/UsersPage.tsx | 23 +++++++++++++++-------- src/translations/en.json | 1 + src/translations/fr.json | 1 + 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index b8b4bf6..c9fde82 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -67,14 +67,21 @@ const UsersPage: FunctionComponent = () => { const [profileNameOptions, setprofileNameOptions] = useState([]); useEffect(() => { - UserAdminSrv.fetchProfiles().then((allProfiles: UserProfile[]) => { - let profiles: string[] = [ - intl.formatMessage({ id: 'users.table.profile.none' }), - ]; - allProfiles?.forEach((p) => profiles.push(p.name)); - setprofileNameOptions(profiles); - }); - }, [intl]); + UserAdminSrv.fetchProfiles() + .then((allProfiles: UserProfile[]) => { + let profiles: string[] = [ + intl.formatMessage({ id: 'users.table.profile.none' }), + ]; + allProfiles?.forEach((p) => profiles.push(p.name)); + setprofileNameOptions(profiles); + }) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'users.table.error.profiles', + }) + ); + }, [intl, snackError]); const columns = useMemo( (): ColDef[] => [ diff --git a/src/translations/en.json b/src/translations/en.json index 17c9ce1..078d50c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -37,6 +37,7 @@ "users.table.error.add": "Error while adding user", "users.table.toolbar.add": "Add user", "users.table.profile.none": "", + "users.table.error.profiles": "Error while fetching profiles", "users.table.toolbar.add.label": "Add a user", "users.form.title": "Add a user", "users.form.content": "Please fill in new user data.", diff --git a/src/translations/fr.json b/src/translations/fr.json index b08e3be..f27c6cb 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -39,6 +39,7 @@ "users.table.toolbar.add.label": "Ajouter un utilisateur", "users.table.toolbar.add": "Ajouter utilisateur", "users.table.profile.none": "", + "users.table.error.profiles": "Erreur pendant la récupération des profils", "users.form.title": "Ajouter un utilisateur", "users.form.content": "Veuillez renseigner les informations de l'utilisateur.", "users.form.field.username.label": "ID utilisateur", From 8e1cebdfabfc79674111b86aedf2919ad6345e13 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 16 Apr 2024 19:31:32 +0200 Subject: [PATCH 30/35] rev remarks: move and rename Signed-off-by: David BRAQUART --- src/pages/profiles/index.ts | 2 +- .../profiles/modification}/custom-mui-dialog.tsx | 0 src/pages/profiles/modification/profile-modification-dialog.tsx | 2 +- src/pages/profiles/{ProfilesPage.tsx => profiles-page.tsx} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename src/{components => pages/profiles/modification}/custom-mui-dialog.tsx (100%) rename src/pages/profiles/{ProfilesPage.tsx => profiles-page.tsx} (100%) diff --git a/src/pages/profiles/index.ts b/src/pages/profiles/index.ts index bff8823..6ff05f3 100644 --- a/src/pages/profiles/index.ts +++ b/src/pages/profiles/index.ts @@ -5,4 +5,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default as Profiles } from './ProfilesPage'; +export { default as Profiles } from './profiles-page'; diff --git a/src/components/custom-mui-dialog.tsx b/src/pages/profiles/modification/custom-mui-dialog.tsx similarity index 100% rename from src/components/custom-mui-dialog.tsx rename to src/pages/profiles/modification/custom-mui-dialog.tsx diff --git a/src/pages/profiles/modification/profile-modification-dialog.tsx b/src/pages/profiles/modification/profile-modification-dialog.tsx index 334fe27..bca1d07 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.tsx +++ b/src/pages/profiles/modification/profile-modification-dialog.tsx @@ -15,7 +15,7 @@ import { useForm } from 'react-hook-form'; import { FunctionComponent, useEffect, useState } from 'react'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { getProfile, modifyProfile, UserProfile } from 'services/user-admin'; -import CustomMuiDialog from '../../../components/custom-mui-dialog'; +import CustomMuiDialog from './custom-mui-dialog'; import { UUID } from 'crypto'; // TODO remove FetchStatus when available in commons-ui (available soon) diff --git a/src/pages/profiles/ProfilesPage.tsx b/src/pages/profiles/profiles-page.tsx similarity index 100% rename from src/pages/profiles/ProfilesPage.tsx rename to src/pages/profiles/profiles-page.tsx From b46e64be5e62a1ea69a3ea40aa426fd2bfcfe85d Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 17 Apr 2024 10:40:22 +0200 Subject: [PATCH 31/35] add fetchProfilesWithoutValidityCheck Signed-off-by: David BRAQUART --- src/pages/users/UsersPage.tsx | 2 +- src/services/user-admin.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index c9fde82..d814f2f 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -67,7 +67,7 @@ const UsersPage: FunctionComponent = () => { const [profileNameOptions, setprofileNameOptions] = useState([]); useEffect(() => { - UserAdminSrv.fetchProfiles() + UserAdminSrv.fetchProfilesWithoutValidityCheck() .then((allProfiles: UserProfile[]) => { let profiles: string[] = [ intl.formatMessage({ id: 'users.table.profile.none' }), diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index e3272ab..e08ba75 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -121,6 +121,22 @@ export function fetchProfiles(): Promise { }) as Promise; } +export function fetchProfilesWithoutValidityCheck(): Promise { + console.debug(`Fetching list of profiles...`); + return backendFetchJson( + `${USER_ADMIN_URL}/profiles?checkLinksValidity=false`, + { + headers: { + Accept: 'application/json', + }, + cache: 'default', + } + ).catch((reason) => { + console.error(`Error while fetching the servers data : ${reason}`); + throw reason; + }) as Promise; +} + export function getProfile(profileId: UUID): Promise { console.debug(`Fetching a profile...`); return backendFetchJson(`${USER_ADMIN_URL}/profiles/${profileId}`, { From 2aec3067394c2501a58d968a85662dbb3f39f7f0 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 17 Apr 2024 12:05:45 +0200 Subject: [PATCH 32/35] rev remarks: use 422 error in case of integrity violation Signed-off-by: David BRAQUART --- src/pages/profiles/profiles-table.tsx | 18 ++++++++++++------ src/translations/en.json | 1 + src/translations/fr.json | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/pages/profiles/profiles-table.tsx b/src/pages/profiles/profiles-table.tsx index 8d7a5ff..3147de5 100644 --- a/src/pages/profiles/profiles-table.tsx +++ b/src/pages/profiles/profiles-table.tsx @@ -69,12 +69,18 @@ const ProfilesTable: React.FunctionComponent = (props) => { const deleteProfiles = useCallback((): Promise | undefined => { let profileNames = rowsSelection.map((userProfile) => userProfile.name); return UserAdminSrv.deleteProfiles(profileNames) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'profiles.table.error.delete', - }) - ) + .catch((error) => { + if (error.status === 422) { + snackError({ + headerId: 'profiles.table.integrity.error.delete', + }); + } else { + snackError({ + messageTxt: error.message, + headerId: 'profiles.table.error.delete', + }); + } + }) .then(() => props.gridRef?.current?.context?.refresh?.()); }, [props.gridRef, rowsSelection, snackError]); diff --git a/src/translations/en.json b/src/translations/en.json index 078d50c..01e8b91 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -54,6 +54,7 @@ "profiles.table.validity.description": "Are all parameters links valid?", "profiles.table.error.add": "Error while adding profile", "profiles.table.error.delete": "Error while deleting profile", + "profiles.table.integrity.error.delete": "Error while deleting profile : a profile is still referenced by users", "profiles.form.modification.title": "Edit profile", "profiles.form.modification.defaultParameters": "Default parameters", diff --git a/src/translations/fr.json b/src/translations/fr.json index f27c6cb..2bb7198 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -55,6 +55,7 @@ "profiles.table.validity.description": "Tous les liens vers les paramètres sont-ils valides ?", "profiles.table.error.add": "Erreur pendant l'ajout du profil", "profiles.table.error.delete": "Erreur pendant la suppression de profil", + "profiles.table.integrity.error.delete": "Erreur pendant la suppression de profil: un profil est toujours référencé par des utilisateurs", "profiles.form.modification.title": "Modifier profil", "profiles.form.modification.defaultParameters": "Paramètres par défaut", From 75839600f165a1d9d619c0e809c494382cf6b2a8 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 17 Apr 2024 14:38:49 +0200 Subject: [PATCH 33/35] rev remarks: minor remarks, renaming, etc Signed-off-by: David BRAQUART --- src/pages/common/paper-form.tsx | 2 +- src/pages/profiles/add-profile-dialog.tsx | 8 ++- .../modification/custom-mui-dialog.tsx | 2 +- ...er-display.tsx => linked-path-display.tsx} | 11 ++-- .../modification/parameter-selection.tsx | 22 ++++--- .../profile-modification-dialog.tsx | 63 ++++++++++++------- src/pages/profiles/profiles-page.tsx | 8 +-- src/pages/profiles/profiles-table.tsx | 2 +- src/services/user-admin.ts | 28 ++++++--- src/translations/en.json | 7 ++- src/translations/fr.json | 7 ++- 11 files changed, 98 insertions(+), 62 deletions(-) rename src/pages/profiles/modification/{parameter-display.tsx => linked-path-display.tsx} (76%) diff --git a/src/pages/common/paper-form.tsx b/src/pages/common/paper-form.tsx index a71219d..0409dde 100644 --- a/src/pages/common/paper-form.tsx +++ b/src/pages/common/paper-form.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { FunctionComponent } from 'react'; +import { FunctionComponent } from 'react'; import { Paper, PaperProps } from '@mui/material'; /* diff --git a/src/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx index c007ad2..2892c9f 100644 --- a/src/pages/profiles/add-profile-dialog.tsx +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { RefObject, useCallback } from 'react'; +import { RefObject, useCallback } from 'react'; import { Button, Dialog, @@ -68,14 +68,16 @@ const AddProfileDialog: React.FunctionComponent = ( addProfile(data.name.trim()); handleClose(); }; - const onSubmitForm = handleSubmit(onSubmit); return ( ( - + )} > diff --git a/src/pages/profiles/modification/custom-mui-dialog.tsx b/src/pages/profiles/modification/custom-mui-dialog.tsx index 740622d..5a87cdc 100644 --- a/src/pages/profiles/modification/custom-mui-dialog.tsx +++ b/src/pages/profiles/modification/custom-mui-dialog.tsx @@ -7,7 +7,7 @@ // TODO: this file is going to be available soon in commons-ui -import React, { FunctionComponent } from 'react'; +import { FunctionComponent } from 'react'; import { FieldErrors, FormProvider } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { diff --git a/src/pages/profiles/modification/parameter-display.tsx b/src/pages/profiles/modification/linked-path-display.tsx similarity index 76% rename from src/pages/profiles/modification/parameter-display.tsx rename to src/pages/profiles/modification/linked-path-display.tsx index 3d7f66e..b1327e4 100644 --- a/src/pages/profiles/modification/parameter-display.tsx +++ b/src/pages/profiles/modification/linked-path-display.tsx @@ -5,17 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React from 'react'; import { Typography, useTheme } from '@mui/material'; import { useIntl } from 'react-intl'; -export interface ParameterDisplayProps { +export interface LinkedPathDisplayProps { nameKey: string; value?: string; linkValidity?: boolean; } -const ParameterDisplay: React.FunctionComponent = ( +const LinkedPathDisplay: React.FunctionComponent = ( props ) => { const intl = useIntl(); @@ -40,11 +39,11 @@ const ParameterDisplay: React.FunctionComponent = ( : intl.formatMessage({ id: props.linkValidity === false - ? 'profiles.form.modification.invalidParameter' - : 'profiles.form.modification.noSelectedParameter', + ? 'linked.path.display.invalidLink' + : 'linked.path.display.noLink', }))} ); }; -export default ParameterDisplay; +export default LinkedPathDisplay; diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index 3ef88fe..169c626 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import FolderIcon from '@mui/icons-material/Folder'; import { Grid, IconButton, Tooltip } from '@mui/material'; @@ -18,16 +18,20 @@ import { fetchRootFolders, } from 'services/directory'; import { fetchElementsInfos } from 'services/explore'; -import ParameterDisplay from './parameter-display'; +import LinkedPathDisplay from './linked-path-display'; -export interface ModifyElementSelectionProps { - elementType: ElementType; +export interface ParameterSelectionProps { + elementType: + | ElementType.LOADFLOW_PARAMETERS + | ElementType.SECURITY_ANALYSIS_PARAMETERS + | ElementType.SENSITIVITY_PARAMETERS + | ElementType.VOLTAGE_INIT_PARAMETERS; parameterFormId: string; } -const ParameterSelection: React.FunctionComponent< - ModifyElementSelectionProps -> = (props) => { +const ParameterSelection: React.FunctionComponent = ( + props +) => { const intl = useIntl(); const [openDirectorySelector, setOpenDirectorySelector] = @@ -115,7 +119,7 @@ const ParameterSelection: React.FunctionComponent<
- { - if (profileId) { - const profileData: UserProfile = { - id: profileId, - name: profileFormData[PROFILE_NAME], - loadFlowParameterId: profileFormData[LF_PARAM_ID], - }; - modifyProfile(profileData) - .catch((error) => { - snackError({ - messageTxt: error.message, - headerId: 'profiles.form.modification.updateError', + const onSubmit = useCallback( + (profileFormData: any) => { + if (profileId) { + const profileData: UserProfile = { + id: profileId, + name: profileFormData[PROFILE_NAME], + loadFlowParameterId: profileFormData[LF_PARAM_ID], + }; + modifyProfile(profileData) + .catch((error) => { + snackError({ + messageTxt: error.message, + headerId: 'profiles.form.modification.updateError', + }); + }) + .then(() => { + onUpdate(); }); - }) - .then(() => { - onUpdate(); - }); - } - }; + } + }, + [profileId, snackError, onUpdate] + ); - const onDialogClose = () => { + const onDialogClose = useCallback(() => { setDataFetchStatus(FetchStatus.IDLE); onClose(); - }; + }, [onClose]); useEffect(() => { if (profileId && open) { @@ -101,7 +110,15 @@ const ProfileModificationDialog: FunctionComponent< } }, [profileId, open, reset, snackError]); - const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; + const isDataReady = useMemo( + () => dataFetchStatus === FetchStatus.FETCH_SUCCESS, + [dataFetchStatus] + ); + + const isDataFetching = useMemo( + () => dataFetchStatus === FetchStatus.FETCHING, + [dataFetchStatus] + ); return ( {isDataReady && } diff --git a/src/pages/profiles/profiles-page.tsx b/src/pages/profiles/profiles-page.tsx index 114970b..bb2450e 100644 --- a/src/pages/profiles/profiles-page.tsx +++ b/src/pages/profiles/profiles-page.tsx @@ -24,15 +24,15 @@ const ProfilesPage: FunctionComponent = () => { const [openAddProfileDialog, setOpenAddProfileDialog] = useState(false); - const handleCloseProfileModificationDialog = () => { + const handleCloseProfileModificationDialog = useCallback(() => { setOpenProfileModificationDialog(false); setEditingProfileId(undefined); - }; + }, []); - const handleUpdateProfileModificationDialog = () => { + const handleUpdateProfileModificationDialog = useCallback(() => { gridContext?.refresh?.(); handleCloseProfileModificationDialog(); - }; + }, [gridContext, handleCloseProfileModificationDialog]); const onRowDoubleClicked = useCallback( (event: RowDoubleClickedEvent) => { diff --git a/src/pages/profiles/profiles-table.tsx b/src/pages/profiles/profiles-table.tsx index 3147de5..c64a525 100644 --- a/src/pages/profiles/profiles-table.tsx +++ b/src/pages/profiles/profiles-table.tsx @@ -66,7 +66,7 @@ const ProfilesTable: React.FunctionComponent = (props) => { [props] ); - const deleteProfiles = useCallback((): Promise | undefined => { + const deleteProfiles = useCallback(() => { let profileNames = rowsSelection.map((userProfile) => userProfile.name); return UserAdminSrv.deleteProfiles(profileNames) .catch((error) => { diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts index e08ba75..fd96726 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -72,7 +72,12 @@ export function udpateUser(userInfos: UserInfos) { 'Content-Type': 'application/json', }, body: JSON.stringify(userInfos), - }); + }) + .then(() => undefined) + .catch((reason) => { + console.error(`Error while updating user : ${reason}`); + throw reason; + }); } export function deleteUsers(subs: string[]): Promise { @@ -96,7 +101,7 @@ export function addUser(sub: string): Promise { return backendFetch(`${USER_ADMIN_URL}/users/${sub}`, { method: 'post' }) .then(() => undefined) .catch((reason) => { - console.error(`Error while pushing the data : ${reason}`); + console.error(`Error while adding user : ${reason}`); throw reason; }); } @@ -116,7 +121,7 @@ export function fetchProfiles(): Promise { }, cache: 'default', }).catch((reason) => { - console.error(`Error while fetching the servers data : ${reason}`); + console.error(`Error while fetching list of profiles : ${reason}`); throw reason; }) as Promise; } @@ -132,7 +137,9 @@ export function fetchProfilesWithoutValidityCheck(): Promise { cache: 'default', } ).catch((reason) => { - console.error(`Error while fetching the servers data : ${reason}`); + console.error( + `Error while fetching list of profiles (without check) : ${reason}` + ); throw reason; }) as Promise; } @@ -145,7 +152,7 @@ export function getProfile(profileId: UUID): Promise { }, cache: 'default', }).catch((reason) => { - console.error(`Error while fetching the servers data : ${reason}`); + console.error(`Error while fetching profile : ${reason}`); throw reason; }) as Promise; } @@ -160,7 +167,12 @@ export function modifyProfile(profileData: UserProfile) { 'Content-Type': 'application/json', }, body: JSON.stringify(profileData), - }); + }) + .then(() => undefined) + .catch((reason) => { + console.error(`Error while updating the data : ${reason}`); + throw reason; + }); } export function addProfile(profileData: UserProfile): Promise { @@ -175,7 +187,7 @@ export function addProfile(profileData: UserProfile): Promise { }) .then(() => undefined) .catch((reason) => { - console.error(`Error while pushing the data : ${reason}`); + console.error(`Error while pushing adding profile : ${reason}`); throw reason; }); } @@ -191,7 +203,7 @@ export function deleteProfiles(names: string[]): Promise { }) .then(() => undefined) .catch((reason) => { - console.error(`Error while deleting the servers data : ${reason}`); + console.error(`Error while deleting profiles : ${reason}`); throw reason; }); } diff --git a/src/translations/en.json b/src/translations/en.json index 01e8b91..c092079 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -58,13 +58,14 @@ "profiles.form.modification.title": "Edit profile", "profiles.form.modification.defaultParameters": "Default parameters", - "profiles.form.modification.noSelectedParameter": "no parameters selected.", - "profiles.form.modification.invalidParameter": "invalid parameters link.", "profiles.form.modification.parameter.choose.tooltip": "Choose parameters", "profiles.form.modification.parameter.reset.tooltip": "Set undefined parameters", "profiles.form.modification.parameterSelection.dialog.title": "Choose parameters", "profiles.form.modification.parameterSelection.dialog.message": "Please choose parameters", "profiles.form.modification.loadflow.name": "Loadflow", "profiles.form.modification.readError": "Error while reading the profile", - "profiles.form.modification.updateError": "Error while updating the profile" + "profiles.form.modification.updateError": "Error while updating the profile", + + "linked.path.display.noLink": "no parameters selected.", + "linked.path.display.invalidLink": "invalid parameters link." } diff --git a/src/translations/fr.json b/src/translations/fr.json index 2bb7198..35d380c 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -59,13 +59,14 @@ "profiles.form.modification.title": "Modifier profil", "profiles.form.modification.defaultParameters": "Paramètres par défaut", - "profiles.form.modification.noSelectedParameter": "pas de paramètres selectionnés.", - "profiles.form.modification.invalidParameter": "lien vers paramètres invalide.", "profiles.form.modification.parameter.choose.tooltip": "Choisir paramètres", "profiles.form.modification.parameter.reset.tooltip": "Ne pas définir de paramètres", "profiles.form.modification.parameterSelection.dialog.title": "Choisir des paramètres", "profiles.form.modification.parameterSelection.dialog.message": "Veuillez choisir des paramètres", "profiles.form.modification.loadflow.name": "Calcul de répartition", "profiles.form.modification.readError": "Erreur lors de la lecture du profil", - "profiles.form.modification.updateError": "Erreur lors de la modification du profil" + "profiles.form.modification.updateError": "Erreur lors de la modification du profil", + + "linked.path.display.invalidLink": "lien vers paramètres invalide.", + "linked.path.display.noLink": "pas de paramètres selectionnés." } From db9da6689b98a42b23391b1f50d4c8d783fde0d8 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 17 Apr 2024 17:59:49 +0200 Subject: [PATCH 34/35] rev remarks: last change Signed-off-by: David BRAQUART --- src/pages/profiles/add-profile-dialog.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx index 2892c9f..66312ce 100644 --- a/src/pages/profiles/add-profile-dialog.tsx +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -58,16 +58,19 @@ const AddProfileDialog: React.FunctionComponent = ( [props.gridRef, snackError] ); - const handleClose = () => { + const handleClose = useCallback(() => { props.setOpen(false); reset(); clearErrors(); - }; + }, [clearErrors, props, reset]); - const onSubmit: SubmitHandler<{ name: string }> = (data) => { - addProfile(data.name.trim()); - handleClose(); - }; + const onSubmit: SubmitHandler<{ name: string }> = useCallback( + (data) => { + addProfile(data.name.trim()); + handleClose(); + }, + [addProfile, handleClose] + ); return ( Date: Wed, 17 Apr 2024 18:25:04 +0200 Subject: [PATCH 35/35] clean code Signed-off-by: David BRAQUART --- src/pages/profiles/add-profile-dialog.tsx | 12 +++++------- .../profiles/modification/linked-path-display.tsx | 3 ++- .../profiles/modification/parameter-selection.tsx | 4 ++-- src/pages/profiles/profiles-table.tsx | 10 ++++++++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx index 66312ce..f11395e 100644 --- a/src/pages/profiles/add-profile-dialog.tsx +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { RefObject, useCallback } from 'react'; +import { FunctionComponent, RefObject, useCallback } from 'react'; import { Button, Dialog, @@ -17,7 +17,7 @@ import { TextField, } from '@mui/material'; import { FormattedMessage } from 'react-intl'; -import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { Controller, useForm } from 'react-hook-form'; import { ManageAccounts } from '@mui/icons-material'; import { UserAdminSrv, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; @@ -30,9 +30,7 @@ export interface AddProfileDialogProps { setOpen: (open: boolean) => void; } -const AddProfileDialog: React.FunctionComponent = ( - props -) => { +const AddProfileDialog: FunctionComponent = (props) => { const { snackError } = useSnackMessage(); const { handleSubmit, control, reset, clearErrors } = useForm<{ @@ -64,8 +62,8 @@ const AddProfileDialog: React.FunctionComponent = ( clearErrors(); }, [clearErrors, props, reset]); - const onSubmit: SubmitHandler<{ name: string }> = useCallback( - (data) => { + const onSubmit = useCallback( + (data: { name: string }) => { addProfile(data.name.trim()); handleClose(); }, diff --git a/src/pages/profiles/modification/linked-path-display.tsx b/src/pages/profiles/modification/linked-path-display.tsx index b1327e4..a29ea57 100644 --- a/src/pages/profiles/modification/linked-path-display.tsx +++ b/src/pages/profiles/modification/linked-path-display.tsx @@ -7,6 +7,7 @@ import { Typography, useTheme } from '@mui/material'; import { useIntl } from 'react-intl'; +import { FunctionComponent } from 'react'; export interface LinkedPathDisplayProps { nameKey: string; @@ -14,7 +15,7 @@ export interface LinkedPathDisplayProps { linkValidity?: boolean; } -const LinkedPathDisplay: React.FunctionComponent = ( +const LinkedPathDisplay: FunctionComponent = ( props ) => { const intl = useIntl(); diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index 169c626..688d3eb 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useEffect, useState } from 'react'; +import { FunctionComponent, useEffect, useState } from 'react'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import FolderIcon from '@mui/icons-material/Folder'; import { Grid, IconButton, Tooltip } from '@mui/material'; @@ -29,7 +29,7 @@ export interface ParameterSelectionProps { parameterFormId: string; } -const ParameterSelection: React.FunctionComponent = ( +const ParameterSelection: FunctionComponent = ( props ) => { const intl = useIntl(); diff --git a/src/pages/profiles/profiles-table.tsx b/src/pages/profiles/profiles-table.tsx index c64a525..82eb611 100644 --- a/src/pages/profiles/profiles-table.tsx +++ b/src/pages/profiles/profiles-table.tsx @@ -5,7 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { RefObject, useCallback, useMemo, useState } from 'react'; +import { + FunctionComponent, + RefObject, + useCallback, + useMemo, + useState, +} from 'react'; import { useIntl } from 'react-intl'; import { Cancel, @@ -45,7 +51,7 @@ export interface ProfilesTableProps { setOpenAddProfileDialog: (open: boolean) => void; } -const ProfilesTable: React.FunctionComponent = (props) => { +const ProfilesTable: FunctionComponent = (props) => { const intl = useIntl(); const { snackError } = useSnackMessage();