diff --git a/.env b/.env index fab96a8..bcbdc28 100644 --- a/.env +++ b/.env @@ -4,8 +4,8 @@ REACT_APP_NAME=GridDynaApp EXTEND_ESLINT=true -REACT_APP_API_PREFIX=api -REACT_APP_GATEWAY_PREFIX=/gateway -REACT_APP_URI=/dynamic-mapping-server REACT_APP_WS_GATEWAY=ws/gateway -REACT_APP_STUDY_URI=/study-server +REACT_APP_API_GATEWAY=api/gateway + +REACT_APP_API_PREFIX=api/gateway +REACT_APP_DYNAMAP_SVC=dynamic-mapping diff --git a/.env.development b/.env.development index 86192d9..8f03c67 100644 --- a/.env.development +++ b/.env.development @@ -2,8 +2,8 @@ REACT_APP_USE_AUTHENTICATION=false REACT_APP_NAME=GridDynaApp -REACT_APP_API_PREFIX=api -REACT_APP_GATEWAY_PREFIX=/gateway -REACT_APP_URI=/dynamic-mapping-server REACT_APP_WS_GATEWAY=ws/gateway -REACT_APP_STUDY_URI=/study-server +REACT_APP_API_GATEWAY=api/gateway + +REACT_APP_API_PREFIX=api +REACT_APP_DYNAMAP_SVC=dynamic-mapping-server diff --git a/package-lock.json b/package-lock.json index c06aea0..1e80af4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.63.4", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.63.4.tgz", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -2750,8 +2750,9 @@ }, "node_modules/@gridsuite/commons-ui": { "version": "0.63.4", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.63.4.tgz", - "integrity": "sha512-0o0pfC8uySLLaEJSdur4/K54jnV9TResXwBI7JD51mfCNq1woXkzlBWmpDi7ruLwGIvGSF/8SR9Tw0F60Eovjw==", + "resolved": "file:../commons-ui/gridsuite-commons-ui-0.63.4.tgz", + "integrity": "sha512-3vvFWa40RY81G57P5H8zUUbAhUwcd1ayB1NWWZnHRvizq+0woRcw9qiy+bTdnt0mA7qznq9a5+IMcQU2bOPQPw==", + "license": "MPL-2.0", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", @@ -2767,6 +2768,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", "react-virtualized": "^9.22.5", + "type-fest": "^4.21.0", "uuid": "^9.0.1" }, "engines": { @@ -2792,6 +2794,7 @@ "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", "react-router-dom": "^6.22.3", + "reconnecting-websocket": "^4.4.0", "yup": "^1.4.0" } }, @@ -2803,6 +2806,17 @@ "node": ">=6" } }, + "node_modules/@gridsuite/commons-ui/node_modules/type-fest": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@hookform/resolvers": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz", @@ -16565,6 +16579,12 @@ "node": ">=8.10.0" } }, + "node_modules/reconnecting-websocket": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz", + "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==", + "peer": true + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", diff --git a/package.json b/package.json index 03840c2..66ec855 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.63.4", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.63.4.tgz", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", diff --git a/src/components/2-molecules/Header.jsx b/src/components/2-molecules/Header.jsx index b9c5c10..51d35db 100644 --- a/src/components/2-molecules/Header.jsx +++ b/src/components/2-molecules/Header.jsx @@ -9,9 +9,8 @@ import { Grid, Tooltip, Typography } from '@mui/material'; import { AddIconButton, AttachButton, SaveButton } from '../1-atoms/buttons/'; import React from 'react'; import PropTypes from 'prop-types'; - +import { mergeSx } from '@gridsuite/commons-ui'; import { styles } from './HeaderStyles'; -import { mergeSx } from 'utils/functions'; const outdatedLabel = 'Generated elements are outdated, re-generate them to delete this warning'; diff --git a/src/components/2-molecules/SetGroupSelect.tsx b/src/components/2-molecules/SetGroupSelect.tsx index ebee5d3..00650aa 100644 --- a/src/components/2-molecules/SetGroupSelect.tsx +++ b/src/components/2-molecules/SetGroupSelect.tsx @@ -6,13 +6,13 @@ */ import { Checkbox, Grid, Typography } from '@mui/material'; +import { mergeSx } from '@gridsuite/commons-ui'; import Select from '../1-atoms/Select'; import { styles } from './SetGroupSelectStyle'; import React, { useEffect, useState } from 'react'; import { SetType } from '../../constants/models'; import { EditButton } from '../1-atoms/buttons'; import { Group, Model } from '../../redux/types/model.type'; -import { mergeSx } from 'utils/functions'; const setLabel = 'and use parameters group'; const editGroupLabel = 'Edit the parameters group and/or the parameters sets'; @@ -92,7 +92,11 @@ const SetGroupSelect = (props: SetGroupSelectProps) => { {`${setLabel} :`} - + { const { diff --git a/src/components/3-organisms/Filter.jsx b/src/components/3-organisms/Filter.jsx index 62f7c88..6536a57 100644 --- a/src/components/3-organisms/Filter.jsx +++ b/src/components/3-organisms/Filter.jsx @@ -8,11 +8,10 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { Box, Grid, Tooltip, Typography } from '@mui/material'; -import { CustomReactQueryBuilder, EXPERT_FILTER_FIELDS, EXPERT_FILTER_QUERY } from '@gridsuite/commons-ui'; +import { CustomReactQueryBuilder, EXPERT_FILTER_FIELDS, EXPERT_FILTER_QUERY, mergeSx } from '@gridsuite/commons-ui'; import { useIntl } from 'react-intl'; import { AddIconButton, DeleteButton } from '../1-atoms/buttons'; import { styles } from './FilterStyle'; -import { mergeSx } from '../../utils/functions'; import InfoIcon from '@mui/icons-material/Info'; const filterLabel = 'Where:'; diff --git a/src/components/3-organisms/Rule.jsx b/src/components/3-organisms/Rule.jsx index c9bd84e..5786b9e 100644 --- a/src/components/3-organisms/Rule.jsx +++ b/src/components/3-organisms/Rule.jsx @@ -7,12 +7,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { mergeSx } from '@gridsuite/commons-ui'; import { CopyButton, DeleteButton } from '../1-atoms/buttons'; import { Grid, Paper, Typography } from '@mui/material'; import { styles } from './RuleStyle'; import ModelSelect from '../2-molecules/ModelSelect'; import SetGroupSelect from '../2-molecules/SetGroupSelect'; -import { mergeSx } from 'utils/functions'; const equipmentLabel = 'Each'; const deleteRuleLabel = 'Delete model'; diff --git a/src/components/3-organisms/SetEditor.jsx b/src/components/3-organisms/SetEditor.jsx index 659db2e..99baf57 100644 --- a/src/components/3-organisms/SetEditor.jsx +++ b/src/components/3-organisms/SetEditor.jsx @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { ParameterOrigin, ParameterType } from '../../constants/models'; import { Box, Grid, TextField, Tooltip, Typography } from '@mui/material'; import InfoIcon from '@mui/icons-material/Info'; -import * as _ from 'lodash'; +import { cloneDeep } from 'lodash'; import { isParameterValueValid } from '../../utils/parameters'; const infoTypeLabel = 'This parameter is of type '; @@ -38,7 +38,7 @@ const SetEditor = (props) => { filteredDefinitions.find((definition) => definition.name === parameterChanged).type === ParameterType.DOUBLE ? newValue.replace(',', '.') : newValue; - const updatedSet = _.cloneDeep(set); + const updatedSet = cloneDeep(set); updatedSet.parameters.find((parameter) => parameter.name === parameterChanged).value = newValueToUse; saveSet(updatedSet); }; @@ -46,7 +46,7 @@ const SetEditor = (props) => { return ( {set.name} - {_.cloneDeep(filteredDefinitions) + {cloneDeep(filteredDefinitions) .sort((a, b) => valueOrigin(a.origin) - valueOrigin(b.origin)) .map((definition) => { const correspondingParameter = set.parameters.find((param) => param.name === definition.name); diff --git a/src/components/3-organisms/automaton/AutomatonProperties.tsx b/src/components/3-organisms/automaton/AutomatonProperties.tsx index 9089173..0fa41da 100644 --- a/src/components/3-organisms/automaton/AutomatonProperties.tsx +++ b/src/components/3-organisms/automaton/AutomatonProperties.tsx @@ -10,7 +10,7 @@ import { Divider, Grid, Typography } from '@mui/material'; import Autocomplete from '../../1-atoms/Autocomplete'; import { styles } from './AutomatonPropertiesStyle'; import { getPossibleOptionsForProperty } from '../../../utils/automata'; -import * as _ from 'lodash'; +import { isArray, join, map, split, trim } from 'lodash'; import { Automaton } from '../../../redux/types/mapping.type'; import { AutomationDefinition } from '../../../redux/types/model.type'; import { EquipmentValues } from '../../../redux/types/network.type'; @@ -39,7 +39,7 @@ const AutomatonProperties = ({ propertyType )( // convert an array to a string content with VALUE_DELIMITER - _.isArray(propertyValue) ? _.join(propertyValue, VALUE_DELIMITER) : propertyValue + isArray(propertyValue) ? join(propertyValue, VALUE_DELIMITER) : propertyValue ); }, [onChangeProperty] @@ -53,7 +53,7 @@ const AutomatonProperties = ({ // convert a string content with VALUE_DELIMITER to an array const propertyValue = propertyDefinition.multiple - ? _.map(_.split(property?.value, VALUE_DELIMITER), _.trim) + ? map(split(property?.value, VALUE_DELIMITER), trim) : property?.value ?? ''; const options = diff --git a/src/components/3-organisms/hooks/useSetSearch.js b/src/components/3-organisms/hooks/useSetSearch.js index 0ed04b9..f2e0bff 100644 --- a/src/components/3-organisms/hooks/useSetSearch.js +++ b/src/components/3-organisms/hooks/useSetSearch.js @@ -13,7 +13,7 @@ import { makeGetSearchSets, ModelSlice, } from '../../../redux/slices/Model'; -import _ from 'lodash'; +import { cloneDeep, find, forEach, reduce } from 'lodash'; import { useDispatch } from 'react-redux'; export default function useSetSearch(currentGroup, currentSet) { @@ -49,13 +49,13 @@ export default function useSetSearch(currentGroup, currentSet) { } const updatedSets = isAll - ? _.reduce(currentGroup?.sets, (acc, set) => [...acc, _.cloneDeep(set)], []) - : [_.cloneDeep(currentSet)]; + ? reduce(currentGroup?.sets, (acc, set) => [...acc, cloneDeep(set)], []) + : [cloneDeep(currentSet)]; // fill with values in the provided sets - _.forEach(updatedSets, (updatedSet) => { - _.forEach(updatedSet?.parameters, (parameter) => { - const templateParameter = _.find(set?.parameters, (elem) => elem.name === parameter.name); + forEach(updatedSets, (updatedSet) => { + forEach(updatedSet?.parameters, (parameter) => { + const templateParameter = find(set?.parameters, (elem) => elem.name === parameter.name); templateParameter && (parameter.value = templateParameter?.value); }); }); diff --git a/src/components/app.js b/src/components/app.js index 4c9db0e..3568522 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -10,23 +10,22 @@ import { useDispatch, useSelector } from 'react-redux'; import { Navigate, Route, Routes, useLocation, useMatch, useNavigate } from 'react-router-dom'; import { Box, CssBaseline } from '@mui/material'; import { createTheme, StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; -import { LIGHT_THEME } from '../redux/slices/Theme'; import { AuthenticationRouter, CardErrorBoundary, getPreLoginPath, initializeAuthenticationDev, initializeAuthenticationProd, + LIGHT_THEME, logout, TopBar, } from '@gridsuite/commons-ui'; import { FormattedMessage } from 'react-intl'; import { ReactComponent as PowsyblLogo } from '../images/powsybl_logo.svg'; import AppPackage from '../../package.json'; -import { fetchAppsAndUrls, fetchIdpSettings, fetchValidateUser, fetchVersion } from '../utils/rest-api'; -import { getServersInfos } from '../rest/studyAPI'; import { UserSlice } from '../redux/slices/User'; import RootContainer from '../containers/RootContainer'; +import { appLocalSrv, appsMetadataSrv, studySrv, userAdminSrv } from '../services'; const lightTheme = createTheme({ palette: { @@ -65,6 +64,8 @@ const App = () => { const navigate = useNavigate(); + const onLogoClick = useCallback(() => navigate('/', { replace: true }), [navigate]); + const dispatch = useDispatch(); const authenticationDispatch = useCallback( @@ -72,6 +73,11 @@ const App = () => { [dispatch] ); + const onLogoutClick = useCallback( + () => logout(authenticationDispatch, userManager.instance), + [authenticationDispatch, userManager.instance] + ); + const location = useLocation(); // Can't use lazy initializer because useMatch is a hook @@ -97,8 +103,8 @@ const App = () => { ? initializeAuthenticationProd( authenticationDispatch, initialMatchSilentRenewCallbackUrl != null, - fetchIdpSettings, - fetchValidateUser, + appLocalSrv.fetchIdpSettings, + userAdminSrv.fetchValidateUser, initialMatchSigninCallbackUrl != null ) : initializeAuthenticationDev( @@ -120,12 +126,18 @@ const App = () => { useEffect(() => { if (user !== null) { - fetchAppsAndUrls().then((res) => { + appsMetadataSrv.fetchAppsMetadata().then((res) => { setAppsAndUrls(res); }); } }, [user]); + const additionalModulesFetcher = useCallback(() => studySrv.getServersInfos('dyna'), []); + const globalVersionFetcher = useCallback( + () => appsMetadataSrv.fetchVersion().then((res) => res?.deployVersion), + [] + ); + return ( @@ -137,12 +149,12 @@ const App = () => { appLogo={} appVersion={AppPackage.version} appLicense={AppPackage.license} - onLogoClick={() => navigate('/', { replace: true })} - onLogoutClick={() => logout(authenticationDispatch, userManager.instance)} + onLogoClick={onLogoClick} + onLogoutClick={onLogoutClick} user={user} appsAndUrls={appsAndUrls} - globalVersionPromise={() => fetchVersion().then((res) => res?.deployVersion)} - additionalModulesPromise={getServersInfos} + globalVersionPromise={globalVersionFetcher} + additionalModulesPromise={additionalModulesFetcher} /> {user !== null ? ( diff --git a/src/hooks/react-hook-form/form/useDataUpdate.ts b/src/hooks/react-hook-form/form/useDataUpdate.ts index 774d785..b8f8e31 100644 --- a/src/hooks/react-hook-form/form/useDataUpdate.ts +++ b/src/hooks/react-hook-form/form/useDataUpdate.ts @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import { FieldErrors, FieldValues, UseFormReturn } from 'react-hook-form'; import { usePrevious } from '@gridsuite/commons-ui'; -import _ from 'lodash'; +import { isEqual } from 'lodash'; const useDataUpdate = ( formApi: UseFormReturn, @@ -18,7 +18,7 @@ const useDataUpdate = ( const prevFormData = usePrevious(formData); useEffect(() => { - if (!_.isEqual(prevFormData, formData)) { + if (!isEqual(prevFormData, formData)) { formApi.handleSubmit(onValid, onInvalid)(); } }, [formApi, onValid, onInvalid, formData, prevFormData]); diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 7b288b8..49bf58a 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -6,3 +6,13 @@ */ /// + +namespace NodeJS { + interface ProcessEnv { + REACT_APP_API_GATEWAY: string; + REACT_APP_WS_GATEWAY: string; + + REACT_APP_API_PREFIX: string; + REACT_APP_DYNAMAP_SVC: string; + } +} diff --git a/src/redux/local-storage.js b/src/redux/local-storage.js deleted file mode 100644 index 309296e..0000000 --- a/src/redux/local-storage.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2020, 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 { DARK_THEME } from './slices/Theme'; - -const LOCAL_STORAGE_THEME_KEY = process.env.REACT_APP_NAME + '_THEME'; - -export const getLocalStorageTheme = () => { - return localStorage.getItem(LOCAL_STORAGE_THEME_KEY) || DARK_THEME; -}; - -export const saveLocalStorageTheme = (theme) => { - localStorage.setItem(LOCAL_STORAGE_THEME_KEY, theme); -}; diff --git a/src/redux/slices/Mapping.js b/src/redux/slices/Mapping.js index bdbdaa0..fa48718 100644 --- a/src/redux/slices/Mapping.js +++ b/src/redux/slices/Mapping.js @@ -6,10 +6,8 @@ */ import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit'; -import * as mappingsAPI from '../../rest/mappingsAPI'; -import * as _ from 'lodash'; +import { cloneDeep, isEmpty, isEqual } from 'lodash'; import RequestStatus from '../../constants/RequestStatus'; -import * as networkAPI from '../../rest/networkAPI'; import { AutomatonFamily } from '../../constants/automatonDefinition'; import { RuleEquipmentTypes } from '../../constants/equipmentType'; import { @@ -23,6 +21,7 @@ import { import { v4 as uuid4 } from 'uuid'; import { enrichIdRqbQuery } from '../../utils/rqb-utils'; import { assignArray } from '../../utils/functions'; +import { dynamicMappingSrv } from '../../services'; const initialState = { mappings: [], @@ -59,7 +58,7 @@ export const DEFAULT_NAME = 'default'; const ruleMatcher = (rule1) => (rule2) => rule1?.id === rule2?.id; const transformMapping = (receivedMapping) => { - const mapping = _.cloneDeep(receivedMapping); + const mapping = cloneDeep(receivedMapping); mapping.rules = mapping.rules.map((rule) => { rule['type'] = rule.equipmentType; delete rule.equipmentType; @@ -215,7 +214,7 @@ const checkFilterValidity = (filter, isLast) => { // only last rule allows empty filter return !!isLast; } - const isQueryExist = !_.isEmpty(filter.rules); + const isQueryExist = !isEmpty(filter.rules); const isQueryValid = isQueryExist && rqbQuerySchemaValidator(yup.object()).isValidSync(filter.rules); return isQueryValid; }; @@ -380,7 +379,7 @@ export const isModified = createSelector( const foundMapping = savedMappings.find((mapping) => mapping.name === activeName); function ignoreInternalProperties(rule) { - const ruleToTest = _.cloneDeep(rule); + const ruleToTest = cloneDeep(rule); delete ruleToTest.matches; // ignore matches which is used for matched equipment ids delete ruleToTest.filterDirty; // ignore the derived field @@ -397,9 +396,9 @@ export const isModified = createSelector( } return !( - _.isEqual(activeRules.map(ignoreInternalProperties), foundMapping.rules.map(ignoreInternalProperties)) && - _.isEqual(activeAutomata, foundMapping.automata) && - _.isEqual(controlledParameters, foundMapping.controlledParameters) + isEqual(activeRules.map(ignoreInternalProperties), foundMapping.rules.map(ignoreInternalProperties)) && + isEqual(activeAutomata, foundMapping.automata) && + isEqual(controlledParameters, foundMapping.controlledParameters) ); } ); @@ -433,7 +432,7 @@ export const postMapping = createAsyncThunk('mappings/post', async (name, { getS : state?.mappings.rules; const augmentedRules = rules.map((rule) => { - let augmentedRule = _.cloneDeep(rule); + let augmentedRule = cloneDeep(rule); augmentedRule.equipmentType = rule.type.toUpperCase(); if (augmentedRule.filter) { @@ -469,27 +468,33 @@ export const postMapping = createAsyncThunk('mappings/post', async (name, { getS ? state?.mappings.mappings.find((mapping) => mapping.name === name)?.controlledParameters : state?.mappings.controlledParameters; - return await mappingsAPI.postMapping(mappingName, augmentedRules, formattedAutomata, controlledParameters, token); + return await dynamicMappingSrv.postMapping( + mappingName, + augmentedRules, + formattedAutomata, + controlledParameters, + token + ); }); export const getMappings = createAsyncThunk('mappings/get', async (_arg, { getState }) => { const token = getState()?.user.user?.id_token; - return await mappingsAPI.getMappings(token); + return await dynamicMappingSrv.getMappings(token); }); export const deleteMapping = createAsyncThunk('mappings/delete', async (mappingName, { getState }) => { const token = getState()?.user.user?.id_token; - return await mappingsAPI.deleteMapping(mappingName, token); + return await dynamicMappingSrv.deleteMapping(mappingName, token); }); export const renameMapping = createAsyncThunk('mappings/rename', async ({ nameToReplace, newName }, { getState }) => { const token = getState()?.user.user?.id_token; - return await mappingsAPI.renameMapping(nameToReplace, newName, token); + return await dynamicMappingSrv.renameMapping(nameToReplace, newName, token); }); export const copyMapping = createAsyncThunk('mappings/copy', async ({ originalName, copyName }, { getState }) => { const token = getState()?.user.user?.id_token; - return await mappingsAPI.copyMapping(originalName, copyName, token); + return await dynamicMappingSrv.copyMapping(originalName, copyName, token); }); export const getNetworkMatchesFromRule = createAsyncThunk('mappings/matchNetwork', async (ruleIndex, { getState }) => { @@ -503,7 +508,7 @@ export const getNetworkMatchesFromRule = createAsyncThunk('mappings/matchNetwork equipmentType: foundRule.type, filter: augmentFilter(foundRule.filter, foundRule.type), }; - return await networkAPI.getNetworkMatchesFromRule(networkId, ruleToMatch, token); + return await dynamicMappingSrv.getNetworkMatchesFromRule(networkId, ruleToMatch, token); }); // daisy-chain action creators @@ -556,7 +561,7 @@ const reducers = { }, // Rule addRule: (state) => { - const newRule = _.cloneDeep(DEFAULT_RULE); + const newRule = cloneDeep(DEFAULT_RULE); // provide an id for new rule newRule.id = uuid4(); newRule.type = state.filteredRuleType; @@ -580,7 +585,7 @@ const reducers = { }, copyRule: (state, action) => { const { index } = action.payload; - const ruleToCopy = _.cloneDeep(filterRulesByType(state.rules, state.filteredRuleType)[index]); + const ruleToCopy = cloneDeep(filterRulesByType(state.rules, state.filteredRuleType)[index]); // force reset rule with new id ruleToCopy.id = uuid4(); @@ -634,7 +639,7 @@ const reducers = { }, // Automaton addAutomaton: (state) => { - const newAutomaton = _.cloneDeep(DEFAULT_AUTOMATON); + const newAutomaton = cloneDeep(DEFAULT_AUTOMATON); newAutomaton.family = state.filteredAutomatonFamily; state.automata.push(newAutomaton); }, @@ -677,9 +682,7 @@ const reducers = { }, copyAutomaton: (state, action) => { const { index } = action.payload; - const automatonToCopy = _.cloneDeep( - filterAutomataByFamily(state.automata, state.filteredAutomatonFamily)[index] - ); + const automatonToCopy = cloneDeep(filterAutomataByFamily(state.automata, state.filteredAutomatonFamily)[index]); state.automata.push(automatonToCopy); }, // Mappings diff --git a/src/redux/slices/Model.js b/src/redux/slices/Model.js index 037520e..c50fac7 100644 --- a/src/redux/slices/Model.js +++ b/src/redux/slices/Model.js @@ -6,10 +6,10 @@ */ import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit'; -import * as modelsAPI from '../../rest/modelsAPI'; import RequestStatus from '../../constants/RequestStatus'; -import * as _ from 'lodash'; +import { cloneDeep, findIndex, forEach, uniqBy } from 'lodash'; import { SetType } from '../../constants/models'; +import { dynamicMappingSrv } from '../../services'; const DEFAULT_GROUP = { name: '', @@ -65,12 +65,12 @@ export const makeGetSearchSets = () => export const getModels = createAsyncThunk('models/get', async (_arg, { getState }) => { const token = getState()?.user.user?.id_token; - return await modelsAPI.getModels(token); + return await dynamicMappingSrv.getModels(token); }); export const getModelDefinitions = createAsyncThunk('models/definitions', async (modelName, { getState }) => { const token = getState()?.user.user?.id_token; - return await modelsAPI.getModelDefinitions(modelName, token); + return await dynamicMappingSrv.getModelDefinitions(modelName, token); }); export const getModelSets = createAsyncThunk( @@ -78,7 +78,7 @@ export const getModelSets = createAsyncThunk( async ({ modelName, groupName, groupType }, { getState }) => { if (groupName) { const token = getState()?.user.user?.id_token; - return await modelsAPI.getModelSets( + return await dynamicMappingSrv.getModelSets( modelName, groupName, groupType !== '' ? groupType : SetType.FIXED, @@ -95,7 +95,7 @@ export const getSearchedModelSets = createAsyncThunk( async ({ modelName, groupName, groupType }, { getState }) => { if (groupName) { const token = getState()?.user.user?.id_token; - return await modelsAPI.getModelSets(modelName, groupName, groupType ?? '', token); + return await dynamicMappingSrv.getModelSets(modelName, groupName, groupType ?? '', token); } else { return []; } @@ -105,14 +105,14 @@ export const getSearchedModelSets = createAsyncThunk( export const postModelSetsGroup = createAsyncThunk('models/post', async (strict, { getState }) => { const token = getState()?.user.user?.id_token; const setGroup = getState()?.models.currentGroup; - return await modelsAPI.postModelSetsGroup(setGroup, strict, token); + return await dynamicMappingSrv.postModelSetsGroup(setGroup, strict, token); }); export const getAutomatonDefinitions = createAsyncThunk( 'models/automaton/get/definitions', async (_arg, { getState }) => { const token = getState()?.user.user?.id_token; - return await modelsAPI.getAutomatonDefinitions(token); + return await dynamicMappingSrv.getAutomatonDefinitions(token); } ); @@ -130,7 +130,7 @@ const reducers = { }, changeGroup: (state, action) => { const { group, originalGroup, modelName, isAbsolute, matches } = action.payload; - const currentGroup = _.cloneDeep(group); + const currentGroup = cloneDeep(group); const definitions = state.parameterDefinitions; currentGroup.modelName = modelName; if (originalGroup) { @@ -148,7 +148,7 @@ const reducers = { const newSets = matches .filter( (match) => - _.findIndex( + findIndex( currentGroup.sets, (set) => set.name === matchingSetName(match, currentGroup.type) ) === -1 @@ -189,7 +189,7 @@ const reducers = { addOrModifySet: (state, action) => { const newSets = action.payload; - _.forEach(Array.isArray(newSets) ? newSets : [newSets], (newSet) => { + forEach(Array.isArray(newSets) ? newSets : [newSets], (newSet) => { const setIndex = state.currentGroup.sets.findIndex((setToTest) => setToTest.name === newSet.name); if (setIndex === -1) { state.currentGroup.sets.push(newSet); @@ -215,13 +215,13 @@ const extraReducers = (builder) => { builder.addCase(getModelSets.fulfilled, (state, action) => { const receivedSets = action.payload; - state.currentGroup.sets = _.uniqBy(receivedSets.concat(state.currentGroup.sets), 'name'); + state.currentGroup.sets = uniqBy(receivedSets.concat(state.currentGroup.sets), 'name'); state.status = RequestStatus.SUCCESS; }); builder.addCase(getSearchedModelSets.fulfilled, (state, action) => { const candidateSets = action.payload; - state.currentGroup.searchSets = _.uniqBy(candidateSets, 'name'); + state.currentGroup.searchSets = uniqBy(candidateSets, 'name'); state.status = RequestStatus.SUCCESS; }); builder.addCase(postModelSetsGroup.fulfilled, (state, action) => { diff --git a/src/redux/slices/Network.js b/src/redux/slices/Network.js index f1e7081..a191fa2 100644 --- a/src/redux/slices/Network.js +++ b/src/redux/slices/Network.js @@ -7,9 +7,9 @@ import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit'; import RequestStatus from '../../constants/RequestStatus'; -import * as networkAPI from '../../rest/networkAPI'; import { createParameterSelector } from '../selectorUtil'; import { PropertyType } from '../../constants/equipmentType'; +import { dynamicMappingSrv } from '../../services'; const initialState = { propertyValues: [], @@ -52,20 +52,20 @@ export const getCurrentNetworkObj = createSelector( export const getPropertyValuesFromFile = createAsyncThunk('network/getValuesFromFile', async (file, { getState }) => { const token = getState()?.user.user?.id_token; - return await networkAPI.getPropertyValuesFromFile(file, token); + return await dynamicMappingSrv.getPropertyValuesFromFile(file, token); }); export const getPropertyValuesFromNetworkId = createAsyncThunk( 'network/getValuesFromId', async (networkId, { getState }) => { const token = getState()?.user.user?.id_token; - return await networkAPI.getPropertyValuesFromId(networkId, token); + return await dynamicMappingSrv.getPropertyValuesFromId(networkId, token); } ); export const getNetworkNames = createAsyncThunk('network/getNetworks', async (_args, { getState }) => { const token = getState()?.user.user?.id_token; - return await networkAPI.getNetworksName(token); + return await dynamicMappingSrv.getNetworksName(token); }); const reducers = { diff --git a/src/redux/slices/Theme.js b/src/redux/slices/Theme.js deleted file mode 100644 index dea1976..0000000 --- a/src/redux/slices/Theme.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2021, 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 { createSlice } from '@reduxjs/toolkit'; -import { getLocalStorageTheme, saveLocalStorageTheme } from '../local-storage'; - -export const SELECT_THEME = 'SELECT_THEME'; -export const DARK_THEME = 'Dark'; -export const LIGHT_THEME = 'Light'; - -export function selectTheme(theme) { - return { type: SELECT_THEME, theme: theme }; -} - -const initialState = getLocalStorageTheme(); - -// Selectors - -// Reducers - -const reducers = { - selectTheme: (state, action) => { - state = action.payload; - saveLocalStorageTheme(state.theme); - }, -}; - -export const ThemeSlice = createSlice({ - name: 'Theme', - initialState, - reducers, -}); - -export const ThemeReducer = ThemeSlice.reducer; diff --git a/src/redux/slices/Theme.ts b/src/redux/slices/Theme.ts new file mode 100644 index 0000000..1c3f3f0 --- /dev/null +++ b/src/redux/slices/Theme.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021, 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 { Action, createSlice, PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { getLocalStorageTheme, GsTheme, saveLocalStorageTheme } from '@gridsuite/commons-ui'; + +export const SELECT_THEME = 'SELECT_THEME'; +type SelectThemeAction = Action & { + theme: GsTheme; +}; +export function selectTheme(theme: GsTheme) { + return { type: SELECT_THEME, theme: theme }; +} + +export type ThemeState = { + theme: GsTheme; +}; + +const initialState: ThemeState = { + theme: getLocalStorageTheme(process.env.REACT_APP_NAME!), +}; + +// Selectors + +// Reducers + +const reducers: SliceCaseReducers = { + selectTheme: (state, action: PayloadAction) => { + state = action.payload; + saveLocalStorageTheme(process.env.REACT_APP_NAME!, state.theme); + }, +}; + +export const ThemeSlice = createSlice({ + name: 'Theme', + initialState, + reducers, +}); + +export const ThemeReducer = ThemeSlice.reducer; diff --git a/src/redux/slices/User.ts b/src/redux/slices/User.ts index 9f14e93..32314ee 100644 --- a/src/redux/slices/User.ts +++ b/src/redux/slices/User.ts @@ -6,9 +6,10 @@ */ import { createSlice, PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import { User } from 'oidc-client'; import { + AuthenticationRouterErrorAction, AuthenticationRouterErrorState, - CommonStoreState, LOGOUT_ERROR, LogoutErrorAction, RESET_AUTHENTICATION_ROUTER_ERROR, @@ -23,16 +24,16 @@ import { UserAction, UserValidationErrorAction, } from '@gridsuite/commons-ui'; -import { AuthenticationRouterErrorAction } from '@gridsuite/commons-ui/dist/redux/authActions'; -export type UserState = CommonStoreState & { +export type UserState = { + user: User | undefined; signInCallbackError: Error | null; authenticationRouterError: AuthenticationRouterErrorState | null; showAuthenticationRouterLogin: boolean; }; const initialState: UserState = { - user: null, + user: undefined, signInCallbackError: null, authenticationRouterError: null, showAuthenticationRouterLogin: false, diff --git a/src/redux/store.ts b/src/redux/store.ts index 83f2453..301cd34 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -5,11 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { configureStore } from '@reduxjs/toolkit'; -import { setCommonStore } from '@gridsuite/commons-ui'; +import { initCommonServices } from '@gridsuite/commons-ui'; import { reducer } from './reducer'; export const store = configureStore({ reducer }); export type AppDispatch = typeof store.dispatch; -setCommonStore({ - getState: () => store.getState().user, -}); + +export function getUser() { + return store.getState().user?.user; +} diff --git a/src/rest/mappingsAPI.js b/src/rest/mappingsAPI.js deleted file mode 100644 index 9773417..0000000 --- a/src/rest/mappingsAPI.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2021, 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, backendFetchText } from '../utils/rest-api'; - -const API_URL = - process.env.REACT_APP_API_PREFIX + - (process.env.REACT_APP_USE_AUTHENTICATION === 'true' - ? process.env.REACT_APP_GATEWAY_PREFIX + '/dynamic-mapping' - : process.env.REACT_APP_URI) + - '/mappings'; - -export function postMapping(mappingName, rules, automata, controlledParameters, token) { - return backendFetchJson( - `${API_URL}/${mappingName}`, - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - body: JSON.stringify({ - name: mappingName, - rules, - automata, - controlledParameters, - }), - }, - token - ); -} - -export function getMappings(token) { - return backendFetchJson( - `${API_URL}/`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function deleteMapping(mappingName, token) { - return backendFetchText( - `${API_URL}/${mappingName}`, - { - method: 'DELETE', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export async function renameMapping(nameToReplace, newName, token) { - return backendFetchJson( - `${API_URL}/rename/${nameToReplace}/to/${newName}`, - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export async function copyMapping(originalName, copyName, token) { - return backendFetchJson( - `${API_URL}/copy/${originalName}/to/${copyName}`, - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} diff --git a/src/rest/modelsAPI.js b/src/rest/modelsAPI.js deleted file mode 100644 index 0a60401..0000000 --- a/src/rest/modelsAPI.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2021, 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 } from '../utils/rest-api'; - -const API_URL = - process.env.REACT_APP_API_PREFIX + - (process.env.REACT_APP_USE_AUTHENTICATION === 'true' - ? process.env.REACT_APP_GATEWAY_PREFIX + '/dynamic-mapping' - : process.env.REACT_APP_URI) + - '/models'; - -export function getModels(token) { - return backendFetchJson( - `${API_URL}/`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function getModelDefinitions(modelName, token) { - return backendFetchJson( - `${API_URL}/${modelName}/parameters/definitions`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function getModelSets(modelName, groupName, groupType, token) { - return backendFetchJson( - `${API_URL}/${modelName}/parameters/sets/${groupName}/${groupType}`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function postModelSetsGroup(setGroup, strict, token) { - return backendFetchJson( - `${API_URL}/${setGroup.modelName}/parameters/sets${strict ? '/strict' : ''}`, - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - body: JSON.stringify(setGroup), - }, - token - ); -} - -export function getAutomatonDefinitions(token) { - return backendFetchJson( - `${API_URL}/automaton-definitions`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} diff --git a/src/rest/networkAPI.js b/src/rest/networkAPI.js deleted file mode 100644 index 2894f04..0000000 --- a/src/rest/networkAPI.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2021, 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 } from '../utils/rest-api'; - -const API_URL = - process.env.REACT_APP_API_PREFIX + - (process.env.REACT_APP_USE_AUTHENTICATION === 'true' - ? process.env.REACT_APP_GATEWAY_PREFIX + '/dynamic-mapping' - : process.env.REACT_APP_URI) + - '/network'; - -export function getPropertyValuesFromFile(networkFile, token) { - const formData = new FormData(); - formData.append('file', networkFile); - - return backendFetchJson( - `${API_URL}/new`, - { - method: 'POST', - headers: { - Accept: 'application/json', - }, - cache: 'default', - body: formData, - }, - token - ); -} - -export function getPropertyValuesFromId(networkId, token) { - return backendFetchJson( - `${API_URL}/${networkId}/values`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function getNetworksName(token) { - return backendFetchJson( - `${API_URL}/`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ); -} - -export function getNetworkMatchesFromRule(networkId, ruleToMatch, token) { - return backendFetchJson( - `${API_URL}/${networkId}/matches/rule`, - { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - body: JSON.stringify(ruleToMatch), - }, - token - ); -} diff --git a/src/rest/studyAPI.js b/src/rest/studyAPI.js deleted file mode 100644 index 9d62af3..0000000 --- a/src/rest/studyAPI.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * 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/. - */ -import { backendFetchJson } from '../utils/rest-api'; - -const API_URL = - process.env.REACT_APP_API_PREFIX + - (process.env.REACT_APP_USE_AUTHENTICATION === 'true' - ? process.env.REACT_APP_GATEWAY_PREFIX + '/study/v1' - : process.env.REACT_APP_STUDY_URI + '/v1'); - -export function getServersInfos(token) { - return backendFetchJson( - `${API_URL}/servers/about?view=dyna`, - { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }, - token - ).catch((reason) => { - console.error('Error while fetching the servers infos : ' + reason); - return reason; - }); -} diff --git a/src/services/app-local.ts b/src/services/app-local.ts new file mode 100644 index 0000000..d983687 --- /dev/null +++ b/src/services/app-local.ts @@ -0,0 +1,20 @@ +/* + * Copyright © 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 { AppLocalComSvc, Env } from '@gridsuite/commons-ui'; + +export type EnvJson = Env & typeof import('../../public/env.json'); + +export default class AppLocalSvc extends AppLocalComSvc { + public constructor() { + super(); + } + + public async fetchEnv() { + return (await super.fetchEnv()) as EnvJson; + } +} diff --git a/src/services/dynamic-mapping.ts b/src/services/dynamic-mapping.ts new file mode 100644 index 0000000..1e27101 --- /dev/null +++ b/src/services/dynamic-mapping.ts @@ -0,0 +1,100 @@ +/* + * Copyright © 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 { ApiService } from '@gridsuite/commons-ui'; +import { getUser } from '../redux/store'; +import { UUID } from 'crypto'; + +export default class DynamicMappingSvc extends ApiService { + public constructor() { + super( + getUser, + process.env.REACT_APP_DYNAMAP_SVC, + // If you want to use user-admin-server in dev mode you must avoid passing through gateway + // and use the user-admin-server directly. SetupProxy should allow this. + // @ts-expect-error url type incompatibility + process.env.REACT_APP_API_PREFIX + ); + } + + public async postMapping(mappingName: string, rules: unknown, automata: unknown, controlledParameters: boolean) { + return this.backendSendFetchJson( + `${this.getPrefix(1)}/mappings/${mappingName}`, + 'POST', + JSON.stringify({ + name: mappingName, + rules, + automata, + controlledParameters, + }) + ); + } + + public async getMappings() { + return this.backendFetchJson(`${this.getPrefix(1)}/mappings/`); + } + + public async deleteMapping(mappingName: string) { + return this.backendFetchText(`${this.getPrefix(1)}/mappings/${mappingName}`, 'DELETE'); + } + + public async renameMapping(nameToReplace: string, newName: string) { + return this.backendFetchJson(`${this.getPrefix(1)}/mappings/rename/${nameToReplace}/to/${newName}`, 'POST'); + } + + public async copyMapping(originalName: string, copyName: string) { + return await this.backendFetchJson(`${this.getPrefix(1)}/mappings/copy/${originalName}/to/${copyName}`, 'POST'); + } + + public async getPropertyValuesFromFile(networkFile: string | Blob) { + const formData = new FormData(); + formData.append('file', networkFile); + return this.backendSendFetchJson(`${this.getPrefix(1)}/network/new`, 'POST', formData); + } + + public async getPropertyValuesFromId(networkId: UUID) { + return this.backendFetchJson(`${this.getPrefix(1)}/network/${networkId}/values`); + } + + public async getNetworksName() { + return this.backendFetchJson(`${this.getPrefix(1)}/network/`); + } + + public async getNetworkMatchesFromRule(networkId: UUID, ruleToMatch: unknown) { + return this.backendSendFetchJson( + `${this.getPrefix(1)}/network/${networkId}/matches/rule`, + 'POST', + JSON.stringify(ruleToMatch) + ); + } + + public async getModels() { + return this.backendFetchJson(`${this.getPrefix(1)}/models/`); + } + + public async getModelDefinitions(modelName: string) { + return this.backendFetchJson(`${this.getPrefix(1)}/models/${modelName}/parameters/definitions`); + } + + public async getModelSets(modelName: string, groupName: string, groupType: unknown) { + return this.backendFetchJson( + `${this.getPrefix(1)}/models/${modelName}/parameters/sets/${groupName}/${groupType}` + ); + } + + public async postModelSetsGroup(setGroup: any, strict: boolean) { + return this.backendSendFetchJson( + `${this.getPrefix(1)}/models/${setGroup.modelName}/parameters/sets${strict ? '/strict' : ''}`, + 'POST', + JSON.stringify(setGroup) + ); + } + + public async getAutomatonDefinitions() { + return this.backendFetchJson(`${this.getPrefix(1)}/models/automaton-definitions`); + } +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..bf79cda --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright © 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 { getUser } from '../redux/store'; +import { + AppsMetadataComSvc, + ConfigComSvc, + ConfigNotificationComSvc, + DirectoryComSvc, + ExploreComSvc, + setCommonServices, + StudyComSvc, + UserAdminComSvc, +} from '@gridsuite/commons-ui'; +import AppLocalSvc from './app-local'; +import DynamicMappingSvc from './dynamic-mapping'; + +export type { EnvJson } from './app-local'; + +// If you want to use user-admin-server in dev mode you must avoid passing through gateway +// and use the user-admin-server directly. SetupProxy should allow this. + +export const appLocalSrv = new AppLocalSvc(), + appsMetadataSrv = new AppsMetadataComSvc(appLocalSrv), + configSrv = new ConfigComSvc( + process.env.REACT_APP_NAME!, + getUser, + // @ts-expect-error url type incompatibility + process.env.REACT_APP_API_GATEWAY + ), + configNotificationSrv = new ConfigNotificationComSvc( + getUser, + // @ts-expect-error url type incompatibility + process.env.REACT_APP_WS_GATEWAY + ), + directorySrv = new DirectoryComSvc( + getUser, + // @ts-expect-error url type incompatibility + process.env.REACT_APP_API_GATEWAY + ), + // @ts-expect-error url type incompatibility + exploreSrv = new ExploreComSvc(getUser, process.env.REACT_APP_API_GATEWAY), + // @ts-expect-error url type incompatibility + studySrv = new StudyComSvc(getUser, process.env.REACT_APP_API_PREFIX), + userAdminSrv = new UserAdminComSvc( + getUser, + // @ts-expect-error url type incompatibility + process.env.REACT_APP_API_GATEWAY + ), + dynamicMappingSrv = new DynamicMappingSvc(); + +setCommonServices( + appLocalSrv, + appsMetadataSrv, + configSrv, + configNotificationSrv, + directorySrv, + exploreSrv, + studySrv, + userAdminSrv +); diff --git a/src/setupProxy.js b/src/setupProxy.js index c757b6b..30d0f94 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -24,8 +24,8 @@ module.exports = function (app) { }) ); app.use( - createProxyMiddleware('http://localhost:5001/api/study-server', { - pathRewrite: { '^/api/study-server/': '/' }, + createProxyMiddleware('http://localhost:5001/api/study', { + pathRewrite: { '^/api/study/': '/' }, }) ); }; diff --git a/src/utils/composition.js b/src/utils/composition.js deleted file mode 100644 index 34e0afa..0000000 --- a/src/utils/composition.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2021, 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 function convertCompositionStringToArray(compositionString) { - // Transform parentheses into arrays - const step1 = compositionString - .split(/([()])/) - .map((x) => x.trim()) - .join(' ') - .replace(/\)\s\)/g, '))') - .replace(/\(/g, '[') - .replace(/\)\s/g, '], ') - .replace(/\)/g, ']'); - - // Wrap whole string in an array - const step2 = '[' + step1 + ']'; - - // Replace whitespace separators with commas and escape values - const step3 = step2 - .replace(/[^[\],\s]+/g, '"$&"') - .replace(/" /g, '", ') - .replace(/,[\s]+]/g, ']'); - - // Parse as a JSON array - const compositionArray = JSON.parse(step3); - - // Wrap Single filters in a group - return compositionArray.map((element) => { - if (!Array.isArray(element) && element !== '||' && element !== '&&') { - return [element]; - } - return element; - }); -} - -export function convertCompositionArrayToString(compositionArray) { - const compositionStringArray = compositionArray.map((element) => { - if (Array.isArray(element)) { - if (element.length === 1) { - return element[0]; - } else { - return `(${convertCompositionArrayToString(element)})`; - } - } - return element; - }); - return compositionStringArray.join(' '); -} - -export const checkCompositionArrayValidity = (compositionArray, isInner = false) => { - let arrayOperation; - return compositionArray - .map((compositionElement, index) => { - if (index % 2 === 0) { - if (Array.isArray(compositionElement)) { - return checkCompositionArrayValidity(compositionElement, true); - } - return /filter\d+\b/.test(compositionElement); - } - if (index === 1) { - arrayOperation = compositionElement; - } - return /(&&|\|\|)/.test(compositionElement) && (!isInner || compositionElement === arrayOperation); - }) - .reduce((acc, element) => acc && element); -}; - -export function getMaxDepthParentheses(logicString) { - let count = 0; - let maxCount = 0; - Array.from(logicString).forEach((char) => { - if (char === '(') { - count++; - } else if (char === ')') { - count--; - } - if (count > maxCount) { - maxCount = count; - } - }); - return maxCount; -} diff --git a/src/utils/functions.js b/src/utils/functions.ts similarity index 67% rename from src/utils/functions.js rename to src/utils/functions.ts index 5b6a4a7..90b401f 100644 --- a/src/utils/functions.js +++ b/src/utils/functions.ts @@ -5,13 +5,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import _ from 'lodash'; +import { pick } from 'lodash'; -export const mergeSx = (...allSx) => allSx.flat(); +type GetMatcher = (target: T) => Parameters['find']>[0]; +type PickProps = Array; /** * Copy properties from corresponding objects in a source array to objects in a target array. - * It returns the target array that contain modified objects + * It returns the target array that contains modified objects * * @param targetArray the target array to copy to inside objects * @param sourceArray the source array from which to copy properties of objects @@ -19,13 +20,18 @@ export const mergeSx = (...allSx) => allSx.flat(); * @param props properties names to copy, specified individually or in array * @return the target array */ -export const assignArray = (targetArray, sourceArray, matcher, ...props) => { +export function assignArray( + targetArray: T[], + sourceArray: T[], + matcher: GetMatcher, + ...props: PickProps +) { targetArray?.forEach((targetObj) => { const matcherOfTarget = matcher(targetObj); const sourceObj = sourceArray?.find(matcherOfTarget); - const pickObj = _.pick(sourceObj, props); + const pickObj = pick(sourceObj, props); Object.assign(targetObj, pickObj); }); return targetArray; -}; +} diff --git a/src/utils/properties.js b/src/utils/properties.js deleted file mode 100644 index 0f9a140..0000000 --- a/src/utils/properties.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2021, 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 { EquipmentProperties } from '../constants/equipmentDefinition'; - -export function getProperty(equipmentType, propertyName) { - return EquipmentProperties[equipmentType].find((property) => property.name === propertyName); -} - -export function getValuesOption(property) { - //TODO Intl - return property?.values?.map((value) => ({ - label: value, - value, - })); -} diff --git a/src/utils/rest-api.js b/src/utils/rest-api.js deleted file mode 100644 index 1c367dd..0000000 --- a/src/utils/rest-api.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2020, 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 { store } from '../redux/store'; -const PREFIX_USER_ADMIN_SERVER_QUERIES = - process.env.REACT_APP_API_PREFIX + process.env.REACT_APP_GATEWAY_PREFIX + '/user-admin'; - -// If you want to use user-admin-server in dev mode you must avoid passing through gateway -// and use the user-admin-server directly. SetupProxy should allow this. -// const PREFIX_USER_ADMIN_SERVER_QUERIES = -// process.env.REACT_APP_API_PREFIX + -// (process.env.REACT_APP_USE_AUTHENTICATION === 'true' -// ? process.env.REACT_APP_API_GATEWAY + '/user-admin' -// : process.env.REACT_APP_USER_ADMIN_URI); - -function getToken() { - const state = store.getState(); - return state.user.id_token; -} - -function parseError(text) { - try { - return JSON.parse(text); - } catch (err) { - return null; - } -} - -function handleError(response) { - return response.text().then((text) => { - const errorName = 'HttpResponseError : '; - let error; - const errorJson = parseError(text); - if (errorJson && errorJson.status && errorJson.error && errorJson.message) { - error = new Error( - errorName + errorJson.status + ' ' + errorJson.error + ', message : ' + errorJson.message - ); - error.status = errorJson.status; - } else { - error = new Error(errorName + response.status + ' ' + response.statusText); - error.status = response.status; - } - throw error; - }); -} - -function prepareRequest(init, token) { - if (!(typeof init == 'undefined' || typeof init == 'object')) { - throw new TypeError('Argument 2 of backendFetch is not an object' + typeof init); - } - const initCopy = Object.assign({}, init); - initCopy.headers = new Headers(initCopy.headers || {}); - const tokenCopy = token ? token : getToken(); - initCopy.headers.append('Authorization', 'Bearer ' + tokenCopy); - return initCopy; -} - -function safeFetch(url, initCopy) { - return fetch(url, initCopy).then((response) => (response.ok ? response : handleError(response))); -} - -export function backendFetch(url, init, token) { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy); -} - -export function backendFetchText(url, init, token) { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy).then((safeResponse) => safeResponse.text()); -} - -export function backendFetchJson(url, init, token) { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy).then((safeResponse) => safeResponse.json()); -} - -export function fetchValidateUser(user) { - const sub = user?.profile?.sub; - if (!sub) { - return Promise.reject(new Error('Error : Fetching access for missing user.profile.sub : ' + user)); - } - - console.info(`Fetching access for user...`); - const CheckAccessUrl = PREFIX_USER_ADMIN_SERVER_QUERIES + `/v1/users/${sub}`; - console.debug(CheckAccessUrl); - - return backendFetch( - CheckAccessUrl, - { - method: 'head', - }, - user?.id_token - ) - .then((response) => { - //if the response is ok, the responseCode will be either 200 or 204 otherwise it's a Http error and it will be caught - return response.status === 200; - }) - .catch((error) => { - if (error.status === 403) { - return false; - } else { - throw error; - } - }); -} - -function fetchEnv() { - return fetch('env.json').then((res) => res.json()); -} - -export function fetchIdpSettings() { - return fetch('idpSettings.json').then((res) => res.json()); -} - -export function fetchAppsAndUrls() { - console.info(`Fetching apps and urls...`); - return fetchEnv() - .then((env) => fetch(env.appsMetadataServerUrl + '/apps-metadata.json')) - .then((response) => response.json()); -} - -export function fetchVersion() { - console.info(`Fetching global metadata...`); - return fetchEnv() - .then((env) => fetch(env.appsMetadataServerUrl + '/version.json')) - .then((response) => response.json()) - .catch((reason) => { - console.error('Error while fetching the version : ' + reason); - return reason; - }); -}