From e594765b3687495d487677e94ab7dfd6b01b1a57 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 11 Jul 2024 17:21:59 +0200 Subject: [PATCH 01/19] rebase: Add common API utility functions --- package-lock.json | 9 +- package.json | 8 +- src/index.ts | 2 + src/redux/commonStore.ts | 4 +- src/services/apps-metadata.ts | 5 +- src/services/directory.ts | 30 +++--- src/services/explore.ts | 14 +-- src/services/index.ts | 1 - src/services/study.ts | 7 +- src/services/utils.ts | 83 --------------- src/utils/api/api-rest.ts | 180 +++++++++++++++++++++++++++++++++ src/utils/api/api-websocket.ts | 22 ++++ src/utils/api/index.ts | 10 ++ src/utils/api/utils.ts | 39 +++++++ src/utils/error.ts | 32 ++++++ src/utils/types.ts | 10 ++ 16 files changed, 330 insertions(+), 126 deletions(-) delete mode 100644 src/services/utils.ts create mode 100644 src/utils/api/api-rest.ts create mode 100644 src/utils/api/api-websocket.ts create mode 100644 src/utils/api/index.ts create mode 100644 src/utils/api/utils.ts create mode 100644 src/utils/error.ts diff --git a/package-lock.json b/package-lock.json index 43b154c6..75ee2ba9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,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" }, "devDependencies": { @@ -88,7 +89,6 @@ "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", - "type-fest": "^4.14.0", "typescript": "5.1.6", "utf-8-validate": "^6.0.3", "vite": "^5.2.7", @@ -15129,10 +15129,9 @@ } }, "node_modules/type-fest": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz", - "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==", - "dev": true, + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", + "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index a983c11f..062c2bca 100644 --- a/package.json +++ b/package.json @@ -42,21 +42,22 @@ "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" }, "peerDependencies": { - "@mui/system": "^5.15.15", - "@mui/x-tree-view": "^6.17.0", - "papaparse": "^5.4.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", + "@mui/system": "^5.15.15", + "@mui/x-tree-view": "^6.17.0", "ag-grid-community": "^31.0.0", "ag-grid-react": "^31.2.0", "notistack": "^3.0.1", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.2", @@ -128,7 +129,6 @@ "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", - "type-fest": "^4.14.0", "typescript": "5.1.6", "utf-8-validate": "^6.0.3", "vite": "^5.2.7", diff --git a/src/index.ts b/src/index.ts index 48362118..e8dd345d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -248,4 +248,6 @@ export { setCommonStore } from './redux/commonStore'; export type { CommonStoreState } from './redux/commonStore'; export type { EquipmentInfos } from './utils/EquipmentType'; +export { getErrorMessage } from './utils/error'; +export * from './utils/api'; export * from './services'; diff --git a/src/redux/commonStore.ts b/src/redux/commonStore.ts index d9a53c09..907f001e 100644 --- a/src/redux/commonStore.ts +++ b/src/redux/commonStore.ts @@ -26,6 +26,6 @@ export function setCommonStore(store: CommonStore): void { commonStore = store; } -export function getUserToken() { - return commonStore?.getState().user?.id_token; +export function getUser() { + return commonStore?.getState().user ?? undefined; } diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index f14cb3de..fe561e02 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -5,10 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { PredefinedProperties } from '../utils/types'; - -// https://github.com/gridsuite/deployment/blob/main/docker-compose/docker-compose.base.yml -// https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/apps-metadata.json -export type Url = string | URL; +import { Url } from '../utils/api'; export type Env = { appsMetadataServerUrl?: Url; diff --git a/src/services/directory.ts b/src/services/directory.ts index 22b88c63..c2717906 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -6,16 +6,17 @@ */ import { UUID } from 'crypto'; -import { backendFetchJson, getRequestParamFromList } from './utils'; +import { + backendFetchJson, + getRequestParam as getRequestParamFromList, +} from '../utils/api'; import { ElementAttributes } from '../utils/types'; const PREFIX_DIRECTORY_SERVER_QUERIES = `${ import.meta.env.VITE_API_GATEWAY }/directory`; -export function fetchRootFolders( - types: string[] -): Promise { +export function fetchRootFolders(types: string[]) { console.info('Fetching Root Directories'); // Add params to Url @@ -25,15 +26,12 @@ export function fetchRootFolders( ).toString(); const fetchRootFoldersUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/root-directories?${urlSearchParams}`; return backendFetchJson(fetchRootFoldersUrl, { - method: 'get', + method: 'GET', headers: { 'Content-Type': 'application/json' }, - }); + }) as Promise; } -export function fetchDirectoryContent( - directoryUuid: UUID, - types?: string[] -): Promise { +export function fetchDirectoryContent(directoryUuid: UUID, types?: string[]) { console.info("Fetching Folder content '%s'", directoryUuid); // Add params to Url @@ -46,21 +44,19 @@ export function fetchDirectoryContent( urlSearchParams ? `?${urlSearchParams}` : '' }`; return backendFetchJson(fetchDirectoryContentUrl, { - method: 'get', + method: 'GET', headers: { 'Content-Type': 'application/json' }, - }); + }) as Promise; } -export function fetchDirectoryElementPath( - elementUuid: UUID -): Promise { +export function fetchDirectoryElementPath(elementUuid: UUID) { console.info(`Fetching element '${elementUuid}' and its parents info ...`); const fetchPathUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/elements/${encodeURIComponent( elementUuid )}/path`; console.debug(fetchPathUrl); return backendFetchJson(fetchPathUrl, { - method: 'get', + method: 'GET', headers: { 'Content-Type': 'application/json' }, - }); + }) as Promise; } diff --git a/src/services/explore.ts b/src/services/explore.ts index 4e2fc6a8..bd6b404f 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -9,8 +9,8 @@ import { UUID } from 'crypto'; import { backendFetch, backendFetchJson, - getRequestParamFromList, -} from './utils'; + getRequestParam as getRequestParamFromList, +} from '../utils/api'; import { ElementAttributes } from '../utils/types'; const PREFIX_EXPLORE_SERVER_QUERIES = `${ @@ -33,7 +33,7 @@ export function createFilter( return backendFetch( `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters?${urlSearchParams.toString()}`, { - method: 'post', + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newFilter), }, @@ -49,7 +49,7 @@ export function saveFilter(filter: any, name: string, token?: string) { filter.id }?${urlSearchParams.toString()}`, { - method: 'put', + method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(filter), }, @@ -61,7 +61,7 @@ export function fetchElementsInfos( ids: UUID[], elementTypes?: string[], equipmentTypes?: string[] -): Promise { +) { console.info('Fetching elements metadata'); // Add params to Url @@ -89,7 +89,7 @@ export function fetchElementsInfos( const url = `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/elements/metadata?${urlSearchParams}`; console.debug(url); return backendFetchJson(url, { - method: 'get', + method: 'GET', headers: { 'Content-Type': 'application/json' }, - }); + }) as Promise; } diff --git a/src/services/index.ts b/src/services/index.ts index b9307db9..18fac583 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,7 +4,6 @@ * 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 * from './utils'; export * from './explore'; export * from './apps-metadata'; export * from './directory'; diff --git a/src/services/study.ts b/src/services/study.ts index b24371de..11dc15e2 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -5,12 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* eslint-disable import/prefer-default-export -- utility class */ + import { UUID } from 'crypto'; -import { backendFetchJson } from './utils'; +import { backendFetchJson } from '../utils/api'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; -// eslint-disable-next-line import/prefer-default-export export function exportFilter( studyUuid: UUID, filterUuid?: UUID, @@ -20,7 +21,7 @@ export function exportFilter( return backendFetchJson( `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, { - method: 'get', + method: 'GET', headers: { 'Content-Type': 'application/json' }, }, token diff --git a/src/services/utils.ts b/src/services/utils.ts deleted file mode 100644 index 408eb0d1..00000000 --- a/src/services/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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 { getUserToken } from '../redux/commonStore'; - -const parseError = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return null; - } -}; - -const prepareRequest = (init: any, token?: string) => { - if (!(typeof init === 'undefined' || typeof init === 'object')) { - throw new TypeError( - `First argument of prepareRequest is not an object : ${typeof init}` - ); - } - const initCopy = { ...init }; - initCopy.headers = new Headers(initCopy.headers || {}); - const tokenCopy = token ?? getUserToken(); - initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); - return initCopy; -}; - -const handleError = (response: any) => { - return response.text().then((text: string) => { - const errorName = 'HttpResponseError : '; - const errorJson = parseError(text); - let customError: Error & { status?: string }; - if ( - errorJson && - errorJson.status && - errorJson.error && - errorJson.message - ) { - customError = new Error( - `${errorName + errorJson.status} ${ - errorJson.error - }, message : ${errorJson.message}` - ); - customError.status = errorJson.status; - } else { - customError = new Error( - `${errorName + response.status} ${ - response.statusText - }, message : ${text}` - ); - customError.status = response.status; - } - throw customError; - }); -}; - -const safeFetch = (url: string, initCopy: any) => { - return fetch(url, initCopy).then((response) => - response.ok ? response : handleError(response) - ); -}; - -export const backendFetch = (url: string, init: any, token?: string) => { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy); -}; - -export const backendFetchJson = (url: string, init: any, token?: string) => { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy).then((safeResponse) => - safeResponse.status === 204 ? null : safeResponse.json() - ); -}; - -export const getRequestParamFromList = ( - paramName: string, - params: string[] = [] -) => { - return new URLSearchParams(params.map((param) => [paramName, param])); -}; diff --git a/src/utils/api/api-rest.ts b/src/utils/api/api-rest.ts new file mode 100644 index 00000000..8b9a98f1 --- /dev/null +++ b/src/utils/api/api-rest.ts @@ -0,0 +1,180 @@ +/** + * 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 { IncomingHttpHeaders } from 'node:http'; +import { LiteralUnion } from 'type-fest'; +import { FileType, getUserToken } from './utils'; +import { KeyOfWithoutIndexSignature } from '../types'; + +export type UrlString = `${string}://${string}` | `/${string}` | `./${string}`; +export type Url = + | (Check extends true ? UrlString : string) + | URL; +export type HttpMethod = + | 'GET' + | 'HEAD' + | 'POST' + | 'PUT' + | 'DELETE' + | 'CONNECT' + | 'OPTIONS' + | 'TRACE' + | 'PATCH'; +type StandardHeader = keyof KeyOfWithoutIndexSignature; +export type HttpHeaderName = LiteralUnion; + +export enum HttpContentType { + APPLICATION_OCTET_STREAM = 'application/octet-stream', + APPLICATION_JSON = 'application/json', + TEXT_PLAIN = 'text/plain', +} + +type RequestInitExt = Omit & { + method?: HttpMethod; +}; +export type InitRequest = HttpMethod | Partial; +export type Token = string; + +export interface ErrorWithStatus extends Error { + status?: number; +} + +export function getRestBase(): string { + // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx + return ( + document.baseURI.replace(/\/+$/, '') + import.meta.env.VITE_API_GATEWAY + ); +} + +function prepareRequest(init?: InitRequest, token?: Token): RequestInit { + if ( + !( + typeof init === 'undefined' || + typeof init === 'string' || + typeof init === 'object' + ) + ) { + throw new TypeError( + `First argument of prepareRequest is not an object: ${typeof init}` + ); + } + const initCopy: RequestInit = + typeof init === 'string' ? { method: init } : { ...(init ?? {}) }; + initCopy.headers = new Headers(initCopy.headers || {}); + const tokenCopy = token || getUserToken(); + initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); + return initCopy; +} + +function parseError(text: string) { + try { + return JSON.parse(text); + } catch (err) { + return null; + } +} + +function handleError(response: Response): Promise { + return response.text().then((text: string) => { + const errorName = 'HttpResponseError : '; + let error: ErrorWithStatus; + const errorJson = parseError(text); + if (errorJson?.status && errorJson?.error && errorJson?.message) { + error = new Error( + `${errorName}${errorJson.status} ${errorJson.error}, message : ${errorJson.message}` + ) as ErrorWithStatus; + error.status = errorJson.status; + } else { + error = new Error( + `${errorName}${response.status} ${response.statusText}` + ) as ErrorWithStatus; + error.status = response.status; + } + throw error; + }); +} + +async function safeFetch(url: Url, initCopy: RequestInit) { + const response = await fetch(url, initCopy); + return response.ok ? response : handleError(response); +} + +export function setRequestHeader( + initReq: InitRequest | undefined, + name: HttpHeaderName, + value: string +): Partial { + let result = initReq; + if (result === undefined) { + result = {}; + } else if (typeof result === 'string') { + result = { + method: result, + }; + } + result.headers = new Headers(); + result.headers.set(name, value); + return result; +} + +export function backendFetch( + url: Url, + init?: InitRequest, + token?: Token +) { + return safeFetch(url, prepareRequest(init, token)); +} + +export async function backendFetchJson( + url: Url, + init?: InitRequest, + token?: Token +): Promise { + const reqInit = setRequestHeader( + init, + 'accept', + HttpContentType.APPLICATION_JSON + ); + return (await backendFetch(url, reqInit, token)).json(); +} + +export async function backendFetchText( + url: Url, + init?: InitRequest, + token?: Token +) { + const reqInit = setRequestHeader( + init, + 'accept', + HttpContentType.TEXT_PLAIN + ); + return (await backendFetch(url, reqInit, token)).text(); +} + +export async function backendFetchFile( + url: Url, + init?: InitRequest, + token?: Token +) { + return (await backendFetch(url, init, token)).blob(); +} + +export function downloadFile(blob: Blob, filename: string, type?: FileType) { + let contentType; + if (type === FileType.ZIP) { + contentType = HttpContentType.APPLICATION_OCTET_STREAM; + } + const href = window.URL.createObjectURL( + new Blob([blob], { type: contentType }) + ); + const link = document.createElement('a'); + link.href = href; + link.setAttribute('download', filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} diff --git a/src/utils/api/api-websocket.ts b/src/utils/api/api-websocket.ts new file mode 100644 index 00000000..102c6051 --- /dev/null +++ b/src/utils/api/api-websocket.ts @@ -0,0 +1,22 @@ +/** + * 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 { getUserToken } from './utils'; + +export function getWsBase(): string { + // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx + return ( + document.baseURI + .replace(/^http(s?):\/\//, 'ws$1://') + .replace(/\/+$/, '') + import.meta.env.VITE_WS_GATEWAY + ); +} + +export function getUrlWithToken(baseUrl: string) { + const querySymbol = baseUrl.includes('?') ? '&' : '?'; + return `${baseUrl}${querySymbol}access_token=${getUserToken()}`; +} diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts new file mode 100644 index 00000000..b0afa325 --- /dev/null +++ b/src/utils/api/index.ts @@ -0,0 +1,10 @@ +/** + * 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 * from './api-rest'; +export * from './api-websocket'; +export * from './utils'; diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts new file mode 100644 index 00000000..0a81eacf --- /dev/null +++ b/src/utils/api/utils.ts @@ -0,0 +1,39 @@ +/** + * 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 { getUser } from '../../redux/commonStore'; + +export enum FileType { + ZIP = 'ZIP', +} + +export function getRequestParam(paramName: string, params: string[] = []) { + return new URLSearchParams(params.map((param) => [paramName, param])); +} + +export function getRequestParams(parameters: Record) { + const searchParams = new URLSearchParams(); + Object.entries(parameters) + .flatMap(([paramName, params]) => + params.map((param) => [paramName, param]) + ) + .forEach(([paramName, param]) => searchParams.append(paramName, param)); + return searchParams; +} + +export function getUserToken() { + return getUser()?.id_token; +} + +export function appendSearchParam( + url: string, + searchParams: URLSearchParams | string | null +) { + return searchParams + ? `${url}${url.includes('?') ? '&' : '?'}${searchParams.toString()}` + : url; +} diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 00000000..1f2c7417 --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,32 @@ +/** + * 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/. + */ + +/* eslint-disable import/prefer-default-export -- utility class */ + +/** + * Function to convert in the best-effort way an error to string + * @param error the "error" to stringify + */ +export function getErrorMessage(error: unknown): string | null { + if (error instanceof Error) { + return error.message; + } + if (error instanceof Object && 'message' in error) { + if ( + typeof error.message === 'string' || + typeof error.message === 'number' || + typeof error.message === 'boolean' + ) { + return `${error.message}`; + } + return JSON.stringify(error.message ?? undefined) ?? null; + } + if (typeof error === 'string') { + return error; + } + return JSON.stringify(error ?? undefined) ?? null; +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 081e37b2..80d0fa4c 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -24,6 +24,16 @@ import { VSC, } from './equipment-types'; +// trick found here https://dev.to/tmlr/til-get-strongly-typed-http-headers-with-typescript-3e33 +export type KeyOfWithoutIndexSignature = { + // copy every declared property from T but remove index signatures + [K in keyof T as string extends K + ? never + : number extends K + ? never + : K]: T[K]; +}; + export type Input = string | number; export type ElementAttributes = { From 8c7784cbee5437fd4c277763dd5fb5bd3feb756f Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 11 Jul 2024 17:53:25 +0200 Subject: [PATCH 02/19] rebase: Refactor existing fetchers --- src/components/TopBar/TopBar.test.tsx | 5 +- src/components/TopBar/TopBar.tsx | 4 +- src/components/filter/utils/filter-api.ts | 24 +++----- src/hooks/predefined-properties-hook.ts | 5 +- src/services/apps-metadata.ts | 34 +++++++--- src/services/directory.ts | 68 ++++++++++---------- src/services/explore.ts | 75 +++++++++-------------- src/services/study.ts | 50 ++++++++++----- 8 files changed, 143 insertions(+), 122 deletions(-) diff --git a/src/components/TopBar/TopBar.test.tsx b/src/components/TopBar/TopBar.test.tsx index 9bdcdea7..a60a12f1 100644 --- a/src/components/TopBar/TopBar.test.tsx +++ b/src/components/TopBar/TopBar.test.tsx @@ -13,7 +13,8 @@ import { red } from '@mui/material/colors'; import { createTheme, ThemeProvider } from '@mui/material'; import { afterEach, beforeEach, expect, it } from '@jest/globals'; import TopBar, { LANG_ENGLISH } from './TopBar'; -import { CommonMetadata, top_bar_en } from '../..'; +import top_bar_en from '../translations/top-bar-en'; +import { AppMetadataCommon } from '../../services'; import PowsyblLogo from '../images/powsybl_logo.svg?react'; @@ -30,7 +31,7 @@ afterEach(() => { container?.remove(); }); -const apps: CommonMetadata[] = [ +const apps: AppMetadataCommon[] = [ { name: 'App1', url: '/app1', diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 4ba03233..e3921915 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -53,7 +53,7 @@ import { User } from 'oidc-client'; import GridLogo, { GridLogoProps } from './GridLogo'; import AboutDialog, { AboutDialogProps } from './AboutDialog'; import { LogoutProps } from '../Login/Logout'; -import { CommonMetadata } from '../../services'; +import { AppMetadataCommon } from '../../services'; const styles = { grow: { @@ -171,7 +171,7 @@ export type TopBarProps = Omit & user?: User; onAboutClick?: () => void; logoAboutDialog?: ReactNode; - appsAndUrls: CommonMetadata[]; + appsAndUrls: AppMetadataCommon[]; onThemeClick?: (theme: GsTheme) => void; theme?: GsTheme; onEquipmentLabellingClick?: (toggle: boolean) => void; diff --git a/src/components/filter/utils/filter-api.ts b/src/components/filter/utils/filter-api.ts index 3178e8d0..3c38cd3d 100644 --- a/src/components/filter/utils/filter-api.ts +++ b/src/components/filter/utils/filter-api.ts @@ -22,8 +22,7 @@ export const saveExplicitNamingFilter = ( id: string | null, setCreateFilterErr: (value: any) => void, handleClose: () => void, - activeDirectory?: UUID, - token?: string + activeDirectory?: UUID ) => { // we remove unnecessary fields from the table let cleanedTableValues; @@ -48,8 +47,7 @@ export const saveExplicitNamingFilter = ( }, name, description, - activeDirectory, - token + activeDirectory ) .then(() => { handleClose(); @@ -65,8 +63,7 @@ export const saveExplicitNamingFilter = ( equipmentType, filterEquipmentsAttributes: cleanedTableValues, }, - name, - token + name ) .then(() => { handleClose(); @@ -81,16 +78,14 @@ export const saveCriteriaBasedFilter = ( filter: any, activeDirectory: any, onClose: () => void, - onError: (message: string) => void, - token?: string + onError: (message: string) => void ) => { const filterForBack = frontToBackTweak(undefined, filter); // no need ID for creation createFilter( filterForBack, filter[FieldConstants.NAME], filter[FieldConstants.DESCRIPTION], - activeDirectory, - token + activeDirectory ) .then(() => { onClose(); @@ -109,8 +104,7 @@ export const saveExpertFilter = ( isFilterCreation: boolean, activeDirectory: any, onClose: () => void, - onError: (message: string) => void, - token?: string + onError: (message: string) => void ) => { if (isFilterCreation) { createFilter( @@ -121,8 +115,7 @@ export const saveExpertFilter = ( }, name, description, - activeDirectory, - token + activeDirectory ) .then(() => { onClose(); @@ -138,8 +131,7 @@ export const saveExpertFilter = ( equipmentType, rules: exportExpertRules(query), }, - name, - token + name ) .then(() => { onClose(); diff --git a/src/hooks/predefined-properties-hook.ts b/src/hooks/predefined-properties-hook.ts index 4282895c..a39c7c0d 100644 --- a/src/hooks/predefined-properties-hook.ts +++ b/src/hooks/predefined-properties-hook.ts @@ -19,7 +19,10 @@ const fetchPredefinedProperties = async ( return Promise.resolve(undefined); } const studyMetadata = await fetchStudyMetadata(); - return studyMetadata.predefinedEquipmentProperties?.[networkEquipmentType]; + return ( + studyMetadata.predefinedEquipmentProperties?.[networkEquipmentType] ?? + undefined + ); }; const usePredefinedProperties = ( diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index fe561e02..33162095 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -4,6 +4,7 @@ * 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 { PredefinedProperties } from '../utils/types'; import { Url } from '../utils/api'; @@ -17,25 +18,44 @@ export type Env = { // [key: string]: string; }; +// https://github.com/gridsuite/deployment/blob/main/docker-compose/version.json +// https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/version.json +export type VersionJson = { + deployVersion?: string; +}; + export async function fetchEnv(): Promise { return (await fetch('env.json')).json(); } -export type CommonMetadata = { +// https://github.com/gridsuite/deployment/blob/main/docker-compose/apps-metadata.json +// https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/apps-metadata.json +export type AppMetadata = AppMetadataCommon | AppMetadataStudy; + +export type AppMetadataCommon = { name: string; url: Url; appColor: string; hiddenInAppsMenu: boolean; }; -export type StudyMetadata = CommonMetadata & { +export type AppMetadataStudy = AppMetadataCommon & { readonly name: 'Study'; resources?: { types: string[]; path: string; }[]; predefinedEquipmentProperties?: { - [networkElementType: string]: PredefinedProperties; + [networkElementType: string]: PredefinedProperties | null | undefined; + /* substation?: { + region?: string[]; + tso?: string[]; + totallyFree?: unknown[]; + Demo?: string[]; + }; + load?: { + codeOI?: string[]; + }; */ }; defaultParametersValues?: { fluxConvention?: string; @@ -45,7 +65,7 @@ export type StudyMetadata = CommonMetadata & { defaultCountry?: string; }; -export async function fetchAppsMetadata(): Promise { +export async function fetchAppsMetadata(): Promise { console.info(`Fetching apps and urls...`); const env = await fetchEnv(); const res = await fetch(`${env.appsMetadataServerUrl}/apps-metadata.json`); @@ -53,12 +73,12 @@ export async function fetchAppsMetadata(): Promise { } const isStudyMetadata = ( - metadata: CommonMetadata -): metadata is StudyMetadata => { + metadata: AppMetadataCommon +): metadata is AppMetadataStudy => { return metadata.name === 'Study'; }; -export async function fetchStudyMetadata(): Promise { +export async function fetchStudyMetadata(): Promise { console.info(`Fetching study metadata...`); const studyMetadata = (await fetchAppsMetadata()).filter(isStudyMetadata); if (!studyMetadata) { diff --git a/src/services/directory.ts b/src/services/directory.ts index c2717906..e3ae6718 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -7,8 +7,10 @@ import { UUID } from 'crypto'; import { + appendSearchParam, + backendFetch, backendFetchJson, - getRequestParam as getRequestParamFromList, + getRequestParam, } from '../utils/api'; import { ElementAttributes } from '../utils/types'; @@ -16,47 +18,45 @@ const PREFIX_DIRECTORY_SERVER_QUERIES = `${ import.meta.env.VITE_API_GATEWAY }/directory`; -export function fetchRootFolders(types: string[]) { +export async function fetchRootFolders(types: string[]) { console.info('Fetching Root Directories'); - - // Add params to Url - const urlSearchParams = getRequestParamFromList( - 'elementTypes', - types - ).toString(); - const fetchRootFoldersUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/root-directories?${urlSearchParams}`; - return backendFetchJson(fetchRootFoldersUrl, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) as Promise; + const urlSearchParams = getRequestParam('elementTypes', types).toString(); + return (await backendFetchJson( + `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/root-directories?${urlSearchParams}`, + 'GET' + )) as ElementAttributes[]; } -export function fetchDirectoryContent(directoryUuid: UUID, types?: string[]) { +export async function fetchDirectoryContent( + directoryUuid: UUID, + types?: string[] +) { console.info("Fetching Folder content '%s'", directoryUuid); - - // Add params to Url - const urlSearchParams = getRequestParamFromList( - 'elementTypes', - types - ).toString(); - - const fetchDirectoryContentUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements${ - urlSearchParams ? `?${urlSearchParams}` : '' - }`; - return backendFetchJson(fetchDirectoryContentUrl, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) as Promise; + return (await backendFetchJson( + appendSearchParam( + `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements`, + getRequestParam('elementTypes', types) + ), + 'GET' + )) as ElementAttributes[]; } -export function fetchDirectoryElementPath(elementUuid: UUID) { +export async function fetchDirectoryElementPath(elementUuid: UUID) { console.info(`Fetching element '${elementUuid}' and its parents info ...`); const fetchPathUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/elements/${encodeURIComponent( elementUuid )}/path`; - console.debug(fetchPathUrl); - return backendFetchJson(fetchPathUrl, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) as Promise; + return (await backendFetchJson(fetchPathUrl, 'GET')) as ElementAttributes[]; +} + +export async function elementExists( + directoryUuid: UUID, + elementName: string, + type: string +) { + const response = await backendFetch( + `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements/${elementName}/types/${type}`, + 'HEAD' + ); + return response.status !== 204; // HTTP 204 : No-content } diff --git a/src/services/explore.ts b/src/services/explore.ts index bd6b404f..4451176c 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -7,9 +7,11 @@ import { UUID } from 'crypto'; import { + appendSearchParam, backendFetch, backendFetchJson, - getRequestParam as getRequestParamFromList, + getRequestParams, + HttpContentType, } from '../utils/api'; import { ElementAttributes } from '../utils/types'; @@ -17,12 +19,11 @@ const PREFIX_EXPLORE_SERVER_QUERIES = `${ import.meta.env.VITE_API_GATEWAY }/explore`; -export function createFilter( +export async function createFilter( newFilter: any, name: string, description: string, - parentDirectoryUuid?: UUID, - token?: string + parentDirectoryUuid?: UUID ) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('name', name); @@ -30,66 +31,48 @@ export function createFilter( if (parentDirectoryUuid) { urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); } - return backendFetch( + await backendFetch( `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters?${urlSearchParams.toString()}`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, body: JSON.stringify(newFilter), - }, - token + } ); } -export function saveFilter(filter: any, name: string, token?: string) { - const urlSearchParams = new URLSearchParams(); - urlSearchParams.append('name', name); - return backendFetch( +export async function saveFilter( + filter: Record, + name: string +) { + await backendFetch( `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters/${ filter.id - }?${urlSearchParams.toString()}`, + }?${new URLSearchParams({ name }).toString()}`, { method: 'PUT', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, body: JSON.stringify(filter), - }, - token + } ); } -export function fetchElementsInfos( +export async function fetchElementsInfos( ids: UUID[], elementTypes?: string[], equipmentTypes?: string[] ) { console.info('Fetching elements metadata'); - - // Add params to Url - const idsParams = getRequestParamFromList( - 'ids', - ids.filter((id) => id) // filter falsy elements - ); - - const equipmentTypesParams = getRequestParamFromList( - 'equipmentTypes', - equipmentTypes - ); - - const elementTypesParams = getRequestParamFromList( - 'elementTypes', - elementTypes - ); - - const urlSearchParams = new URLSearchParams([ - ...idsParams, - ...equipmentTypesParams, - ...elementTypesParams, - ]).toString(); - - const url = `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/elements/metadata?${urlSearchParams}`; - console.debug(url); - return backendFetchJson(url, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) as Promise; + const urlSearchParams = getRequestParams({ + ids: ids.filter((id) => id), // filter falsy elements + equipmentTypes: equipmentTypes ?? [], + elementTypes: elementTypes ?? [], + }); + return (await backendFetchJson( + appendSearchParam( + `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/elements/metadata?${urlSearchParams}`, + urlSearchParams + ), + 'GET' + )) as ElementAttributes[]; } diff --git a/src/services/study.ts b/src/services/study.ts index 11dc15e2..da4aef37 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -5,25 +5,47 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable import/prefer-default-export -- utility class */ - import { UUID } from 'crypto'; import { backendFetchJson } from '../utils/api'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; -export function exportFilter( - studyUuid: UUID, - filterUuid?: UUID, - token?: string -) { +// https://github.com/powsybl/powsybl-core/blob/main/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/IdentifiableType.java#L14 +export enum IdentifiableType { + NETWORK = 'NETWORK', + SUBSTATION = 'SUBSTATION', + VOLTAGE_LEVEL = 'VOLTAGE_LEVEL', + AREA = 'AREA', + HVDC_LINE = 'HVDC_LINE', + BUS = 'BUS', + SWITCH = 'SWITCH', + BUSBAR_SECTION = 'BUSBAR_SECTION', + LINE = 'LINE', + TIE_LINE = 'TIE_LINE', + TWO_WINDINGS_TRANSFORMER = 'TWO_WINDINGS_TRANSFORMER', + THREE_WINDINGS_TRANSFORMER = 'THREE_WINDINGS_TRANSFORMER', + GENERATOR = 'GENERATOR', + BATTERY = 'BATTERY', + LOAD = 'LOAD', + SHUNT_COMPENSATOR = 'SHUNT_COMPENSATOR', + DANGLING_LINE = 'DANGLING_LINE', + STATIC_VAR_COMPENSATOR = 'STATIC_VAR_COMPENSATOR', + HVDC_CONVERTER_STATION = 'HVDC_CONVERTER_STATION', + OVERLOAD_MANAGEMENT_SYSTEM = 'OVERLOAD_MANAGEMENT_SYSTEM', + GROUND = 'GROUND', +} + +// https://github.com/gridsuite/filter/blob/main/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifiableAttributes.java#L20 +export type IdentifiableAttributes = { + id: string; + type: IdentifiableType; + distributionKey: number; // double +}; + +export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { console.info('get filter export on study root node'); - return backendFetchJson( + return (await backendFetchJson( `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - token - ); + 'GET' + )) as IdentifiableAttributes[]; } From 6a1dc705fd9d974b5a7845af4de61526e2a11d63 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 11 Jul 2024 17:06:36 +0200 Subject: [PATCH 03/19] Rework API variables * `VITE_*` variables are kept in finale build * URLs used by fetchers are based on app base to be permissive to a base not at root --- src/services/directory.ts | 5 ++--- src/services/explore.ts | 5 ++--- src/services/study.ts | 4 ++-- src/vite-env.d.ts | 14 ++++++++++++++ vite.config.mts | 23 ++++++++++++++++++----- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/services/directory.ts b/src/services/directory.ts index e3ae6718..98dd9df5 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -11,12 +11,11 @@ import { backendFetch, backendFetchJson, getRequestParam, + getRestBase, } from '../utils/api'; import { ElementAttributes } from '../utils/types'; -const PREFIX_DIRECTORY_SERVER_QUERIES = `${ - import.meta.env.VITE_API_GATEWAY -}/directory`; +const PREFIX_DIRECTORY_SERVER_QUERIES = `${getRestBase()}/directory`; export async function fetchRootFolders(types: string[]) { console.info('Fetching Root Directories'); diff --git a/src/services/explore.ts b/src/services/explore.ts index 4451176c..94944108 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -11,13 +11,12 @@ import { backendFetch, backendFetchJson, getRequestParams, + getRestBase, HttpContentType, } from '../utils/api'; import { ElementAttributes } from '../utils/types'; -const PREFIX_EXPLORE_SERVER_QUERIES = `${ - import.meta.env.VITE_API_GATEWAY -}/explore`; +const PREFIX_EXPLORE_SERVER_QUERIES = `${getRestBase()}/explore`; export async function createFilter( newFilter: any, diff --git a/src/services/study.ts b/src/services/study.ts index da4aef37..2ac32963 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -6,9 +6,9 @@ */ import { UUID } from 'crypto'; -import { backendFetchJson } from '../utils/api'; +import { backendFetchJson, getRestBase } from '../utils/api'; -const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; +const PREFIX_STUDY_QUERIES = `${getRestBase()}/study`; // https://github.com/powsybl/powsybl-core/blob/main/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/IdentifiableType.java#L14 export enum IdentifiableType { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index db95603f..1484ffe2 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -7,3 +7,17 @@ /// /// + +/* Don't know why but seem that TypeScript merge definitions of these two interfaces with existing ones. + * https://vitejs.dev/guide/env-and-mode#intellisense-for-typescript + */ +import { UrlString } from './utils/api'; + +interface ImportMetaEnv { + readonly VITE_API_GATEWAY: UrlString; + readonly VITE_WS_GATEWAY: UrlString; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/vite.config.mts b/vite.config.mts index e0de22a9..442b2a6e 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -53,12 +53,12 @@ export default defineConfig((config) => ({ // file, so e.g. src/nested/foo.js becomes nested/foo path.relative( 'src', - file.slice(0, file.length - path.extname(file).length) + file.slice(0, file.length - path.extname(file).length), ), // This expands the relative paths to absolute paths, so e.g. // src/nested/foo becomes /project/src/nested/foo.js url.fileURLToPath(new URL(file, import.meta.url)), - ]) + ]), ), output: { chunkFileNames: 'chunks/[name].[hash].js', // in case some chunks are created, but it should not because every file is supposed to be an entry point @@ -67,6 +67,19 @@ export default defineConfig((config) => ({ }, }, minify: false, // easier to debug on the apps using this lib + define: + config.command === 'build' + ? { + /* We want to keep some variables in the final build to be resolved by the application using this library. + * https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/define.ts + * If the plugin "vite:define" change how it works, we probably will need to write plugins to obfuscate before then restore after. + */ + 'import.meta.env.VITE_API_GATEWAY': + 'import.meta.env.VITE_API_GATEWAY', + 'import.meta.env.VITE_WS_GATEWAY': + 'import.meta.env.VITE_WS_GATEWAY', + } + : undefined, }, })); @@ -86,7 +99,7 @@ function reactVirtualized(): PluginOption { const reactVirtualizedPath = require.resolve('react-virtualized'); const { pathname: reactVirtualizedFilePath } = new url.URL( reactVirtualizedPath, - import.meta.url + import.meta.url, ); const file = reactVirtualizedFilePath.replace( path.join('dist', 'commonjs', 'index.js'), @@ -95,8 +108,8 @@ function reactVirtualized(): PluginOption { 'es', 'WindowScroller', 'utils', - 'onScroll.js' - ) + 'onScroll.js', + ), ); const code = await fs.readFile(file, 'utf-8'); const modified = code.replace(WRONG_CODE, ''); From 547319ed9f9320e141690a6d2e14be5683bc5e67 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 11 Jul 2024 18:00:57 +0200 Subject: [PATCH 04/19] rebase: Add new common fetchers --- src/services/apps-metadata.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index 33162095..3c120bfa 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -7,6 +7,7 @@ import { PredefinedProperties } from '../utils/types'; import { Url } from '../utils/api'; +import { IdpSettings } from '../utils/AuthService'; export type Env = { appsMetadataServerUrl?: Url; @@ -87,3 +88,23 @@ export async function fetchStudyMetadata(): Promise { return studyMetadata[0]; // There should be only one study metadata } } + +export async function fetchDefaultParametersValues(): Promise< + AppMetadataStudy['defaultParametersValues'] +> { + console.debug('fetching default parameters values from apps-metadata file'); + return (await fetchStudyMetadata()).defaultParametersValues; +} + +export async function fetchVersion(): Promise { + console.debug('Fetching global version...'); + const envData = await fetchEnv(); + return ( + await fetch(`${envData.appsMetadataServerUrl}/version.json`) + ).json(); +} + +export async function fetchIdpSettings() { + // TODO get app base path, can cause problems if router use + return (await (await fetch('idpSettings.json')).json()) as IdpSettings; +} From b9c636c69df5bbeac3be6918c9944ab3837c17ef Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 11 Jul 2024 18:40:55 +0200 Subject: [PATCH 05/19] rebase: `ElementExists` definition from filters --- .../criteria-based-filter-edition-dialog.tsx | 10 +--------- .../filter/expert/expert-filter-edition-dialog.tsx | 11 +---------- .../explicit-naming-filter-edition-dialog.tsx | 11 +---------- src/components/filter/filter-creation-dialog.tsx | 4 ---- src/components/filter/filter-form.tsx | 5 +---- .../inputs/react-hook-form/unique-name-input.tsx | 9 ++++----- src/utils/ElementType.ts | 9 +-------- 7 files changed, 9 insertions(+), 50 deletions(-) diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index bb86f2e1..3d27a886 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -27,7 +27,6 @@ import yup from '../../../utils/yup-config'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; import { saveFilter } from '../../../services/explore'; -import { ElementExistsType } from '../../../utils/ElementType'; import FilterForm from '../filter-form'; export type SelectionCopy = { @@ -67,7 +66,6 @@ interface CriteriaBasedFilterEditionDialogProps { selection: SelectionCopy ) => Dispatch>; activeDirectory?: UUID; - elementExists?: ElementExistsType; language?: string; } @@ -82,7 +80,6 @@ function CriteriaBasedFilterEditionDialog({ selectionForCopy, setSelelectionForCopy, activeDirectory, - elementExists, language, }: CriteriaBasedFilterEditionDialogProps) { const { snackError } = useSnackMessage(); @@ -169,12 +166,7 @@ function CriteriaBasedFilterEditionDialog({ isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} > - {isDataReady && ( - - )} + {isDataReady && } ); } diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index 41a839b1..f1271fc6 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -20,7 +20,6 @@ import { saveExpertFilter } from '../utils/filter-api'; import { importExpertRules } from './expert-filter-utils'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { ElementExistsType } from '../../../utils/ElementType'; const formSchema = yup .object() @@ -39,12 +38,10 @@ export interface ExpertFilterEditionDialogProps { open: boolean; onClose: () => void; broadcastChannel: BroadcastChannel; - selectionForCopy: any; getFilterById: (id: string) => Promise<{ [prop: string]: any }>; setSelectionForCopy: (selection: any) => void; activeDirectory?: UUID; - elementExists?: ElementExistsType; language?: string; } @@ -59,7 +56,6 @@ function ExpertFilterEditionDialog({ getFilterById, setSelectionForCopy, activeDirectory, - elementExists, language, }: ExpertFilterEditionDialogProps) { const { snackError } = useSnackMessage(); @@ -154,12 +150,7 @@ function ExpertFilterEditionDialog({ isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} > - {isDataReady && ( - - )} + {isDataReady && } ); } diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index 67220d88..0bb3b6cc 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -20,12 +20,10 @@ import { FILTER_EQUIPMENTS_ATTRIBUTES, } from './explicit-naming-filter-form'; import FieldConstants from '../../../utils/field-constants'; - import FilterForm from '../filter-form'; import { noSelectionForCopy } from '../../../utils/equipment-types'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { ElementExistsType } from '../../../utils/ElementType'; const formSchema = yup .object() @@ -48,7 +46,6 @@ interface ExplicitNamingFilterEditionDialogProps { setSelectionForCopy: (selection: any) => void; getFilterById: (id: string) => Promise; activeDirectory?: UUID; - elementExists?: ElementExistsType; language?: string; } @@ -63,7 +60,6 @@ function ExplicitNamingFilterEditionDialog({ setSelectionForCopy, getFilterById, activeDirectory, - elementExists, language, }: ExplicitNamingFilterEditionDialogProps) { const { snackError } = useSnackMessage(); @@ -160,12 +156,7 @@ function ExplicitNamingFilterEditionDialog({ isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} > - {isDataReady && ( - - )} + {isDataReady && } ); } diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index 61edd14b..d39490f5 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -34,7 +34,6 @@ import { getExpertFilterEmptyFormData, } from './expert/expert-filter-form'; import { FilterType } from './constants/filter-constants'; -import { ElementExistsType } from '../../utils/ElementType'; const emptyFormData = { [FieldConstants.NAME]: '', @@ -66,7 +65,6 @@ export interface FilterCreationDialogProps { open: boolean; onClose: () => void; activeDirectory?: UUID; - elementExists?: ElementExistsType; language?: string; sourceFilterForExplicitNamingConversion?: { id: UUID; @@ -78,7 +76,6 @@ function FilterCreationDialog({ open, onClose, activeDirectory, - elementExists, language, sourceFilterForExplicitNamingConversion = undefined, }: FilterCreationDialogProps) { @@ -173,7 +170,6 @@ function FilterCreationDialog({ {creation && ( diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index bf8434b9..b6e36b28 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -15,7 +15,8 @@ import TextField from '@mui/material/TextField'; import { UUID } from 'crypto'; import useDebounce from '../../../hooks/useDebounce'; import FieldConstants from '../../../utils/field-constants'; -import { ElementExistsType, ElementType } from '../../../utils/ElementType'; +import { ElementType } from '../../../utils/ElementType'; +import { elementExists } from '../../../services'; interface UniqueNameInputProps { name: string; @@ -34,7 +35,6 @@ interface UniqueNameInputProps { | 'InputProps' >; activeDirectory?: UUID; - elementExists?: ElementExistsType; } /** @@ -48,7 +48,6 @@ function UniqueNameInput({ onManualChangeCallback, formProps, activeDirectory, - elementExists, }: UniqueNameInputProps) { const { field: { onChange, onBlur, value, ref }, @@ -77,7 +76,7 @@ function UniqueNameInput({ const handleCheckName = useCallback( (nameValue: string) => { if (nameValue) { - elementExists?.(directory, nameValue, elementType) + elementExists(directory, nameValue, elementType) .then((alreadyExist) => { if (alreadyExist) { setError(name, { @@ -98,7 +97,7 @@ function UniqueNameInput({ }); } }, - [setError, clearErrors, name, elementType, elementExists, directory] + [setError, clearErrors, name, elementType, directory] ); const debouncedHandleCheckName = useDebounce(handleCheckName, 700); diff --git a/src/utils/ElementType.ts b/src/utils/ElementType.ts index 125da320..73622383 100644 --- a/src/utils/ElementType.ts +++ b/src/utils/ElementType.ts @@ -5,8 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { UUID } from 'crypto'; - +// eslint-disable-next-line import/prefer-default-export -- TODO export enum ElementType { DIRECTORY = 'DIRECTORY', STUDY = 'STUDY', @@ -20,9 +19,3 @@ export enum ElementType { SENSITIVITY_PARAMETERS = 'SENSITIVITY_PARAMETERS', SHORT_CIRCUIT_PARAMETERS = 'SHORT_CIRCUIT_PARAMETERS', } - -export type ElementExistsType = ( - directory: UUID, - value: string, - elementType: ElementType -) => Promise; From 8ec3572fc8b3c8a00dcc116bf729f15f41deb84d Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 16 Jul 2024 21:12:13 +0200 Subject: [PATCH 06/19] refactor fetchers access --- .../directory-item-selector.tsx | 12 +++----- .../dialogs/modify-element-selection.tsx | 18 +++++++----- .../criteria-based-filter-edition-dialog.tsx | 4 +-- .../explicit-naming-filter-form.tsx | 7 +++-- src/components/filter/utils/filter-api.ts | 12 ++++---- .../react-hook-form/directory-items-input.tsx | 22 +++++++------- .../react-hook-form/unique-name-input.tsx | 4 +-- .../element-value-editor.tsx | 29 ++++++++++--------- src/hooks/predefined-properties-hook.ts | 4 +-- src/services/index.ts | 15 +++++++--- 10 files changed, 70 insertions(+), 57 deletions(-) diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index 7416858b..59bbea61 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -15,11 +15,7 @@ import TreeViewFinder, { TreeViewFinderProps, } from '../TreeViewFinder/TreeViewFinder'; import { useSnackMessage } from '../../hooks/useSnackMessage'; -import { - fetchDirectoryContent, - fetchElementsInfos, - fetchRootFolders, -} from '../../services'; +import { DirectorySvc, ExploreSvc } from '../../services'; const styles = { icon: (theme: Theme) => ({ @@ -269,7 +265,7 @@ function DirectoryItemSelector({ ); const updateRootDirectories = useCallback(() => { - fetchRootFolders(types) + DirectorySvc.fetchRootFolders(types) .then((newData) => { const [nrs, mdr] = updatedTree( rootsRef.current, @@ -292,7 +288,7 @@ function DirectoryItemSelector({ const fetchDirectory = useCallback( (nodeId: UUID): void => { const typeList = types.includes(ElementType.DIRECTORY) ? [] : types; - fetchDirectoryContent(nodeId, typeList) + DirectorySvc.fetchDirectoryContent(nodeId, typeList) .then((children) => { const childrenMatchedTypes = children.filter((item: any) => contentFilter().has(item.type) @@ -303,7 +299,7 @@ function DirectoryItemSelector({ equipmentTypes && equipmentTypes.length > 0 ) { - fetchElementsInfos( + ExploreSvc.fetchElementsInfos( childrenMatchedTypes.map((e: any) => e.elementUuid), types, equipmentTypes diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index dee56b09..06287064 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -14,7 +14,7 @@ import { TreeViewFinderNodeProps } from '../TreeViewFinder'; import FieldConstants from '../../utils/field-constants'; import DirectoryItemSelector from '../DirectoryItemSelector/directory-item-selector'; import { ElementType } from '../../utils/ElementType'; -import { fetchDirectoryElementPath } from '../../services'; +import { DirectorySvc } from '../../services'; export interface ModifyElementSelectionProps { elementType: ElementType; @@ -46,13 +46,15 @@ function ModifyElementSelection(props: ModifyElementSelectionProps) { useEffect(() => { if (directory) { - fetchDirectoryElementPath(directory).then((res: any) => { - setActiveDirectoryName( - res - .map((element: any) => element.elementName.trim()) - .join('/') - ); - }); + DirectorySvc.fetchDirectoryElementPath(directory).then( + (res: any) => { + setActiveDirectoryName( + res + .map((element: any) => element.elementName.trim()) + .join('/') + ); + } + ); } }, [directory]); diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index 3d27a886..353c1b02 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -26,7 +26,7 @@ import { criteriaBasedFilterSchema } from './criteria-based-filter-form'; import yup from '../../../utils/yup-config'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { saveFilter } from '../../../services/explore'; +import { ExploreSvc } from '../../../services'; import FilterForm from '../filter-form'; export type SelectionCopy = { @@ -124,7 +124,7 @@ function CriteriaBasedFilterEditionDialog({ const onSubmit = useCallback( (filterForm: any) => { - saveFilter( + ExploreSvc.saveFilter( frontToBackTweak(id, filterForm), filterForm[FieldConstants.NAME] ) diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index 129974ad..b06926f9 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -26,7 +26,7 @@ import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { ElementType } from '../../../utils/ElementType'; import ModifyElementSelection from '../../dialogs/modify-element-selection'; -import { exportFilter } from '../../../services/study'; +import { StudySvc } from '../../../services'; import { EquipmentType } from '../../../utils/EquipmentType'; export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; @@ -226,7 +226,10 @@ function ExplicitNamingFilterForm({ }; const onStudySelected = (studyUuid: UUID) => { - exportFilter(studyUuid, sourceFilterForExplicitNamingConversion?.id) + StudySvc.exportFilter( + studyUuid, + sourceFilterForExplicitNamingConversion?.id + ) .then((matchingEquipments: any) => { setValue( FILTER_EQUIPMENTS_ATTRIBUTES, diff --git a/src/components/filter/utils/filter-api.ts b/src/components/filter/utils/filter-api.ts index 3c38cd3d..966eac23 100644 --- a/src/components/filter/utils/filter-api.ts +++ b/src/components/filter/utils/filter-api.ts @@ -11,7 +11,7 @@ import { frontToBackTweak } from '../criteria-based/criteria-based-filter-utils' import { Generator, Load } from '../../../utils/equipment-types'; import { exportExpertRules } from '../expert/expert-filter-utils'; import { DISTRIBUTION_KEY, FilterType } from '../constants/filter-constants'; -import { createFilter, saveFilter } from '../../../services/explore'; +import { ExploreSvc } from '../../../services'; export const saveExplicitNamingFilter = ( tableValues: any[], @@ -39,7 +39,7 @@ export const saveExplicitNamingFilter = ( })); } if (isFilterCreation) { - createFilter( + ExploreSvc.createFilter( { type: FilterType.EXPLICIT_NAMING.id, equipmentType, @@ -56,7 +56,7 @@ export const saveExplicitNamingFilter = ( setCreateFilterErr(error.message); }); } else { - saveFilter( + ExploreSvc.saveFilter( { id, type: FilterType.EXPLICIT_NAMING.id, @@ -81,7 +81,7 @@ export const saveCriteriaBasedFilter = ( onError: (message: string) => void ) => { const filterForBack = frontToBackTweak(undefined, filter); // no need ID for creation - createFilter( + ExploreSvc.createFilter( filterForBack, filter[FieldConstants.NAME], filter[FieldConstants.DESCRIPTION], @@ -107,7 +107,7 @@ export const saveExpertFilter = ( onError: (message: string) => void ) => { if (isFilterCreation) { - createFilter( + ExploreSvc.createFilter( { type: FilterType.EXPERT.id, equipmentType, @@ -124,7 +124,7 @@ export const saveExpertFilter = ( onError(error.message); }); } else { - saveFilter( + ExploreSvc.saveFilter( { id, type: FilterType.EXPERT.id, diff --git a/src/components/inputs/react-hook-form/directory-items-input.tsx b/src/components/inputs/react-hook-form/directory-items-input.tsx index e7e523e5..821231d4 100644 --- a/src/components/inputs/react-hook-form/directory-items-input.tsx +++ b/src/components/inputs/react-hook-form/directory-items-input.tsx @@ -29,7 +29,7 @@ import { mergeSx } from '../../../utils/styles'; import OverflowableText from '../../OverflowableText'; import MidFormError from './error-management/mid-form-error'; import DirectoryItemSelector from '../../DirectoryItemSelector/directory-item-selector'; -import { fetchDirectoryElementPath } from '../../../services'; +import { DirectorySvc } from '../../../services'; export const NAME = 'name'; @@ -176,16 +176,18 @@ function DirectoryItemsInput({ const chips = getValues(name) as any[]; const chip = chips.at(index)?.id; if (chip) { - fetchDirectoryElementPath(chip).then((response: any[]) => { - const path = response - .filter((e) => e.elementUuid !== chip) - .map((e) => e.elementUuid); + DirectorySvc.fetchDirectoryElementPath(chip).then( + (response: any[]) => { + const path = response + .filter((e) => e.elementUuid !== chip) + .map((e) => e.elementUuid); - setExpanded(path); - setSelected([chip]); - setDirectoryItemSelectorOpen(true); - setMultiSelect(false); - }); + setExpanded(path); + setSelected([chip]); + setDirectoryItemSelectorOpen(true); + setMultiSelect(false); + } + ); } }, [getValues, name] diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index b6e36b28..dc6b91b6 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -16,7 +16,7 @@ import { UUID } from 'crypto'; import useDebounce from '../../../hooks/useDebounce'; import FieldConstants from '../../../utils/field-constants'; import { ElementType } from '../../../utils/ElementType'; -import { elementExists } from '../../../services'; +import { DirectorySvc } from '../../../services'; interface UniqueNameInputProps { name: string; @@ -76,7 +76,7 @@ function UniqueNameInput({ const handleCheckName = useCallback( (nameValue: string) => { if (nameValue) { - elementExists(directory, nameValue, elementType) + DirectorySvc.elementExists(directory, nameValue, elementType) .then((alreadyExist) => { if (alreadyExist) { setError(name, { diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index 9c249f99..904d7285 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -8,7 +8,7 @@ import { validate as uuidValidate } from 'uuid'; import { useEffect } from 'react'; import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; -import { fetchElementsInfos } from '../../../services'; +import { ExploreSvc } from '../../../services'; import DirectoryItemsInput from '../react-hook-form/directory-items-input'; interface ElementValueEditorProps { @@ -43,18 +43,20 @@ function ElementValueEditor(props: ElementValueEditorProps) { defaultValue[0].length > 0 && uuidValidate(defaultValue[0]) ) { - fetchElementsInfos(defaultValue).then((childrenWithMetadata) => { - setValue( - name, - childrenWithMetadata.map((v: any) => { - return { - id: v.elementUuid, - name: v.elementName, - specificMetadata: v.specificMetadata, - }; - }) - ); - }); + ExploreSvc.fetchElementsInfos(defaultValue).then( + (childrenWithMetadata) => { + setValue( + name, + childrenWithMetadata.map((v: any) => { + return { + id: v.elementUuid, + name: v.elementName, + specificMetadata: v.specificMetadata, + }; + }) + ); + } + ); } }, [name, defaultValue, elementType, setValue]); @@ -72,4 +74,5 @@ function ElementValueEditor(props: ElementValueEditorProps) { /> ); } + export default ElementValueEditor; diff --git a/src/hooks/predefined-properties-hook.ts b/src/hooks/predefined-properties-hook.ts index a39c7c0d..eb950c47 100644 --- a/src/hooks/predefined-properties-hook.ts +++ b/src/hooks/predefined-properties-hook.ts @@ -8,7 +8,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import mapEquipmentTypeForPredefinedProperties from '../utils/equipment-types-for-predefined-properties-mapper'; import { useSnackMessage } from './useSnackMessage'; import { EquipmentType, PredefinedProperties } from '../utils/types'; -import { fetchStudyMetadata } from '../services'; +import { AppsMetadataSvc } from '../services'; const fetchPredefinedProperties = async ( equipmentType: EquipmentType @@ -18,7 +18,7 @@ const fetchPredefinedProperties = async ( if (networkEquipmentType === undefined) { return Promise.resolve(undefined); } - const studyMetadata = await fetchStudyMetadata(); + const studyMetadata = await AppsMetadataSvc.fetchStudyMetadata(); return ( studyMetadata.predefinedEquipmentProperties?.[networkEquipmentType] ?? undefined diff --git a/src/services/index.ts b/src/services/index.ts index 18fac583..34ea4a30 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,7 +4,14 @@ * 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 * from './explore'; -export * from './apps-metadata'; -export * from './directory'; -export * from './study'; +export * as ExploreSvc from './explore'; +export type * from './explore'; + +export * as AppsMetadataSvc from './apps-metadata'; +export type * from './apps-metadata'; + +export * as DirectorySvc from './directory'; +export type * from './directory'; + +export * as StudySvc from './study'; +export type * from './study'; From 7f73212994f55601a94e8b48bc776646f192e942 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 18 Jul 2024 15:40:22 +0200 Subject: [PATCH 07/19] Move non remote fetcher to dedicated file --- src/services/app-local.ts | 28 ++++++++++++++++++++++++++++ src/services/apps-metadata.ts | 21 +-------------------- src/services/index.ts | 8 ++++++-- 3 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 src/services/app-local.ts diff --git a/src/services/app-local.ts b/src/services/app-local.ts new file mode 100644 index 00000000..e1ec7c11 --- /dev/null +++ b/src/services/app-local.ts @@ -0,0 +1,28 @@ +/* + * 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 { Url } from '../utils/api'; +import { IdpSettings } from '../utils/AuthService'; + +export type Env = { + appsMetadataServerUrl?: Url; + mapBoxToken?: string; + // https://github.com/gridsuite/deployment/blob/main/docker-compose/env.json + // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-dev/env.json + // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-integ/env.json + // https://github.com/gridsuite/deployment/blob/main/k8s/live/local/env.json + // [key: string]: string; +}; + +export async function fetchEnv(): Promise { + return (await fetch('env.json')).json(); +} + +export async function fetchIdpSettings() { + // TODO get app base path, can cause problems if router use + return (await (await fetch('idpSettings.json')).json()) as IdpSettings; +} diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index 3c120bfa..d6076d1b 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -7,17 +7,7 @@ import { PredefinedProperties } from '../utils/types'; import { Url } from '../utils/api'; -import { IdpSettings } from '../utils/AuthService'; - -export type Env = { - appsMetadataServerUrl?: Url; - mapBoxToken?: string; - // https://github.com/gridsuite/deployment/blob/main/docker-compose/env.json - // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-dev/env.json - // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-integ/env.json - // https://github.com/gridsuite/deployment/blob/main/k8s/live/local/env.json - // [key: string]: string; -}; +import { fetchEnv } from './app-local'; // https://github.com/gridsuite/deployment/blob/main/docker-compose/version.json // https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/version.json @@ -25,10 +15,6 @@ export type VersionJson = { deployVersion?: string; }; -export async function fetchEnv(): Promise { - return (await fetch('env.json')).json(); -} - // https://github.com/gridsuite/deployment/blob/main/docker-compose/apps-metadata.json // https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/apps-metadata.json export type AppMetadata = AppMetadataCommon | AppMetadataStudy; @@ -103,8 +89,3 @@ export async function fetchVersion(): Promise { await fetch(`${envData.appsMetadataServerUrl}/version.json`) ).json(); } - -export async function fetchIdpSettings() { - // TODO get app base path, can cause problems if router use - return (await (await fetch('idpSettings.json')).json()) as IdpSettings; -} diff --git a/src/services/index.ts b/src/services/index.ts index 34ea4a30..4aa24597 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,8 +4,9 @@ * 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 * as ExploreSvc from './explore'; -export type * from './explore'; + +export * as AppLocalSvc from './app-local'; +export type * from './app-local'; export * as AppsMetadataSvc from './apps-metadata'; export type * from './apps-metadata'; @@ -13,5 +14,8 @@ export type * from './apps-metadata'; export * as DirectorySvc from './directory'; export type * from './directory'; +export * as ExploreSvc from './explore'; +export type * from './explore'; + export * as StudySvc from './study'; export type * from './study'; From c4989e9dca11e57c993605b73a0bf51c082820b7 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 18 Jul 2024 15:44:18 +0200 Subject: [PATCH 08/19] Add fetchers for AboutDialog --- src/services/apps-metadata.ts | 4 ++++ src/services/study.ts | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index d6076d1b..a628c55e 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -89,3 +89,7 @@ export async function fetchVersion(): Promise { await fetch(`${envData.appsMetadataServerUrl}/version.json`) ).json(); } + +export async function fetchDeployedVersion() { + return (await fetchVersion())?.deployVersion; +} diff --git a/src/services/study.ts b/src/services/study.ts index 2ac32963..05d52339 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -7,6 +7,7 @@ import { UUID } from 'crypto'; import { backendFetchJson, getRestBase } from '../utils/api'; +import { GridSuiteModule } from '../components/TopBar/AboutDialog'; const PREFIX_STUDY_QUERIES = `${getRestBase()}/study`; @@ -49,3 +50,10 @@ export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { 'GET' )) as IdentifiableAttributes[]; } + +export async function getServersInfos(viewName: string) { + console.info('get backend servers informations'); + return (await backendFetchJson( + `${PREFIX_STUDY_QUERIES}/v1/servers/about?view=${viewName}` + )) as GridSuiteModule[]; +} From de3df20ca7a57f28628439b9fdcca1c3d380dfc9 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 18 Jul 2024 16:48:16 +0200 Subject: [PATCH 09/19] Resolve problem of `_API_GATEWAY` & `_WS_GATEWAY` variables for CRA apps --- src/services/app-local.ts | 1 - src/services/directory.ts | 19 ++++++++++++++----- src/services/explore.ts | 19 +++++++++++++------ src/services/study.ts | 13 ++++++++++--- src/utils/api/api-rest.ts | 5 ++--- src/utils/api/api-websocket.ts | 3 ++- src/utils/api/index.ts | 1 + src/utils/api/variables.ts | 29 +++++++++++++++++++++++++++++ 8 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 src/utils/api/variables.ts diff --git a/src/services/app-local.ts b/src/services/app-local.ts index e1ec7c11..61a8f44c 100644 --- a/src/services/app-local.ts +++ b/src/services/app-local.ts @@ -23,6 +23,5 @@ export async function fetchEnv(): Promise { } export async function fetchIdpSettings() { - // TODO get app base path, can cause problems if router use return (await (await fetch('idpSettings.json')).json()) as IdpSettings; } diff --git a/src/services/directory.ts b/src/services/directory.ts index 98dd9df5..b818b438 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -15,13 +15,20 @@ import { } from '../utils/api'; import { ElementAttributes } from '../utils/types'; -const PREFIX_DIRECTORY_SERVER_QUERIES = `${getRestBase()}/directory`; +/** + * Return the base API prefix to the directory server + *
Note: cannot be a const because part of the prefix can be overridden at runtime + * @param vApi the version of api to use + */ +function getPrefix(vApi: number) { + return `${getRestBase()}/directory/v${vApi}`; +} export async function fetchRootFolders(types: string[]) { console.info('Fetching Root Directories'); const urlSearchParams = getRequestParam('elementTypes', types).toString(); return (await backendFetchJson( - `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/root-directories?${urlSearchParams}`, + `${getPrefix(1)}/root-directories?${urlSearchParams}`, 'GET' )) as ElementAttributes[]; } @@ -33,7 +40,7 @@ export async function fetchDirectoryContent( console.info("Fetching Folder content '%s'", directoryUuid); return (await backendFetchJson( appendSearchParam( - `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements`, + `${getPrefix(1)}/directories/${directoryUuid}/elements`, getRequestParam('elementTypes', types) ), 'GET' @@ -42,7 +49,7 @@ export async function fetchDirectoryContent( export async function fetchDirectoryElementPath(elementUuid: UUID) { console.info(`Fetching element '${elementUuid}' and its parents info ...`); - const fetchPathUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/elements/${encodeURIComponent( + const fetchPathUrl = `${getPrefix(1)}/elements/${encodeURIComponent( elementUuid )}/path`; return (await backendFetchJson(fetchPathUrl, 'GET')) as ElementAttributes[]; @@ -54,7 +61,9 @@ export async function elementExists( type: string ) { const response = await backendFetch( - `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements/${elementName}/types/${type}`, + `${getPrefix( + 1 + )}/directories/${directoryUuid}/elements/${elementName}/types/${type}`, 'HEAD' ); return response.status !== 204; // HTTP 204 : No-content diff --git a/src/services/explore.ts b/src/services/explore.ts index 94944108..f1df42ff 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -16,7 +16,14 @@ import { } from '../utils/api'; import { ElementAttributes } from '../utils/types'; -const PREFIX_EXPLORE_SERVER_QUERIES = `${getRestBase()}/explore`; +/** + * Return the base API prefix to the explore server + *
Note: cannot be a const because part of the prefix can be overridden at runtime + * @param vApi the version of api to use + */ +function getPrefix(vApi: number) { + return `${getRestBase()}/explore/v${vApi}`; +} export async function createFilter( newFilter: any, @@ -31,7 +38,7 @@ export async function createFilter( urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); } await backendFetch( - `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters?${urlSearchParams.toString()}`, + `${getPrefix(1)}/explore/filters?${urlSearchParams.toString()}`, { method: 'POST', headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, @@ -45,9 +52,9 @@ export async function saveFilter( name: string ) { await backendFetch( - `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters/${ - filter.id - }?${new URLSearchParams({ name }).toString()}`, + `${getPrefix(1)}/explore/filters/${filter.id}?${new URLSearchParams({ + name, + }).toString()}`, { method: 'PUT', headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, @@ -69,7 +76,7 @@ export async function fetchElementsInfos( }); return (await backendFetchJson( appendSearchParam( - `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/elements/metadata?${urlSearchParams}`, + `${getPrefix(1)}/explore/elements/metadata?${urlSearchParams}`, urlSearchParams ), 'GET' diff --git a/src/services/study.ts b/src/services/study.ts index 05d52339..161e7aea 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -9,7 +9,14 @@ import { UUID } from 'crypto'; import { backendFetchJson, getRestBase } from '../utils/api'; import { GridSuiteModule } from '../components/TopBar/AboutDialog'; -const PREFIX_STUDY_QUERIES = `${getRestBase()}/study`; +/** + * Return the base API prefix to the study server + *
Note: cannot be a const because part of the prefix can be overridden at runtime + * @param vApi the version of api to use + */ +function getPrefix(vApi: number) { + return `${getRestBase()}/study/v${vApi}`; +} // https://github.com/powsybl/powsybl-core/blob/main/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/IdentifiableType.java#L14 export enum IdentifiableType { @@ -46,7 +53,7 @@ export type IdentifiableAttributes = { export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { console.info('get filter export on study root node'); return (await backendFetchJson( - `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, + `${getPrefix(1)}/studies/${studyUuid}/filters/${filterUuid}/elements`, 'GET' )) as IdentifiableAttributes[]; } @@ -54,6 +61,6 @@ export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { export async function getServersInfos(viewName: string) { console.info('get backend servers informations'); return (await backendFetchJson( - `${PREFIX_STUDY_QUERIES}/v1/servers/about?view=${viewName}` + `${getPrefix(1)}/servers/about?view=${viewName}` )) as GridSuiteModule[]; } diff --git a/src/utils/api/api-rest.ts b/src/utils/api/api-rest.ts index 8b9a98f1..422d2753 100644 --- a/src/utils/api/api-rest.ts +++ b/src/utils/api/api-rest.ts @@ -7,6 +7,7 @@ import { IncomingHttpHeaders } from 'node:http'; import { LiteralUnion } from 'type-fest'; +import { getGatewayRestPath } from './variables'; import { FileType, getUserToken } from './utils'; import { KeyOfWithoutIndexSignature } from '../types'; @@ -45,9 +46,7 @@ export interface ErrorWithStatus extends Error { export function getRestBase(): string { // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return ( - document.baseURI.replace(/\/+$/, '') + import.meta.env.VITE_API_GATEWAY - ); + return document.baseURI.replace(/\/+$/, '') + getGatewayRestPath(); } function prepareRequest(init?: InitRequest, token?: Token): RequestInit { diff --git a/src/utils/api/api-websocket.ts b/src/utils/api/api-websocket.ts index 102c6051..374ca042 100644 --- a/src/utils/api/api-websocket.ts +++ b/src/utils/api/api-websocket.ts @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { getGatewayWsPath } from './variables'; import { getUserToken } from './utils'; export function getWsBase(): string { @@ -12,7 +13,7 @@ export function getWsBase(): string { return ( document.baseURI .replace(/^http(s?):\/\//, 'ws$1://') - .replace(/\/+$/, '') + import.meta.env.VITE_WS_GATEWAY + .replace(/\/+$/, '') + getGatewayWsPath() ); } diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts index b0afa325..d674c373 100644 --- a/src/utils/api/index.ts +++ b/src/utils/api/index.ts @@ -8,3 +8,4 @@ export * from './api-rest'; export * from './api-websocket'; export * from './utils'; +export { setGatewayRestPath, setGatewayWsPath } from './variables'; diff --git a/src/utils/api/variables.ts b/src/utils/api/variables.ts new file mode 100644 index 00000000..194d61a2 --- /dev/null +++ b/src/utils/api/variables.ts @@ -0,0 +1,29 @@ +/* + * 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/. + */ + +/* The problem we have here is that some front apps don't use Vite, and sont using VITE_* vars don't work... + * What we do here is to try to use these variables as default, while permit to devs to overwrite these constants. + */ + +let restGatewayPath: string = import.meta.env.VITE_API_GATEWAY; +let wsGatewayPath: string = import.meta.env.VITE_WS_GATEWAY; + +export function setGatewayRestPath(restPath: string) { + restGatewayPath = restPath; +} + +export function getGatewayRestPath() { + return restGatewayPath; +} + +export function setGatewayWsPath(wsPath: string) { + wsGatewayPath = wsPath; +} + +export function getGatewayWsPath() { + return wsGatewayPath; +} From e3a16de4463d38224eaf8a81bbbc06ef5c887b10 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 23 Jul 2024 16:05:37 +0200 Subject: [PATCH 10/19] Add config server common fetchers --- package-lock.json | 7 ++ package.json | 1 + src/services/config-notification.ts | 40 +++++++++++ src/services/config.ts | 105 ++++++++++++++++++++++++++++ src/services/index.ts | 6 ++ src/vite-env.d.ts | 1 + vite.config.mts | 2 + 7 files changed, 162 insertions(+) create mode 100644 src/services/config-notification.ts create mode 100644 src/services/config.ts diff --git a/package-lock.json b/package-lock.json index 8579bf73..6d8233ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,6 +121,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" } }, @@ -13937,6 +13938,12 @@ "once": "^1.3.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/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", diff --git a/package.json b/package.json index ee7abd30..62c60171 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,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" }, "devDependencies": { diff --git a/src/services/config-notification.ts b/src/services/config-notification.ts new file mode 100644 index 00000000..c6e92387 --- /dev/null +++ b/src/services/config-notification.ts @@ -0,0 +1,40 @@ +/* + * 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/. + */ + +/* eslint-disable import/prefer-default-export */ + +import ReconnectingWebSocket, { Event } from 'reconnecting-websocket'; +import { getUrlWithToken, getWsBase } from '../utils/api'; + +/** + * Return the base API prefix to the config server + *
Note: cannot be a const because part of the prefix can be overridden at runtime + * @param vApi the version of api to use + */ +function getPrefix() { + return `${getWsBase()}/config-notification`; +} + +export function connectNotificationsWsUpdateConfig( + appName: string +): ReconnectingWebSocket { + const webSocketUrl = `${getPrefix()}/notify?appName=${appName}`; + const reconnectingWebSocket = new ReconnectingWebSocket( + () => getUrlWithToken(webSocketUrl), + undefined, + { debug: `${import.meta.env.VITE_DEBUG_REQUESTS}` === 'true' } + ); + reconnectingWebSocket.onopen = (event: Event) => { + console.groupCollapsed( + `Connected Websocket update config ui: ${appName}` + ); + console.debug(`Websocket URL: ${webSocketUrl}`); + console.dir(event); + console.groupEnd(); + }; + return reconnectingWebSocket; +} diff --git a/src/services/config.ts b/src/services/config.ts new file mode 100644 index 00000000..c9a5b5d8 --- /dev/null +++ b/src/services/config.ts @@ -0,0 +1,105 @@ +/* + * 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/. + */ +/* eslint-disable import/prefer-default-export */ + +import { GsLang, GsTheme } from '@gridsuite/commons-ui'; +import { LiteralUnion } from 'type-fest'; +import { backendFetch, backendFetchJson, getRestBase } from '../utils/api'; + +/** + * Return the base API prefix to the config server + *
Note: cannot be a const because part of the prefix can be overridden at runtime + * @param vApi the version of api to use + */ +function getPrefix(vApi: number) { + return `${getRestBase()}/config/v${vApi}`; +} + +export const COMMON_APP_NAME = 'common'; + +export const PARAM_THEME = 'theme'; +export const PARAM_LANGUAGE = 'language'; + +const COMMON_CONFIG_PARAMS_NAMES = new Set([PARAM_THEME, PARAM_LANGUAGE]); + +// https://github.com/gridsuite/config-server/blob/main/src/main/java/org/gridsuite/config/server/dto/ParameterInfos.java +export type ConfigParameter = + | { + readonly name: typeof PARAM_LANGUAGE; + value: GsLang; + } + | { + readonly name: typeof PARAM_THEME; + value: GsTheme; + }; +export type ConfigParameters = ConfigParameter[]; + +type AppConfigParameter = LiteralUnion< + typeof PARAM_THEME | typeof PARAM_LANGUAGE, + string +>; + +type AppConfigType = + TAppName extends string + ? typeof COMMON_APP_NAME | TAppName + : LiteralUnion; + +/** + * Permit knowing if a parameter is common/shared between webapps or is specific to this application. + * @param appName the current application name/identifier + * @param paramName the parameter name/key + */ +function getAppName( + appName: TAppName, + paramName: AppConfigParameter +) { + return ( + COMMON_CONFIG_PARAMS_NAMES.has(paramName) ? COMMON_APP_NAME : appName + ) as AppConfigType; +} + +type AppName = LiteralUnion; +export async function fetchConfigParameters(appName: AppName) { + console.debug(`Fetching UI configuration params for app : ${appName}`); + const fetchParams = `${getPrefix(1)}/applications/${appName}/parameters`; + return (await backendFetchJson(fetchParams)) as ConfigParameters; +} + +export async function fetchConfigParameter( + currentAppName: string, + paramName: AppConfigParameter +) { + const appName = getAppName(currentAppName, paramName); + console.debug( + `Fetching UI config parameter '${paramName}' for app '${appName}'` + ); + const fetchParams = `${getPrefix( + 1 + )}/applications/${appName}/parameters/${paramName}`; + return (await backendFetchJson(fetchParams)) as ConfigParameter; +} + +export async function updateConfigParameter( + currentAppName: string, + paramName: AppConfigParameter, + value: Parameters[0] +) { + const appName = getAppName(currentAppName, paramName); + console.debug( + `Updating config parameter '${paramName}=${value}' for app '${appName}'` + ); + const updateParams = `${getPrefix( + 1 + )}/applications/${appName}/parameters/${paramName}?value=${encodeURIComponent( + value + )}`; + return ( + await backendFetch(updateParams, { + method: 'PUT', + }) + ).ok; +} diff --git a/src/services/index.ts b/src/services/index.ts index 4aa24597..063e3ba2 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -11,6 +11,12 @@ export type * from './app-local'; export * as AppsMetadataSvc from './apps-metadata'; export type * from './apps-metadata'; +export * as ConfigSvc from './config'; +export type * from './config'; + +export * as ConfigNotificationSvc from './config-notification'; +export type * from './config-notification'; + export * as DirectorySvc from './directory'; export type * from './directory'; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 1484ffe2..98d02c8c 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -16,6 +16,7 @@ import { UrlString } from './utils/api'; interface ImportMetaEnv { readonly VITE_API_GATEWAY: UrlString; readonly VITE_WS_GATEWAY: UrlString; + readonly VITE_DEBUG_REQUESTS?: boolean; } interface ImportMeta { diff --git a/vite.config.mts b/vite.config.mts index 442b2a6e..23f23431 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -78,6 +78,8 @@ export default defineConfig((config) => ({ 'import.meta.env.VITE_API_GATEWAY', 'import.meta.env.VITE_WS_GATEWAY': 'import.meta.env.VITE_WS_GATEWAY', + 'import.meta.env.VITE_DEBUG_REQUESTS': + 'import.meta.env.VITE_DEBUG_REQUESTS', } : undefined, }, From 8e7d9a0caf2fd3662b6a89d73c2a073de77a9ca3 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 23 Jul 2024 16:28:47 +0200 Subject: [PATCH 11/19] more simple use of AdboudDialog to get modules ... for us --- src/components/TopBar/AboutDialog.tsx | 30 ++++++++------------------- src/components/TopBar/modules.ts | 22 ++++++++++++++++++++ src/services/study.ts | 2 +- 3 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 src/components/TopBar/modules.ts diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 4fd28658..2b9e2f2f 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -42,6 +42,8 @@ import { } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; import { LogoText } from './GridLogo'; +import { StudySvc } from '../../services'; +import { GridSuiteModule, ModuleType, moduleTypeSort } from './modules'; const styles = { general: { @@ -116,14 +118,6 @@ function getGlobalVersion( return Promise.resolve(null); } -const moduleTypeSort = { - app: 1, - server: 10, - other: 20, -}; - -type ModuleType = keyof typeof moduleTypeSort; - type ModuleDefinition = { name: string; type: ModuleType }; function compareModules(c1: ModuleDefinition, c2: ModuleDefinition) { @@ -134,13 +128,7 @@ function compareModules(c1: ModuleDefinition, c2: ModuleDefinition) { ); } -export type GridSuiteModule = { - name: string; - type: ModuleType; - version?: string; - gitTag?: string; - // license?: string; -}; +export type { GridSuiteModule } from './modules'; export interface AboutDialogProps { open: boolean; @@ -150,7 +138,7 @@ export interface AboutDialogProps { appVersion?: string; appGitTag?: string; appLicense?: string; - additionalModulesPromise?: () => Promise; + additionalModulesPromise?: string | (() => Promise); logo?: ReactNode; } @@ -302,7 +290,7 @@ function Module({ type, name, version, gitTag }: GridSuiteModule) { ); } -function AboutDialog({ +export default function AboutDialog({ open, onClose, globalVersionPromise, @@ -312,7 +300,7 @@ function AboutDialog({ appLicense, additionalModulesPromise, logo, -}: AboutDialogProps) { +}: Readonly) { const theme = useTheme(); const [isRefreshing, setIsRefreshing] = useState(false); const [loadingGlobalVersion, setLoadingGlobalVersion] = useState(false); @@ -362,7 +350,9 @@ function AboutDialog({ }; (additionalModulesPromise ? Promise.resolve(setLoadingAdditionalModules(true)).then(() => - additionalModulesPromise() + typeof additionalModulesPromise === 'string' + ? StudySvc.getServersInfos(additionalModulesPromise) + : additionalModulesPromise() ) : Promise.reject(new Error('no getter')) ) @@ -574,5 +564,3 @@ function AboutDialog({ ); } - -export default AboutDialog; diff --git a/src/components/TopBar/modules.ts b/src/components/TopBar/modules.ts new file mode 100644 index 00000000..d01f000c --- /dev/null +++ b/src/components/TopBar/modules.ts @@ -0,0 +1,22 @@ +/* + * 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/. + */ + +export const moduleTypeSort = { + app: 1, + server: 10, + other: 20, +}; + +export type ModuleType = keyof typeof moduleTypeSort; + +export type GridSuiteModule = { + name: string; + type: ModuleType; + version?: string; + gitTag?: string; + // license?: string; +}; diff --git a/src/services/study.ts b/src/services/study.ts index 161e7aea..8eaf9e7a 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -7,7 +7,7 @@ import { UUID } from 'crypto'; import { backendFetchJson, getRestBase } from '../utils/api'; -import { GridSuiteModule } from '../components/TopBar/AboutDialog'; +import { GridSuiteModule } from '../components/TopBar/modules'; /** * Return the base API prefix to the study server From a1930f914bbd660be8e6c4af155245b4b52eaada Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 23 Jul 2024 16:54:11 +0200 Subject: [PATCH 12/19] better than casting --- src/services/config.ts | 4 ++-- src/services/directory.ts | 10 +++++----- src/services/explore.ts | 4 ++-- src/services/study.ts | 8 ++++---- src/utils/api/api-rest.ts | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/services/config.ts b/src/services/config.ts index c9a5b5d8..f8ad3e62 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -66,7 +66,7 @@ type AppName = LiteralUnion; export async function fetchConfigParameters(appName: AppName) { console.debug(`Fetching UI configuration params for app : ${appName}`); const fetchParams = `${getPrefix(1)}/applications/${appName}/parameters`; - return (await backendFetchJson(fetchParams)) as ConfigParameters; + return backendFetchJson(fetchParams); } export async function fetchConfigParameter( @@ -80,7 +80,7 @@ export async function fetchConfigParameter( const fetchParams = `${getPrefix( 1 )}/applications/${appName}/parameters/${paramName}`; - return (await backendFetchJson(fetchParams)) as ConfigParameter; + return backendFetchJson(fetchParams); } export async function updateConfigParameter( diff --git a/src/services/directory.ts b/src/services/directory.ts index b818b438..768fd452 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -27,10 +27,10 @@ function getPrefix(vApi: number) { export async function fetchRootFolders(types: string[]) { console.info('Fetching Root Directories'); const urlSearchParams = getRequestParam('elementTypes', types).toString(); - return (await backendFetchJson( + return backendFetchJson( `${getPrefix(1)}/root-directories?${urlSearchParams}`, 'GET' - )) as ElementAttributes[]; + ); } export async function fetchDirectoryContent( @@ -38,13 +38,13 @@ export async function fetchDirectoryContent( types?: string[] ) { console.info("Fetching Folder content '%s'", directoryUuid); - return (await backendFetchJson( + return backendFetchJson( appendSearchParam( `${getPrefix(1)}/directories/${directoryUuid}/elements`, getRequestParam('elementTypes', types) ), 'GET' - )) as ElementAttributes[]; + ); } export async function fetchDirectoryElementPath(elementUuid: UUID) { @@ -52,7 +52,7 @@ export async function fetchDirectoryElementPath(elementUuid: UUID) { const fetchPathUrl = `${getPrefix(1)}/elements/${encodeURIComponent( elementUuid )}/path`; - return (await backendFetchJson(fetchPathUrl, 'GET')) as ElementAttributes[]; + return backendFetchJson(fetchPathUrl, 'GET'); } export async function elementExists( diff --git a/src/services/explore.ts b/src/services/explore.ts index f1df42ff..d8a66570 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -74,11 +74,11 @@ export async function fetchElementsInfos( equipmentTypes: equipmentTypes ?? [], elementTypes: elementTypes ?? [], }); - return (await backendFetchJson( + return backendFetchJson( appendSearchParam( `${getPrefix(1)}/explore/elements/metadata?${urlSearchParams}`, urlSearchParams ), 'GET' - )) as ElementAttributes[]; + ); } diff --git a/src/services/study.ts b/src/services/study.ts index 8eaf9e7a..db70f8c7 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -52,15 +52,15 @@ export type IdentifiableAttributes = { export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { console.info('get filter export on study root node'); - return (await backendFetchJson( + return backendFetchJson( `${getPrefix(1)}/studies/${studyUuid}/filters/${filterUuid}/elements`, 'GET' - )) as IdentifiableAttributes[]; + ); } export async function getServersInfos(viewName: string) { console.info('get backend servers informations'); - return (await backendFetchJson( + return backendFetchJson( `${getPrefix(1)}/servers/about?view=${viewName}` - )) as GridSuiteModule[]; + ); } diff --git a/src/utils/api/api-rest.ts b/src/utils/api/api-rest.ts index 422d2753..307f0cd9 100644 --- a/src/utils/api/api-rest.ts +++ b/src/utils/api/api-rest.ts @@ -128,11 +128,11 @@ export function backendFetch( return safeFetch(url, prepareRequest(init, token)); } -export async function backendFetchJson( +export async function backendFetchJson( url: Url, init?: InitRequest, token?: Token -): Promise { +): Promise { const reqInit = setRequestHeader( init, 'accept', From 8b97ce0c77e5a24a7b940862bdcae0affd478207 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 23 Jul 2024 18:12:57 +0200 Subject: [PATCH 13/19] Migrate common local-storage parameters --- src/components/dialogs/custom-mui-dialog.tsx | 3 +- .../criteria-based-filter-edition-dialog.tsx | 3 +- .../expert/expert-filter-edition-dialog.tsx | 3 +- .../explicit-naming-filter-edition-dialog.tsx | 3 +- .../filter/filter-creation-dialog.tsx | 3 +- .../provider/custom-form-provider.tsx | 5 ++- .../select-inputs/countries-input.tsx | 2 +- .../country-value-editor.tsx | 2 +- src/hooks/localized-countries-hook.ts | 37 +++++-------------- src/index.ts | 8 ++-- src/local-storage/index.ts | 9 +++++ src/local-storage/lang.ts | 32 ++++++++++++++++ src/local-storage/theme.ts | 23 ++++++++++++ src/utils/language.ts | 27 ++++++++++++++ 14 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 src/local-storage/index.ts create mode 100644 src/local-storage/lang.ts create mode 100644 src/local-storage/theme.ts create mode 100644 src/utils/language.ts diff --git a/src/components/dialogs/custom-mui-dialog.tsx b/src/components/dialogs/custom-mui-dialog.tsx index c67a573b..3ddcb1b9 100644 --- a/src/components/dialogs/custom-mui-dialog.tsx +++ b/src/components/dialogs/custom-mui-dialog.tsx @@ -22,6 +22,7 @@ import CancelButton from '../inputs/react-hook-form/utils/cancel-button'; import CustomFormProvider, { MergedFormContextProps, } from '../inputs/react-hook-form/provider/custom-form-provider'; +import { GsLangUser } from '../TopBar/TopBar'; interface ICustomMuiDialog { open: boolean; @@ -36,7 +37,7 @@ interface ICustomMuiDialog { onCancel?: () => void; children: React.ReactNode; isDataFetching?: boolean; - language?: string; + language?: GsLangUser; } const styles = { diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index 353c1b02..6998ce1c 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -28,6 +28,7 @@ import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; import { ExploreSvc } from '../../../services'; import FilterForm from '../filter-form'; +import { GsLangUser } from '../../TopBar/TopBar'; export type SelectionCopy = { sourceItemUuid: UUID | null; @@ -66,7 +67,7 @@ interface CriteriaBasedFilterEditionDialogProps { selection: SelectionCopy ) => Dispatch>; activeDirectory?: UUID; - language?: string; + language?: GsLangUser; } function CriteriaBasedFilterEditionDialog({ diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index f1271fc6..6f9d854a 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -20,6 +20,7 @@ import { saveExpertFilter } from '../utils/filter-api'; import { importExpertRules } from './expert-filter-utils'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; +import { GsLangUser } from '../../TopBar/TopBar'; const formSchema = yup .object() @@ -42,7 +43,7 @@ export interface ExpertFilterEditionDialogProps { getFilterById: (id: string) => Promise<{ [prop: string]: any }>; setSelectionForCopy: (selection: any) => void; activeDirectory?: UUID; - language?: string; + language?: GsLangUser; } function ExpertFilterEditionDialog({ diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index 0bb3b6cc..91f0984d 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -24,6 +24,7 @@ import FilterForm from '../filter-form'; import { noSelectionForCopy } from '../../../utils/equipment-types'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; +import { GsLangUser } from '../../TopBar/TopBar'; const formSchema = yup .object() @@ -46,7 +47,7 @@ interface ExplicitNamingFilterEditionDialogProps { setSelectionForCopy: (selection: any) => void; getFilterById: (id: string) => Promise; activeDirectory?: UUID; - language?: string; + language?: GsLangUser; } function ExplicitNamingFilterEditionDialog({ diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index d39490f5..3a6147dd 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -34,6 +34,7 @@ import { getExpertFilterEmptyFormData, } from './expert/expert-filter-form'; import { FilterType } from './constants/filter-constants'; +import { GsLangUser } from '../TopBar/TopBar'; const emptyFormData = { [FieldConstants.NAME]: '', @@ -65,7 +66,7 @@ export interface FilterCreationDialogProps { open: boolean; onClose: () => void; activeDirectory?: UUID; - language?: string; + language?: GsLangUser; sourceFilterForExplicitNamingConversion?: { id: UUID; equipmentType: string; diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index a4b6beb8..f2785577 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -8,12 +8,13 @@ import React, { createContext, PropsWithChildren } from 'react'; import { FormProvider, UseFormReturn } from 'react-hook-form'; import * as yup from 'yup'; -import { getSystemLanguage } from '../../../../hooks/localized-countries-hook'; +import { getSystemLanguage } from '../../../../utils/language'; +import { GsLangUser } from '../../../TopBar/TopBar'; type CustomFormContextProps = { removeOptional?: boolean; validationSchema: yup.AnySchema; - language?: string; + language?: GsLangUser; }; export type MergedFormContextProps = UseFormReturn & diff --git a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx index 33af576b..fbca295d 100644 --- a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx @@ -7,7 +7,7 @@ import { useCallback } from 'react'; import { Chip } from '@mui/material'; import AutocompleteInput from '../autocomplete-inputs/autocomplete-input'; -import { useLocalizedCountries } from '../../../../hooks/localized-countries-hook'; +import useLocalizedCountries from '../../../../hooks/localized-countries-hook'; import useCustomFormContext from '../provider/use-custom-form-context'; import { Option } from '../../../../utils/types'; diff --git a/src/components/inputs/react-query-builder/country-value-editor.tsx b/src/components/inputs/react-query-builder/country-value-editor.tsx index 768d4331..37b3d93a 100644 --- a/src/components/inputs/react-query-builder/country-value-editor.tsx +++ b/src/components/inputs/react-query-builder/country-value-editor.tsx @@ -11,7 +11,7 @@ import { Autocomplete, TextField } from '@mui/material'; import { useMemo } from 'react'; import useConvertValue from './use-convert-value'; import useValid from './use-valid'; -import { useLocalizedCountries } from '../../../hooks/localized-countries-hook'; +import useLocalizedCountries from '../../../hooks/localized-countries-hook'; import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; function CountryValueEditor(props: ValueEditorProps) { diff --git a/src/hooks/localized-countries-hook.ts b/src/hooks/localized-countries-hook.ts index b3ac6da7..c04c3588 100644 --- a/src/hooks/localized-countries-hook.ts +++ b/src/hooks/localized-countries-hook.ts @@ -7,38 +7,21 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import localizedCountries, { LocalizedCountries } from 'localized-countries'; -// @ts-ignore -import countriesFr from 'localized-countries/data/fr'; -// @ts-ignore -import countriesEn from 'localized-countries/data/en'; -import { - LANG_FRENCH, - LANG_ENGLISH, - LANG_SYSTEM, -} from '../components/TopBar/TopBar'; +import countriesFr from 'localized-countries/data/fr.json'; +import countriesEn from 'localized-countries/data/en.json'; +import { getComputedLanguage } from '../utils/language'; +import { GsLang, LANG_ENGLISH } from '../components/TopBar/TopBar'; -const supportedLanguages = [LANG_FRENCH, LANG_ENGLISH]; - -export const getSystemLanguage = () => { - const systemLanguage = navigator.language.split(/[-_]/)[0]; - return supportedLanguages.includes(systemLanguage) - ? systemLanguage - : LANG_ENGLISH; -}; - -export const getComputedLanguage = (language: string | undefined) => { - return language === LANG_SYSTEM - ? getSystemLanguage() - : language ?? LANG_ENGLISH; -}; - -export const useLocalizedCountries = (language: string | undefined) => { +export default function useLocalizedCountries(language: GsLang | undefined) { const [localizedCountriesModule, setLocalizedCountriesModule] = useState(); // TODO FM this is disgusting, can we make it better ? useEffect(() => { - const lang = getComputedLanguage(language).substring(0, 2); + const lang = getComputedLanguage(language ?? LANG_ENGLISH).substring( + 0, + 2 + ); let localizedCountriesResult; // vite does not support ESM dynamic imports on node_modules, so we have to imports the languages before and do this // https://github.com/vitejs/vite/issues/14102 @@ -72,4 +55,4 @@ export const useLocalizedCountries = (language: string | undefined) => { ); return { translate, countryCodes }; -}; +} diff --git a/src/index.ts b/src/index.ts index 49085dee..b2f1396c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -243,11 +243,7 @@ export { export { default as InputWithPopupConfirmation } from './components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation'; export { default as MuiSelectInput } from './components/inputs/react-hook-form/select-inputs/mui-select-input'; export { default as CountriesInput } from './components/inputs/react-hook-form/select-inputs/countries-input'; -export { - getSystemLanguage, - getComputedLanguage, - useLocalizedCountries, -} from './hooks/localized-countries-hook'; +export { default as useLocalizedCountries } from './hooks/localized-countries-hook'; export { default as MultipleAutocompleteInput } from './components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input'; export { default as CsvUploader } from './components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader'; export { default as UniqueNameInput } from './components/inputs/react-hook-form/unique-name-input'; @@ -271,3 +267,5 @@ export type { EquipmentInfos } from './utils/EquipmentType'; export { getErrorMessage } from './utils/error'; export * from './utils/api'; export * from './services'; +export * from './utils/language'; +export * from './local-storage'; diff --git a/src/local-storage/index.ts b/src/local-storage/index.ts new file mode 100644 index 00000000..60872260 --- /dev/null +++ b/src/local-storage/index.ts @@ -0,0 +1,9 @@ +/* + * 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/. + */ + +export * from './theme'; +export * from './lang'; diff --git a/src/local-storage/lang.ts b/src/local-storage/lang.ts new file mode 100644 index 00000000..c1d0fc95 --- /dev/null +++ b/src/local-storage/lang.ts @@ -0,0 +1,32 @@ +/* + * 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 { GsLang, LANG_SYSTEM } from '../components/TopBar/TopBar'; + +import { getComputedLanguage } from '../utils/language'; + +function getLocalStorageLanguageKey(appName: string) { + return `${appName.toUpperCase()}_LANGUAGE`; +} + +export function getLocalStorageLanguage(appName: string) { + return ( + (localStorage.getItem(getLocalStorageLanguageKey(appName)) as GsLang) || + LANG_SYSTEM + ); +} + +export function saveLocalStorageLanguage( + appName: string, + language: GsLang +): void { + localStorage.setItem(getLocalStorageLanguageKey(appName), language); +} + +export function getLocalStorageComputedLanguage(appName: string) { + return getComputedLanguage(getLocalStorageLanguage(appName)); +} diff --git a/src/local-storage/theme.ts b/src/local-storage/theme.ts new file mode 100644 index 00000000..197b092b --- /dev/null +++ b/src/local-storage/theme.ts @@ -0,0 +1,23 @@ +/* + * 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 { DARK_THEME, GsTheme } from '../components/TopBar/TopBar'; + +function getLocalStorageThemeKey(appName: string) { + return `${appName.toUpperCase()}_THEME`; +} + +export function getLocalStorageTheme(appName: string) { + return ( + (localStorage.getItem(getLocalStorageThemeKey(appName)) as GsTheme) || + DARK_THEME + ); +} + +export function saveLocalStorageTheme(appName: string, theme: GsTheme) { + localStorage.setItem(getLocalStorageThemeKey(appName), theme); +} diff --git a/src/utils/language.ts b/src/utils/language.ts new file mode 100644 index 00000000..623467cb --- /dev/null +++ b/src/utils/language.ts @@ -0,0 +1,27 @@ +/* + * 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 { + GsLang, + GsLangUser, + LANG_ENGLISH, + LANG_FRENCH, + LANG_SYSTEM, +} from '../components/TopBar/TopBar'; + +const supportedLanguages = [LANG_FRENCH, LANG_ENGLISH]; + +export function getSystemLanguage() { + const systemLanguage = navigator.language.split(/[-_]/)[0]; + return supportedLanguages.includes(systemLanguage) + ? (systemLanguage as GsLangUser) + : LANG_ENGLISH; +} + +export function getComputedLanguage(language: GsLang) { + return language === LANG_SYSTEM ? getSystemLanguage() : language; +} From eb1452c177a282ce265a9d722c2c4bc8f09b9009 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 24 Jul 2024 23:04:50 +0200 Subject: [PATCH 14/19] Resolve fetchers namespace problem Found a way to resolve import namespace not extendable while keeping ftechers grouped by service/server, and also managing user token_id problem. --- demo/src/app.jsx | 2 +- .../AuthenticationRouter.tsx | 3 +- .../directory-item-selector.tsx | 53 +++--- src/components/TopBar/AboutDialog.tsx | 4 +- .../dialogs/modify-element-selection.tsx | 10 +- .../criteria-based-filter-edition-dialog.tsx | 11 +- .../explicit-naming-filter-form.tsx | 11 +- src/components/filter/utils/filter-api.ts | 95 ++++++----- .../react-hook-form/directory-items-input.tsx | 10 +- .../react-hook-form/unique-name-input.tsx | 5 +- .../element-value-editor.tsx | 10 +- src/hooks/predefined-properties-hook.ts | 4 +- src/index.ts | 6 +- src/redux/commonStore.ts | 31 ---- src/services/app-local.ts | 14 +- src/services/apps-metadata.ts | 86 ++++++---- src/services/base-service.ts | 159 ++++++++++++++++++ src/services/config-notification.ts | 54 +++--- src/services/config.ts | 100 +++++------ src/services/directory.ts | 108 ++++++------ src/services/explore.ts | 131 +++++++-------- src/services/index.ts | 44 +++-- src/services/instances.ts | 63 +++++++ src/services/study.ts | 48 +++--- .../authActions.ts => utils/AuthActions.ts} | 4 +- src/utils/AuthService.ts | 2 +- src/utils/api/api-rest.ts | 72 +------- src/utils/api/api-websocket.ts | 23 --- src/utils/api/index.ts | 14 +- src/utils/api/utils.ts | 6 - src/utils/api/variables.ts | 29 ---- 31 files changed, 660 insertions(+), 552 deletions(-) delete mode 100644 src/redux/commonStore.ts create mode 100644 src/services/base-service.ts create mode 100644 src/services/instances.ts rename src/{redux/authActions.ts => utils/AuthActions.ts} (97%) delete mode 100644 src/utils/api/api-websocket.ts delete mode 100644 src/utils/api/variables.ts diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 6ad017b2..ba56734b 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -95,7 +95,7 @@ import searchEquipments from '../data/EquipmentSearchBar'; import { EquipmentItem } from '../../src/components/ElementSearchDialog/equipment-item'; import OverflowableText from '../../src/components/OverflowableText'; -import { setShowAuthenticationRouterLogin } from '../../src/redux/authActions'; +import { setShowAuthenticationRouterLogin } from '../../src/utils/AuthActions'; import TableTab from './TableTab'; import FlatParametersTab from './FlatParametersTab'; diff --git a/src/components/AuthenticationRouter/AuthenticationRouter.tsx b/src/components/AuthenticationRouter/AuthenticationRouter.tsx index 2772b73d..befaf3a8 100644 --- a/src/components/AuthenticationRouter/AuthenticationRouter.tsx +++ b/src/components/AuthenticationRouter/AuthenticationRouter.tsx @@ -17,6 +17,7 @@ import { Alert, AlertTitle, Grid } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { UserManager } from 'oidc-client'; import SignInCallbackHandler from '../SignInCallbackHandler'; +import { AuthenticationActions } from '../../utils/AuthActions'; import { handleSigninCallback, handleSilentRenewCallback, @@ -27,8 +28,6 @@ import SilentRenewCallbackHandler from '../SilentRenewCallbackHandler'; import Login from '../Login'; import Logout from '../Login/Logout'; -import { AuthenticationActions } from '../../redux/authActions'; - export type AuthenticationRouterErrorState = { userName?: string; userValidationError?: { error: Error }; diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index 59bbea61..460146a3 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -15,7 +15,7 @@ import TreeViewFinder, { TreeViewFinderProps, } from '../TreeViewFinder/TreeViewFinder'; import { useSnackMessage } from '../../hooks/useSnackMessage'; -import { DirectorySvc, ExploreSvc } from '../../services'; +import { directorySvc, exploreSvc } from '../../services/instances'; const styles = { icon: (theme: Theme) => ({ @@ -265,7 +265,8 @@ function DirectoryItemSelector({ ); const updateRootDirectories = useCallback(() => { - DirectorySvc.fetchRootFolders(types) + directorySvc + .fetchRootFolders(types) .then((newData) => { const [nrs, mdr] = updatedTree( rootsRef.current, @@ -288,7 +289,8 @@ function DirectoryItemSelector({ const fetchDirectory = useCallback( (nodeId: UUID): void => { const typeList = types.includes(ElementType.DIRECTORY) ? [] : types; - DirectorySvc.fetchDirectoryContent(nodeId, typeList) + directorySvc + .fetchDirectoryContent(nodeId, typeList) .then((children) => { const childrenMatchedTypes = children.filter((item: any) => contentFilter().has(item.type) @@ -299,24 +301,33 @@ function DirectoryItemSelector({ equipmentTypes && equipmentTypes.length > 0 ) { - ExploreSvc.fetchElementsInfos( - childrenMatchedTypes.map((e: any) => e.elementUuid), - types, - equipmentTypes - ).then((childrenWithMetadata) => { - const filtredChildren = itemFilter - ? childrenWithMetadata.filter((val: any) => { - // Accept every directory - if (val.type === ElementType.DIRECTORY) { - return true; - } - // otherwise filter with the custom itemFilter func - return itemFilter(val); - }) - : childrenWithMetadata; - // update directory content - addToDirectory(nodeId, filtredChildren); - }); + exploreSvc + .fetchElementsInfos( + childrenMatchedTypes.map( + (e: any) => e.elementUuid + ), + types, + equipmentTypes + ) + .then((childrenWithMetadata) => { + const filtredChildren = itemFilter + ? childrenWithMetadata.filter( + (val: any) => { + // Accept every directory + if ( + val.type === + ElementType.DIRECTORY + ) { + return true; + } + // otherwise filter with the custom itemFilter func + return itemFilter(val); + } + ) + : childrenWithMetadata; + // update directory content + addToDirectory(nodeId, filtredChildren); + }); } else { // update directory content addToDirectory(nodeId, childrenMatchedTypes); diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 2b9e2f2f..c4ecebae 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -42,7 +42,7 @@ import { } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; import { LogoText } from './GridLogo'; -import { StudySvc } from '../../services'; +import { studySvc } from '../../services/instances'; import { GridSuiteModule, ModuleType, moduleTypeSort } from './modules'; const styles = { @@ -351,7 +351,7 @@ export default function AboutDialog({ (additionalModulesPromise ? Promise.resolve(setLoadingAdditionalModules(true)).then(() => typeof additionalModulesPromise === 'string' - ? StudySvc.getServersInfos(additionalModulesPromise) + ? studySvc.getServersInfos(additionalModulesPromise) : additionalModulesPromise() ) : Promise.reject(new Error('no getter')) diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index 06287064..6d72a4a8 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -14,7 +14,7 @@ import { TreeViewFinderNodeProps } from '../TreeViewFinder'; import FieldConstants from '../../utils/field-constants'; import DirectoryItemSelector from '../DirectoryItemSelector/directory-item-selector'; import { ElementType } from '../../utils/ElementType'; -import { DirectorySvc } from '../../services'; +import { directorySvc } from '../../services/instances'; export interface ModifyElementSelectionProps { elementType: ElementType; @@ -46,15 +46,15 @@ function ModifyElementSelection(props: ModifyElementSelectionProps) { useEffect(() => { if (directory) { - DirectorySvc.fetchDirectoryElementPath(directory).then( - (res: any) => { + directorySvc + .fetchDirectoryElementPath(directory) + .then((res: any) => { setActiveDirectoryName( res .map((element: any) => element.elementName.trim()) .join('/') ); - } - ); + }); } }, [directory]); diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index 6998ce1c..6f361580 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -26,7 +26,7 @@ import { criteriaBasedFilterSchema } from './criteria-based-filter-form'; import yup from '../../../utils/yup-config'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { ExploreSvc } from '../../../services'; +import { exploreSvc } from '../../../services/instances'; import FilterForm from '../filter-form'; import { GsLangUser } from '../../TopBar/TopBar'; @@ -125,10 +125,11 @@ function CriteriaBasedFilterEditionDialog({ const onSubmit = useCallback( (filterForm: any) => { - ExploreSvc.saveFilter( - frontToBackTweak(id, filterForm), - filterForm[FieldConstants.NAME] - ) + exploreSvc + .saveFilter( + frontToBackTweak(id, filterForm), + filterForm[FieldConstants.NAME] + ) .then(() => { if (selectionForCopy.sourceItemUuid === id) { setSelelectionForCopy(noSelectionForCopy); diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index b06926f9..1fa00847 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -26,7 +26,7 @@ import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { ElementType } from '../../../utils/ElementType'; import ModifyElementSelection from '../../dialogs/modify-element-selection'; -import { StudySvc } from '../../../services'; +import { studySvc } from '../../../services/instances'; import { EquipmentType } from '../../../utils/EquipmentType'; export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; @@ -226,10 +226,11 @@ function ExplicitNamingFilterForm({ }; const onStudySelected = (studyUuid: UUID) => { - StudySvc.exportFilter( - studyUuid, - sourceFilterForExplicitNamingConversion?.id - ) + studySvc + .exportFilter( + studyUuid, + sourceFilterForExplicitNamingConversion?.id + ) .then((matchingEquipments: any) => { setValue( FILTER_EQUIPMENTS_ATTRIBUTES, diff --git a/src/components/filter/utils/filter-api.ts b/src/components/filter/utils/filter-api.ts index 966eac23..f6b81977 100644 --- a/src/components/filter/utils/filter-api.ts +++ b/src/components/filter/utils/filter-api.ts @@ -11,7 +11,7 @@ import { frontToBackTweak } from '../criteria-based/criteria-based-filter-utils' import { Generator, Load } from '../../../utils/equipment-types'; import { exportExpertRules } from '../expert/expert-filter-utils'; import { DISTRIBUTION_KEY, FilterType } from '../constants/filter-constants'; -import { ExploreSvc } from '../../../services'; +import { exploreSvc } from '../../../services/instances'; export const saveExplicitNamingFilter = ( tableValues: any[], @@ -39,16 +39,17 @@ export const saveExplicitNamingFilter = ( })); } if (isFilterCreation) { - ExploreSvc.createFilter( - { - type: FilterType.EXPLICIT_NAMING.id, - equipmentType, - filterEquipmentsAttributes: cleanedTableValues, - }, - name, - description, - activeDirectory - ) + exploreSvc + .createFilter( + { + type: FilterType.EXPLICIT_NAMING.id, + equipmentType, + filterEquipmentsAttributes: cleanedTableValues, + }, + name, + description, + activeDirectory + ) .then(() => { handleClose(); }) @@ -56,15 +57,16 @@ export const saveExplicitNamingFilter = ( setCreateFilterErr(error.message); }); } else { - ExploreSvc.saveFilter( - { - id, - type: FilterType.EXPLICIT_NAMING.id, - equipmentType, - filterEquipmentsAttributes: cleanedTableValues, - }, - name - ) + exploreSvc + .saveFilter( + { + id, + type: FilterType.EXPLICIT_NAMING.id, + equipmentType, + filterEquipmentsAttributes: cleanedTableValues, + }, + name + ) .then(() => { handleClose(); }) @@ -81,12 +83,13 @@ export const saveCriteriaBasedFilter = ( onError: (message: string) => void ) => { const filterForBack = frontToBackTweak(undefined, filter); // no need ID for creation - ExploreSvc.createFilter( - filterForBack, - filter[FieldConstants.NAME], - filter[FieldConstants.DESCRIPTION], - activeDirectory - ) + exploreSvc + .createFilter( + filterForBack, + filter[FieldConstants.NAME], + filter[FieldConstants.DESCRIPTION], + activeDirectory + ) .then(() => { onClose(); }) @@ -107,16 +110,17 @@ export const saveExpertFilter = ( onError: (message: string) => void ) => { if (isFilterCreation) { - ExploreSvc.createFilter( - { - type: FilterType.EXPERT.id, - equipmentType, - rules: exportExpertRules(query), - }, - name, - description, - activeDirectory - ) + exploreSvc + .createFilter( + { + type: FilterType.EXPERT.id, + equipmentType, + rules: exportExpertRules(query), + }, + name, + description, + activeDirectory + ) .then(() => { onClose(); }) @@ -124,15 +128,16 @@ export const saveExpertFilter = ( onError(error.message); }); } else { - ExploreSvc.saveFilter( - { - id, - type: FilterType.EXPERT.id, - equipmentType, - rules: exportExpertRules(query), - }, - name - ) + exploreSvc + .saveFilter( + { + id, + type: FilterType.EXPERT.id, + equipmentType, + rules: exportExpertRules(query), + }, + name + ) .then(() => { onClose(); }) diff --git a/src/components/inputs/react-hook-form/directory-items-input.tsx b/src/components/inputs/react-hook-form/directory-items-input.tsx index 821231d4..f78c4810 100644 --- a/src/components/inputs/react-hook-form/directory-items-input.tsx +++ b/src/components/inputs/react-hook-form/directory-items-input.tsx @@ -29,7 +29,7 @@ import { mergeSx } from '../../../utils/styles'; import OverflowableText from '../../OverflowableText'; import MidFormError from './error-management/mid-form-error'; import DirectoryItemSelector from '../../DirectoryItemSelector/directory-item-selector'; -import { DirectorySvc } from '../../../services'; +import { directorySvc } from '../../../services/instances'; export const NAME = 'name'; @@ -176,8 +176,9 @@ function DirectoryItemsInput({ const chips = getValues(name) as any[]; const chip = chips.at(index)?.id; if (chip) { - DirectorySvc.fetchDirectoryElementPath(chip).then( - (response: any[]) => { + directorySvc + .fetchDirectoryElementPath(chip) + .then((response: any[]) => { const path = response .filter((e) => e.elementUuid !== chip) .map((e) => e.elementUuid); @@ -186,8 +187,7 @@ function DirectoryItemsInput({ setSelected([chip]); setDirectoryItemSelectorOpen(true); setMultiSelect(false); - } - ); + }); } }, [getValues, name] diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index dc6b91b6..7fe94f33 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -16,7 +16,7 @@ import { UUID } from 'crypto'; import useDebounce from '../../../hooks/useDebounce'; import FieldConstants from '../../../utils/field-constants'; import { ElementType } from '../../../utils/ElementType'; -import { DirectorySvc } from '../../../services'; +import { directorySvc } from '../../../services/instances'; interface UniqueNameInputProps { name: string; @@ -76,7 +76,8 @@ function UniqueNameInput({ const handleCheckName = useCallback( (nameValue: string) => { if (nameValue) { - DirectorySvc.elementExists(directory, nameValue, elementType) + directorySvc + .elementExists(directory, nameValue, elementType) .then((alreadyExist) => { if (alreadyExist) { setError(name, { diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index 904d7285..0bca4ddd 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -8,7 +8,7 @@ import { validate as uuidValidate } from 'uuid'; import { useEffect } from 'react'; import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; -import { ExploreSvc } from '../../../services'; +import { exploreSvc } from '../../../services/instances'; import DirectoryItemsInput from '../react-hook-form/directory-items-input'; interface ElementValueEditorProps { @@ -43,8 +43,9 @@ function ElementValueEditor(props: ElementValueEditorProps) { defaultValue[0].length > 0 && uuidValidate(defaultValue[0]) ) { - ExploreSvc.fetchElementsInfos(defaultValue).then( - (childrenWithMetadata) => { + exploreSvc + .fetchElementsInfos(defaultValue) + .then((childrenWithMetadata) => { setValue( name, childrenWithMetadata.map((v: any) => { @@ -55,8 +56,7 @@ function ElementValueEditor(props: ElementValueEditorProps) { }; }) ); - } - ); + }); } }, [name, defaultValue, elementType, setValue]); diff --git a/src/hooks/predefined-properties-hook.ts b/src/hooks/predefined-properties-hook.ts index eb950c47..5a4bf265 100644 --- a/src/hooks/predefined-properties-hook.ts +++ b/src/hooks/predefined-properties-hook.ts @@ -8,7 +8,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import mapEquipmentTypeForPredefinedProperties from '../utils/equipment-types-for-predefined-properties-mapper'; import { useSnackMessage } from './useSnackMessage'; import { EquipmentType, PredefinedProperties } from '../utils/types'; -import { AppsMetadataSvc } from '../services'; +import { appsMetadataSvc } from '../services/instances'; const fetchPredefinedProperties = async ( equipmentType: EquipmentType @@ -18,7 +18,7 @@ const fetchPredefinedProperties = async ( if (networkEquipmentType === undefined) { return Promise.resolve(undefined); } - const studyMetadata = await AppsMetadataSvc.fetchStudyMetadata(); + const studyMetadata = await appsMetadataSvc.fetchStudyMetadata(); return ( studyMetadata.predefinedEquipmentProperties?.[networkEquipmentType] ?? undefined diff --git a/src/index.ts b/src/index.ts index a228e204..38a3d638 100644 --- a/src/index.ts +++ b/src/index.ts @@ -133,7 +133,7 @@ export { USER_VALIDATION_ERROR, RESET_AUTHENTICATION_ROUTER_ERROR, SHOW_AUTH_INFO_LOGIN, -} from './redux/authActions'; +} from './utils/AuthActions'; export type { AuthenticationActions, AuthenticationRouterErrorBase, @@ -144,7 +144,7 @@ export type { UnauthorizedUserAction, UserAction, UserValidationErrorAction, -} from './redux/authActions'; +} from './utils/AuthActions'; export { default as report_viewer_en } from './components/translations/report-viewer-en'; export { default as report_viewer_fr } from './components/translations/report-viewer-fr'; export { default as login_en } from './components/translations/login-en'; @@ -261,8 +261,6 @@ export { } from './components/filter/criteria-based/criteria-based-filter-utils'; export { mergeSx } from './utils/styles'; -export { setCommonStore } from './redux/commonStore'; -export type { CommonStoreState } from './redux/commonStore'; export type { EquipmentInfos } from './utils/EquipmentType'; export { getErrorMessage } from './utils/error'; diff --git a/src/redux/commonStore.ts b/src/redux/commonStore.ts deleted file mode 100644 index 907f001e..00000000 --- a/src/redux/commonStore.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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 { User } from 'oidc-client'; - -export type CommonStoreState = { - user: User | null; -}; - -interface CommonStore { - getState(): CommonStoreState; -} - -let commonStore: CommonStore | undefined; - -/** - * Set a copy of the reference to the store to be able to access it from this library. - * It's useful to get access to the user token outside of the React context in API files. - * NB : temporary solution before refactoring the token management in the whole gridsuite stack. - */ -export function setCommonStore(store: CommonStore): void { - commonStore = store; -} - -export function getUser() { - return commonStore?.getState().user ?? undefined; -} diff --git a/src/services/app-local.ts b/src/services/app-local.ts index 61a8f44c..18aad0a2 100644 --- a/src/services/app-local.ts +++ b/src/services/app-local.ts @@ -18,10 +18,14 @@ export type Env = { // [key: string]: string; }; -export async function fetchEnv(): Promise { - return (await fetch('env.json')).json(); -} +export default class AppLocalComSvc { + // eslint-disable-next-line class-methods-use-this + public async fetchEnv(): Promise { + return (await fetch('env.json')).json(); + } -export async function fetchIdpSettings() { - return (await (await fetch('idpSettings.json')).json()) as IdpSettings; + // eslint-disable-next-line class-methods-use-this + public async fetchIdpSettings() { + return (await (await fetch('idpSettings.json')).json()) as IdpSettings; + } } diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index a628c55e..6fdb1327 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * 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/. @@ -7,7 +7,7 @@ import { PredefinedProperties } from '../utils/types'; import { Url } from '../utils/api'; -import { fetchEnv } from './app-local'; +import AppLocalComSvc from './app-local'; // https://github.com/gridsuite/deployment/blob/main/docker-compose/version.json // https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/version.json @@ -52,44 +52,58 @@ export type AppMetadataStudy = AppMetadataCommon & { defaultCountry?: string; }; -export async function fetchAppsMetadata(): Promise { - console.info(`Fetching apps and urls...`); - const env = await fetchEnv(); - const res = await fetch(`${env.appsMetadataServerUrl}/apps-metadata.json`); - return res.json(); -} - -const isStudyMetadata = ( +function isStudyMetadata( metadata: AppMetadataCommon -): metadata is AppMetadataStudy => { +): metadata is AppMetadataStudy { return metadata.name === 'Study'; -}; +} -export async function fetchStudyMetadata(): Promise { - console.info(`Fetching study metadata...`); - const studyMetadata = (await fetchAppsMetadata()).filter(isStudyMetadata); - if (!studyMetadata) { - throw new Error('Study entry could not be found in metadata'); - } else { - return studyMetadata[0]; // There should be only one study metadata +export default class AppsMetadataComSvc { + private readonly appLocalSvc: AppLocalComSvc; + + public constructor(appLocalSvc?: AppLocalComSvc) { + this.appLocalSvc = appLocalSvc ?? new AppLocalComSvc(); } -} -export async function fetchDefaultParametersValues(): Promise< - AppMetadataStudy['defaultParametersValues'] -> { - console.debug('fetching default parameters values from apps-metadata file'); - return (await fetchStudyMetadata()).defaultParametersValues; -} + public async fetchAppsMetadata(): Promise { + console.info(`Fetching apps and urls...`); + const env = await this.appLocalSvc.fetchEnv(); + const res = await fetch( + `${env.appsMetadataServerUrl}/apps-metadata.json` + ); + return res.json(); + } -export async function fetchVersion(): Promise { - console.debug('Fetching global version...'); - const envData = await fetchEnv(); - return ( - await fetch(`${envData.appsMetadataServerUrl}/version.json`) - ).json(); -} + public async fetchStudyMetadata(): Promise { + console.info(`Fetching study metadata...`); + const studyMetadata = (await this.fetchAppsMetadata()).filter( + isStudyMetadata + ); + if (!studyMetadata) { + throw new Error('Study entry could not be found in metadata'); + } else { + return studyMetadata[0]; // There should be only one study metadata + } + } + + public async fetchDefaultParametersValues(): Promise< + AppMetadataStudy['defaultParametersValues'] + > { + console.debug( + 'fetching default parameters values from apps-metadata file' + ); + return (await this.fetchStudyMetadata()).defaultParametersValues; + } -export async function fetchDeployedVersion() { - return (await fetchVersion())?.deployVersion; + public async fetchVersion(): Promise { + console.debug('Fetching global version...'); + const envData = await this.appLocalSvc.fetchEnv(); + return ( + await fetch(`${envData.appsMetadataServerUrl}/version.json`) + ).json(); + } + + public async fetchDeployedVersion() { + return (await this.fetchVersion())?.deployVersion; + } } diff --git a/src/services/base-service.ts b/src/services/base-service.ts new file mode 100644 index 00000000..8f7acf6e --- /dev/null +++ b/src/services/base-service.ts @@ -0,0 +1,159 @@ +/* + * 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/. + */ + +/* eslint-disable max-classes-per-file */ + +import { User } from 'oidc-client'; +import { + HttpContentType, + InitRequest, + setRequestHeader, + Token, + Url, + UrlString, +} from '../utils/api'; +import { safeFetch } from '../utils/api/api-rest'; + +/* The first problem we have here is that some front apps don't use Vite, and by consequence using VITE_* vars don't work... + * What we do here is to try to use these variables as default, while permitting devs to overwrite these constants. + * + * The second problem is to how to keep organized the fetcher by service while letting devs add to others in apps. + * Using named export was try but isn't extendable (some sort of "import namespace"). + * + * The third problem is how to manage the user token ID that comes from the app's side for now. + * We can't use React context, and using a pseudo store copy isn't a satisfying solution. + */ + +export type UserGetter = () => User | undefined; + +/* Note: some utilities functions are moved in the class as it's a dependant of runtime data + * Note: the baseUrlPrefix isn't in the base because websocket services haven't a version in url + */ +abstract class BaseService { + private readonly getUser: UserGetter; + + protected constructor(userGetter: UserGetter) { + this.getUser = userGetter; + } + + protected getUserToken() { + return this.getUser()?.id_token; + } +} + +export abstract class WsService extends BaseService { + protected readonly queryPrefix: string; + + protected constructor( + userGetter: UserGetter, + service: string, + wsGatewayPath: UrlString = import.meta.env.VITE_WS_GATEWAY + ) { + super(userGetter); + this.queryPrefix = `${WsService.getWsBase(wsGatewayPath)}/${service}`; + } + + private static getWsBase(wsGatewayPath: string): string { + // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx + return ( + document.baseURI + .replace(/^http(s?):\/\//, 'ws$1://') + .replace(/\/+$/, '') + wsGatewayPath + ); + } + + protected getUrlWithToken(baseUrl: string) { + const querySymbol = baseUrl.includes('?') ? '&' : '?'; + return `${baseUrl}${querySymbol}access_token=${this.getUserToken()}`; + } +} + +export abstract class ApiService extends BaseService { + private readonly basePrefix: string; + + protected constructor( + userGetter: UserGetter, + service: string, + restGatewayPath: UrlString = import.meta.env.VITE_API_GATEWAY + ) { + super(userGetter); + this.basePrefix = `${ApiService.getRestBase( + restGatewayPath + )}/${service}`; + } + + private static getRestBase(restGatewayPath: string): string { + // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx + return document.baseURI.replace(/\/+$/, '') + restGatewayPath; + } + + /** + * Return the base API prefix to the server + * @param vApi the version of api to use + */ + protected getPrefix(vApi: number) { + return `${this.basePrefix}/config/v${vApi}`; + } + + private prepareRequest(init?: InitRequest, token?: Token): RequestInit { + if ( + !( + typeof init === 'undefined' || + typeof init === 'string' || + typeof init === 'object' + ) + ) { + throw new TypeError( + `First argument of prepareRequest is not an object: ${typeof init}` + ); + } + const initCopy: RequestInit = + typeof init === 'string' ? { method: init } : { ...(init ?? {}) }; + initCopy.headers = new Headers(initCopy.headers || {}); + const tokenCopy = token || this.getUserToken(); + initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); + return initCopy; + } + + protected backendFetch(url: Url, init?: InitRequest, token?: Token) { + return safeFetch(url, this.prepareRequest(init, token)); + } + + protected async backendFetchJson( + url: Url, + init?: InitRequest, + token?: Token + ): Promise { + const reqInit = setRequestHeader( + init, + 'accept', + HttpContentType.APPLICATION_JSON + ); + return (await this.backendFetch(url, reqInit, token)).json(); + } + + protected async backendFetchText( + url: Url, + init?: InitRequest, + token?: Token + ) { + const reqInit = setRequestHeader( + init, + 'accept', + HttpContentType.TEXT_PLAIN + ); + return (await this.backendFetch(url, reqInit, token)).text(); + } + + protected async backendFetchFile( + url: Url, + init?: InitRequest, + token?: Token + ) { + return (await this.backendFetch(url, init, token)).blob(); + } +} diff --git a/src/services/config-notification.ts b/src/services/config-notification.ts index c6e92387..55d8ba19 100644 --- a/src/services/config-notification.ts +++ b/src/services/config-notification.ts @@ -1,40 +1,36 @@ /* - * Copyright (c) 2024, RTE (http://www.rte-france.com) + * 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/. */ -/* eslint-disable import/prefer-default-export */ - import ReconnectingWebSocket, { Event } from 'reconnecting-websocket'; -import { getUrlWithToken, getWsBase } from '../utils/api'; +import { UrlString } from '../utils/api'; +import { UserGetter, WsService } from './base-service'; -/** - * Return the base API prefix to the config server - *
Note: cannot be a const because part of the prefix can be overridden at runtime - * @param vApi the version of api to use - */ -function getPrefix() { - return `${getWsBase()}/config-notification`; -} +export default class ConfigNotificationComSvc extends WsService { + public constructor(userGetter: UserGetter, wsGatewayPath?: UrlString) { + super(userGetter, 'config-notification', wsGatewayPath); + } -export function connectNotificationsWsUpdateConfig( - appName: string -): ReconnectingWebSocket { - const webSocketUrl = `${getPrefix()}/notify?appName=${appName}`; - const reconnectingWebSocket = new ReconnectingWebSocket( - () => getUrlWithToken(webSocketUrl), - undefined, - { debug: `${import.meta.env.VITE_DEBUG_REQUESTS}` === 'true' } - ); - reconnectingWebSocket.onopen = (event: Event) => { - console.groupCollapsed( - `Connected Websocket update config ui: ${appName}` + public connectNotificationsWsUpdateConfig( + appName: string + ): ReconnectingWebSocket { + const webSocketUrl = `${this.queryPrefix}/notify?appName=${appName}`; + const reconnectingWebSocket = new ReconnectingWebSocket( + () => this.getUrlWithToken(webSocketUrl), + undefined, + { debug: `${import.meta.env.VITE_DEBUG_REQUESTS}` === 'true' } ); - console.debug(`Websocket URL: ${webSocketUrl}`); - console.dir(event); - console.groupEnd(); - }; - return reconnectingWebSocket; + reconnectingWebSocket.onopen = (event: Event) => { + console.groupCollapsed( + `Connected Websocket update config ui: ${appName}` + ); + console.debug(`Websocket URL: ${webSocketUrl}`); + console.dir(event); + console.groupEnd(); + }; + return reconnectingWebSocket; + } } diff --git a/src/services/config.ts b/src/services/config.ts index f8ad3e62..49757842 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -4,20 +4,11 @@ * 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/. */ -/* eslint-disable import/prefer-default-export */ import { GsLang, GsTheme } from '@gridsuite/commons-ui'; import { LiteralUnion } from 'type-fest'; -import { backendFetch, backendFetchJson, getRestBase } from '../utils/api'; - -/** - * Return the base API prefix to the config server - *
Note: cannot be a const because part of the prefix can be overridden at runtime - * @param vApi the version of api to use - */ -function getPrefix(vApi: number) { - return `${getRestBase()}/config/v${vApi}`; -} +import { UrlString } from '../utils/api'; +import { ApiService, UserGetter } from './base-service'; export const COMMON_APP_NAME = 'common'; @@ -43,6 +34,7 @@ type AppConfigParameter = LiteralUnion< string >; +// TODO: how to test it's a fixed value and not any string? type AppConfigType = TAppName extends string ? typeof COMMON_APP_NAME | TAppName @@ -62,44 +54,54 @@ function getAppName( ) as AppConfigType; } -type AppName = LiteralUnion; -export async function fetchConfigParameters(appName: AppName) { - console.debug(`Fetching UI configuration params for app : ${appName}`); - const fetchParams = `${getPrefix(1)}/applications/${appName}/parameters`; - return backendFetchJson(fetchParams); -} +export default class ConfigComSvc extends ApiService { + private readonly appName: TAppName; -export async function fetchConfigParameter( - currentAppName: string, - paramName: AppConfigParameter -) { - const appName = getAppName(currentAppName, paramName); - console.debug( - `Fetching UI config parameter '${paramName}' for app '${appName}'` - ); - const fetchParams = `${getPrefix( - 1 - )}/applications/${appName}/parameters/${paramName}`; - return backendFetchJson(fetchParams); -} + public constructor( + appName: TAppName, + userGetter: UserGetter, + restGatewayPath?: UrlString + ) { + super(userGetter, 'config', restGatewayPath); + this.appName = appName; + } -export async function updateConfigParameter( - currentAppName: string, - paramName: AppConfigParameter, - value: Parameters[0] -) { - const appName = getAppName(currentAppName, paramName); - console.debug( - `Updating config parameter '${paramName}=${value}' for app '${appName}'` - ); - const updateParams = `${getPrefix( - 1 - )}/applications/${appName}/parameters/${paramName}?value=${encodeURIComponent( - value - )}`; - return ( - await backendFetch(updateParams, { - method: 'PUT', - }) - ).ok; + public async fetchConfigParameters(appName: AppConfigType) { + console.debug(`Fetching UI configuration params for app : ${appName}`); + const fetchParams = `${this.getPrefix( + 1 + )}/applications/${appName}/parameters`; + return this.backendFetchJson(fetchParams); + } + + public async fetchConfigParameter(paramName: AppConfigParameter) { + const appName = getAppName(this.appName, paramName); + console.debug( + `Fetching UI config parameter '${paramName}' for app '${appName}'` + ); + const fetchParams = `${this.getPrefix( + 1 + )}/applications/${appName}/parameters/${paramName}`; + return this.backendFetchJson(fetchParams); + } + + public async updateConfigParameter( + paramName: AppConfigParameter, + value: Parameters[0] + ) { + const appName = getAppName(this.appName, paramName); + console.debug( + `Updating config parameter '${paramName}=${value}' for app '${appName}'` + ); + const updateParams = `${this.getPrefix( + 1 + )}/applications/${appName}/parameters/${paramName}?value=${encodeURIComponent( + value + )}`; + return ( + await this.backendFetch(updateParams, { + method: 'PUT', + }) + ).ok; + } } diff --git a/src/services/directory.ts b/src/services/directory.ts index 768fd452..c9b8d839 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -1,70 +1,64 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * 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 { UUID } from 'crypto'; -import { - appendSearchParam, - backendFetch, - backendFetchJson, - getRequestParam, - getRestBase, -} from '../utils/api'; +import { appendSearchParam, getRequestParam, UrlString } from '../utils/api'; import { ElementAttributes } from '../utils/types'; +import { ApiService, UserGetter } from './base-service'; -/** - * Return the base API prefix to the directory server - *
Note: cannot be a const because part of the prefix can be overridden at runtime - * @param vApi the version of api to use - */ -function getPrefix(vApi: number) { - return `${getRestBase()}/directory/v${vApi}`; -} +export default class DirectoryComSvc extends ApiService { + public constructor(userGetter: UserGetter, restGatewayPath?: UrlString) { + super(userGetter, 'directory', restGatewayPath); + } -export async function fetchRootFolders(types: string[]) { - console.info('Fetching Root Directories'); - const urlSearchParams = getRequestParam('elementTypes', types).toString(); - return backendFetchJson( - `${getPrefix(1)}/root-directories?${urlSearchParams}`, - 'GET' - ); -} + public async fetchRootFolders(types: string[]) { + console.info('Fetching Root Directories'); + const urlSearchParams = getRequestParam( + 'elementTypes', + types + ).toString(); + return this.backendFetchJson( + `${this.getPrefix(1)}/root-directories?${urlSearchParams}`, + 'GET' + ); + } -export async function fetchDirectoryContent( - directoryUuid: UUID, - types?: string[] -) { - console.info("Fetching Folder content '%s'", directoryUuid); - return backendFetchJson( - appendSearchParam( - `${getPrefix(1)}/directories/${directoryUuid}/elements`, - getRequestParam('elementTypes', types) - ), - 'GET' - ); -} + public async fetchDirectoryContent(directoryUuid: UUID, types?: string[]) { + console.info("Fetching Folder content '%s'", directoryUuid); + return this.backendFetchJson( + appendSearchParam( + `${this.getPrefix(1)}/directories/${directoryUuid}/elements`, + getRequestParam('elementTypes', types) + ), + 'GET' + ); + } -export async function fetchDirectoryElementPath(elementUuid: UUID) { - console.info(`Fetching element '${elementUuid}' and its parents info ...`); - const fetchPathUrl = `${getPrefix(1)}/elements/${encodeURIComponent( - elementUuid - )}/path`; - return backendFetchJson(fetchPathUrl, 'GET'); -} - -export async function elementExists( - directoryUuid: UUID, - elementName: string, - type: string -) { - const response = await backendFetch( - `${getPrefix( + public async fetchDirectoryElementPath(elementUuid: UUID) { + console.info( + `Fetching element '${elementUuid}' and its parents info ...` + ); + const fetchPathUrl = `${this.getPrefix( 1 - )}/directories/${directoryUuid}/elements/${elementName}/types/${type}`, - 'HEAD' - ); - return response.status !== 204; // HTTP 204 : No-content + )}/elements/${encodeURIComponent(elementUuid)}/path`; + return this.backendFetchJson(fetchPathUrl, 'GET'); + } + + public async elementExists( + directoryUuid: UUID, + elementName: string, + type: string + ) { + const response = await this.backendFetch( + `${this.getPrefix( + 1 + )}/directories/${directoryUuid}/elements/${elementName}/types/${type}`, + 'HEAD' + ); + return response.status !== 204; // HTTP 204 : No-content + } } diff --git a/src/services/explore.ts b/src/services/explore.ts index d8a66570..564757c1 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * 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/. @@ -8,77 +8,76 @@ import { UUID } from 'crypto'; import { appendSearchParam, - backendFetch, - backendFetchJson, getRequestParams, - getRestBase, HttpContentType, + UrlString, } from '../utils/api'; import { ElementAttributes } from '../utils/types'; +import { ApiService, UserGetter } from './base-service'; -/** - * Return the base API prefix to the explore server - *
Note: cannot be a const because part of the prefix can be overridden at runtime - * @param vApi the version of api to use - */ -function getPrefix(vApi: number) { - return `${getRestBase()}/explore/v${vApi}`; -} - -export async function createFilter( - newFilter: any, - name: string, - description: string, - parentDirectoryUuid?: UUID -) { - const urlSearchParams = new URLSearchParams(); - urlSearchParams.append('name', name); - urlSearchParams.append('description', description); - if (parentDirectoryUuid) { - urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); +export default class ExploreComSvc extends ApiService { + public constructor(userGetter: UserGetter, restGatewayPath?: UrlString) { + super(userGetter, 'explore', restGatewayPath); } - await backendFetch( - `${getPrefix(1)}/explore/filters?${urlSearchParams.toString()}`, - { - method: 'POST', - headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, - body: JSON.stringify(newFilter), - } - ); -} -export async function saveFilter( - filter: Record, - name: string -) { - await backendFetch( - `${getPrefix(1)}/explore/filters/${filter.id}?${new URLSearchParams({ - name, - }).toString()}`, - { - method: 'PUT', - headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, - body: JSON.stringify(filter), + public async createFilter( + newFilter: any, + name: string, + description: string, + parentDirectoryUuid?: UUID + ) { + const urlSearchParams = new URLSearchParams(); + urlSearchParams.append('name', name); + urlSearchParams.append('description', description); + if (parentDirectoryUuid) { + urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); } - ); -} + await this.backendFetch( + `${this.getPrefix( + 1 + )}/explore/filters?${urlSearchParams.toString()}`, + { + method: 'POST', + headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, + body: JSON.stringify(newFilter), + } + ); + } -export async function fetchElementsInfos( - ids: UUID[], - elementTypes?: string[], - equipmentTypes?: string[] -) { - console.info('Fetching elements metadata'); - const urlSearchParams = getRequestParams({ - ids: ids.filter((id) => id), // filter falsy elements - equipmentTypes: equipmentTypes ?? [], - elementTypes: elementTypes ?? [], - }); - return backendFetchJson( - appendSearchParam( - `${getPrefix(1)}/explore/elements/metadata?${urlSearchParams}`, - urlSearchParams - ), - 'GET' - ); + public async saveFilter(filter: Record, name: string) { + await this.backendFetch( + `${this.getPrefix(1)}/explore/filters/${ + filter.id + }?${new URLSearchParams({ + name, + }).toString()}`, + { + method: 'PUT', + headers: { 'Content-Type': HttpContentType.APPLICATION_JSON }, + body: JSON.stringify(filter), + } + ); + } + + public async fetchElementsInfos( + ids: UUID[], + elementTypes?: string[], + equipmentTypes?: string[] + ) { + console.info('Fetching elements metadata'); + const urlSearchParams = getRequestParams({ + ids: ids.filter((id) => id), // filter falsy elements + equipmentTypes: equipmentTypes ?? [], + elementTypes: elementTypes ?? [], + }); + return this.backendFetchJson( + appendSearchParam( + `${this.getPrefix( + 1 + )}/explore/elements/metadata?${urlSearchParams}`, + urlSearchParams + ), + 'GET' + ); + } } diff --git a/src/services/index.ts b/src/services/index.ts index 063e3ba2..3c921687 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,27 +1,39 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * 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/. */ -export * as AppLocalSvc from './app-local'; -export type * from './app-local'; +export { ApiService, WsService } from './base-service'; +export type { UserGetter } from './base-service'; -export * as AppsMetadataSvc from './apps-metadata'; -export type * from './apps-metadata'; +export { setCommonServices, initCommonServices } from './instances'; -export * as ConfigSvc from './config'; -export type * from './config'; +export { default as AppLocalComSvc } from './app-local'; +export type { Env } from './app-local'; -export * as ConfigNotificationSvc from './config-notification'; -export type * from './config-notification'; +export { default as AppsMetadataComSvc } from './apps-metadata'; +export type { + AppMetadata, + AppMetadataCommon, + AppMetadataStudy, + VersionJson, +} from './apps-metadata'; -export * as DirectorySvc from './directory'; -export type * from './directory'; +export { + default as ConfigComSvc, + COMMON_APP_NAME, + PARAM_THEME, + PARAM_LANGUAGE, +} from './config'; +export type { ConfigParameter, ConfigParameters } from './config'; -export * as ExploreSvc from './explore'; -export type * from './explore'; +export { default as ConfigNotificationComSvc } from './config-notification'; -export * as StudySvc from './study'; -export type * from './study'; +export { default as DirectoryComSvc } from './directory'; + +export { default as ExploreComSvc } from './explore'; + +export { default as StudyComSvc, IdentifiableType } from './study'; +export type { IdentifiableAttributes } from './study'; diff --git a/src/services/instances.ts b/src/services/instances.ts new file mode 100644 index 00000000..90138673 --- /dev/null +++ b/src/services/instances.ts @@ -0,0 +1,63 @@ +/* + * 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 from './app-local'; +import AppsMetadataComSvc from './apps-metadata'; +import ConfigComSvc from './config'; +import ConfigNotificationComSvc from './config-notification'; +import DirectoryComSvc from './directory'; +import ExploreComSvc from './explore'; +import StudyComSvc from './study'; +import { UserGetter } from './base-service'; + +/* + * This "local" instances are means to be used only internally in commons-ui library, not by external apps. + * On the other hand, it's up to the app side to give services instances, it uses a component who needs the API. + */ + +// eslint-disable-next-line one-var, import/no-mutable-exports +export let appLocalSvc: AppLocalComSvc, + appsMetadataSvc: AppsMetadataComSvc, + configSvc: ConfigComSvc, + configNotificationSvc: ConfigNotificationComSvc, + directorySvc: DirectoryComSvc, + exploreSvc: ExploreComSvc, + studySvc: StudyComSvc; + +export function setCommonServices( + appLocalService: AppLocalComSvc, + appsMetadataService: AppsMetadataComSvc, + configService: ConfigComSvc, + configNotificationService: ConfigNotificationComSvc, + directoryService: DirectoryComSvc, + exploreService: ExploreComSvc, + studyService: StudyComSvc +) { + appLocalSvc = appLocalService; + appsMetadataSvc = appsMetadataService; + configSvc = configService; + configNotificationSvc = configNotificationService; + directorySvc = directoryService; + exploreSvc = exploreService; + studySvc = studyService; +} + +export function initCommonServices( + appName: TAppName, + userGetter: UserGetter +) { + const tmpAppLocal = new AppLocalComSvc(); + setCommonServices( + tmpAppLocal, + new AppsMetadataComSvc(tmpAppLocal), + new ConfigComSvc(appName, userGetter), + new ConfigNotificationComSvc(userGetter), + new DirectoryComSvc(userGetter), + new ExploreComSvc(userGetter), + new StudyComSvc(userGetter) + ); +} diff --git a/src/services/study.ts b/src/services/study.ts index db70f8c7..185f98ab 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -1,22 +1,14 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * 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 { UUID } from 'crypto'; -import { backendFetchJson, getRestBase } from '../utils/api'; import { GridSuiteModule } from '../components/TopBar/modules'; - -/** - * Return the base API prefix to the study server - *
Note: cannot be a const because part of the prefix can be overridden at runtime - * @param vApi the version of api to use - */ -function getPrefix(vApi: number) { - return `${getRestBase()}/study/v${vApi}`; -} +import { ApiService, UserGetter } from './base-service'; +import { UrlString } from '../utils/api'; // https://github.com/powsybl/powsybl-core/blob/main/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/IdentifiableType.java#L14 export enum IdentifiableType { @@ -50,17 +42,25 @@ export type IdentifiableAttributes = { distributionKey: number; // double }; -export async function exportFilter(studyUuid: UUID, filterUuid?: UUID) { - console.info('get filter export on study root node'); - return backendFetchJson( - `${getPrefix(1)}/studies/${studyUuid}/filters/${filterUuid}/elements`, - 'GET' - ); -} +export default class StudyComSvc extends ApiService { + public constructor(userGetter: UserGetter, restGatewayPath?: UrlString) { + super(userGetter, 'explore', restGatewayPath); + } + + public async exportFilter(studyUuid: UUID, filterUuid?: UUID) { + console.info('get filter export on study root node'); + return this.backendFetchJson( + `${this.getPrefix( + 1 + )}/studies/${studyUuid}/filters/${filterUuid}/elements`, + 'GET' + ); + } -export async function getServersInfos(viewName: string) { - console.info('get backend servers informations'); - return backendFetchJson( - `${getPrefix(1)}/servers/about?view=${viewName}` - ); + public async getServersInfos(viewName: string) { + console.info('get backend servers informations'); + return this.backendFetchJson( + `${this.getPrefix(1)}/servers/about?view=${viewName}` + ); + } } diff --git a/src/redux/authActions.ts b/src/utils/AuthActions.ts similarity index 97% rename from src/redux/authActions.ts rename to src/utils/AuthActions.ts index 8948d99d..5309f0b8 100644 --- a/src/redux/authActions.ts +++ b/src/utils/AuthActions.ts @@ -14,11 +14,11 @@ type ReadonlyAction = Readonly>; export const USER = 'USER'; export type UserAction = ReadonlyAction & { - user: User | null; + user: User | undefined; }; export function setLoggedUser(user: User | null): UserAction { - return { type: USER, user }; + return { type: USER, user: user ?? undefined }; } export const SIGNIN_CALLBACK_ERROR = 'SIGNIN_CALLBACK_ERROR'; diff --git a/src/utils/AuthService.ts b/src/utils/AuthService.ts index 4aeb4376..021e6e51 100644 --- a/src/utils/AuthService.ts +++ b/src/utils/AuthService.ts @@ -18,7 +18,7 @@ import { setSignInCallbackError, setUnauthorizedUserInfo, setUserValidationError, -} from '../redux/authActions'; +} from './AuthActions'; type UserValidationFunc = (user: User) => Promise; type IdpSettingsGetter = () => Promise; diff --git a/src/utils/api/api-rest.ts b/src/utils/api/api-rest.ts index 307f0cd9..9a85debd 100644 --- a/src/utils/api/api-rest.ts +++ b/src/utils/api/api-rest.ts @@ -7,8 +7,7 @@ import { IncomingHttpHeaders } from 'node:http'; import { LiteralUnion } from 'type-fest'; -import { getGatewayRestPath } from './variables'; -import { FileType, getUserToken } from './utils'; +import { FileType } from './utils'; import { KeyOfWithoutIndexSignature } from '../types'; export type UrlString = `${string}://${string}` | `/${string}` | `./${string}`; @@ -44,31 +43,6 @@ export interface ErrorWithStatus extends Error { status?: number; } -export function getRestBase(): string { - // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return document.baseURI.replace(/\/+$/, '') + getGatewayRestPath(); -} - -function prepareRequest(init?: InitRequest, token?: Token): RequestInit { - if ( - !( - typeof init === 'undefined' || - typeof init === 'string' || - typeof init === 'object' - ) - ) { - throw new TypeError( - `First argument of prepareRequest is not an object: ${typeof init}` - ); - } - const initCopy: RequestInit = - typeof init === 'string' ? { method: init } : { ...(init ?? {}) }; - initCopy.headers = new Headers(initCopy.headers || {}); - const tokenCopy = token || getUserToken(); - initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); - return initCopy; -} - function parseError(text: string) { try { return JSON.parse(text); @@ -97,7 +71,7 @@ function handleError(response: Response): Promise { }); } -async function safeFetch(url: Url, initCopy: RequestInit) { +export async function safeFetch(url: Url, initCopy: RequestInit) { const response = await fetch(url, initCopy); return response.ok ? response : handleError(response); } @@ -120,48 +94,6 @@ export function setRequestHeader( return result; } -export function backendFetch( - url: Url, - init?: InitRequest, - token?: Token -) { - return safeFetch(url, prepareRequest(init, token)); -} - -export async function backendFetchJson( - url: Url, - init?: InitRequest, - token?: Token -): Promise { - const reqInit = setRequestHeader( - init, - 'accept', - HttpContentType.APPLICATION_JSON - ); - return (await backendFetch(url, reqInit, token)).json(); -} - -export async function backendFetchText( - url: Url, - init?: InitRequest, - token?: Token -) { - const reqInit = setRequestHeader( - init, - 'accept', - HttpContentType.TEXT_PLAIN - ); - return (await backendFetch(url, reqInit, token)).text(); -} - -export async function backendFetchFile( - url: Url, - init?: InitRequest, - token?: Token -) { - return (await backendFetch(url, init, token)).blob(); -} - export function downloadFile(blob: Blob, filename: string, type?: FileType) { let contentType; if (type === FileType.ZIP) { diff --git a/src/utils/api/api-websocket.ts b/src/utils/api/api-websocket.ts deleted file mode 100644 index 374ca042..00000000 --- a/src/utils/api/api-websocket.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 { getGatewayWsPath } from './variables'; -import { getUserToken } from './utils'; - -export function getWsBase(): string { - // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return ( - document.baseURI - .replace(/^http(s?):\/\//, 'ws$1://') - .replace(/\/+$/, '') + getGatewayWsPath() - ); -} - -export function getUrlWithToken(baseUrl: string) { - const querySymbol = baseUrl.includes('?') ? '&' : '?'; - return `${baseUrl}${querySymbol}access_token=${getUserToken()}`; -} diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts index d674c373..a6a16c2d 100644 --- a/src/utils/api/index.ts +++ b/src/utils/api/index.ts @@ -4,8 +4,14 @@ * 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 * from './api-rest'; -export * from './api-websocket'; +export { HttpContentType, setRequestHeader, downloadFile } from './api-rest'; +export type { + UrlString, + Url, + HttpMethod, + HttpHeaderName, + InitRequest, + Token, + ErrorWithStatus, +} from './api-rest'; export * from './utils'; -export { setGatewayRestPath, setGatewayWsPath } from './variables'; diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts index 0a81eacf..4ab3c5cb 100644 --- a/src/utils/api/utils.ts +++ b/src/utils/api/utils.ts @@ -5,8 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { getUser } from '../../redux/commonStore'; - export enum FileType { ZIP = 'ZIP', } @@ -25,10 +23,6 @@ export function getRequestParams(parameters: Record) { return searchParams; } -export function getUserToken() { - return getUser()?.id_token; -} - export function appendSearchParam( url: string, searchParams: URLSearchParams | string | null diff --git a/src/utils/api/variables.ts b/src/utils/api/variables.ts deleted file mode 100644 index 194d61a2..00000000 --- a/src/utils/api/variables.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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/. - */ - -/* The problem we have here is that some front apps don't use Vite, and sont using VITE_* vars don't work... - * What we do here is to try to use these variables as default, while permit to devs to overwrite these constants. - */ - -let restGatewayPath: string = import.meta.env.VITE_API_GATEWAY; -let wsGatewayPath: string = import.meta.env.VITE_WS_GATEWAY; - -export function setGatewayRestPath(restPath: string) { - restGatewayPath = restPath; -} - -export function getGatewayRestPath() { - return restGatewayPath; -} - -export function setGatewayWsPath(wsPath: string) { - wsGatewayPath = wsPath; -} - -export function getGatewayWsPath() { - return wsGatewayPath; -} From 571416f352067d9240f9ea69617a73fe4859ac7f Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 24 Jul 2024 22:25:28 +0200 Subject: [PATCH 15/19] Add user-admin user validation for auth --- src/services/base-service.ts | 4 ++-- src/services/index.ts | 2 ++ src/services/instances.ts | 11 ++++++++--- src/services/user-admin.ts | 38 ++++++++++++++++++++++++++++++++++++ src/utils/api/utils.ts | 15 ++++++++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/services/user-admin.ts diff --git a/src/services/base-service.ts b/src/services/base-service.ts index 8f7acf6e..b8504bd2 100644 --- a/src/services/base-service.ts +++ b/src/services/base-service.ts @@ -40,8 +40,8 @@ abstract class BaseService { this.getUser = userGetter; } - protected getUserToken() { - return this.getUser()?.id_token; + protected getUserToken(user?: User) { + return (user ?? this.getUser())?.id_token; } } diff --git a/src/services/index.ts b/src/services/index.ts index 3c921687..67c42af1 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -37,3 +37,5 @@ export { default as ExploreComSvc } from './explore'; export { default as StudyComSvc, IdentifiableType } from './study'; export type { IdentifiableAttributes } from './study'; + +export { default as UserAdminComSvc } from './user-admin'; diff --git a/src/services/instances.ts b/src/services/instances.ts index 90138673..88cfc64d 100644 --- a/src/services/instances.ts +++ b/src/services/instances.ts @@ -12,6 +12,7 @@ import ConfigNotificationComSvc from './config-notification'; import DirectoryComSvc from './directory'; import ExploreComSvc from './explore'; import StudyComSvc from './study'; +import UserAdminComSvc from './user-admin'; import { UserGetter } from './base-service'; /* @@ -26,7 +27,8 @@ export let appLocalSvc: AppLocalComSvc, configNotificationSvc: ConfigNotificationComSvc, directorySvc: DirectoryComSvc, exploreSvc: ExploreComSvc, - studySvc: StudyComSvc; + studySvc: StudyComSvc, + userAdminSvc: UserAdminComSvc; export function setCommonServices( appLocalService: AppLocalComSvc, @@ -35,7 +37,8 @@ export function setCommonServices( configNotificationService: ConfigNotificationComSvc, directoryService: DirectoryComSvc, exploreService: ExploreComSvc, - studyService: StudyComSvc + studyService: StudyComSvc, + userAdminService: UserAdminComSvc ) { appLocalSvc = appLocalService; appsMetadataSvc = appsMetadataService; @@ -44,6 +47,7 @@ export function setCommonServices( directorySvc = directoryService; exploreSvc = exploreService; studySvc = studyService; + userAdminSvc = userAdminService; } export function initCommonServices( @@ -58,6 +62,7 @@ export function initCommonServices( new ConfigNotificationComSvc(userGetter), new DirectoryComSvc(userGetter), new ExploreComSvc(userGetter), - new StudyComSvc(userGetter) + new StudyComSvc(userGetter), + new UserAdminComSvc(userGetter) ); } diff --git a/src/services/user-admin.ts b/src/services/user-admin.ts new file mode 100644 index 00000000..1c8d8fb6 --- /dev/null +++ b/src/services/user-admin.ts @@ -0,0 +1,38 @@ +/* + * 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 { User } from 'oidc-client'; +import { ApiService, UserGetter } from './base-service'; +import { extractUserSub, UrlString } from '../utils/api'; + +export default class UserAdminComSvc extends ApiService { + public constructor(userGetter: UserGetter, restGatewayPath?: UrlString) { + super(userGetter, 'user-admin', restGatewayPath); + } + + /** + * Note: is called from commons-ui AuthServices to validate user infos before setting state.user! + */ + public async fetchValidateUser(user: User) { + try { + const userSub = await extractUserSub(user); + console.debug(`Fetching access for user "${userSub}"...`); + const response = await this.backendFetch( + `${this.getPrefix(1)}/users/${userSub}`, + { method: 'HEAD' }, + this.getUserToken(user) ?? undefined + ); + // if the response is ok, the responseCode will be either 200 or 204 otherwise it's an HTTP error and it will be caught + return response.status === 200; + } catch (error: any) { + if (error.status === 403) { + return false; + } + throw error; + } + } +} diff --git a/src/utils/api/utils.ts b/src/utils/api/utils.ts index 4ab3c5cb..a1e53e89 100644 --- a/src/utils/api/utils.ts +++ b/src/utils/api/utils.ts @@ -4,6 +4,7 @@ * 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 { User } from 'oidc-client'; export enum FileType { ZIP = 'ZIP', @@ -31,3 +32,17 @@ export function appendSearchParam( ? `${url}${url.includes('?') ? '&' : '?'}${searchParams.toString()}` : url; } + +// TODO: why was it promisified? dont remember the reason... +export async function extractUserSub(user: User | undefined) { + const sub = user?.profile?.sub; + if (!sub) { + throw new Error( + `Fetching access for missing user.profile.sub : ${JSON.stringify( + user + )}` + ); + } else { + return sub; + } +} From bf87e312a8ebd09050dd27506805dc8d55469fc4 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 26 Jul 2024 15:17:57 +0200 Subject: [PATCH 16/19] fix bad import + cyclic dependencies --- demo/src/app.jsx | 5 +---- src/components/TopBar/TopBar.test.tsx | 3 ++- src/components/TopBar/TopBar.tsx | 16 +++++++--------- src/components/dialogs/custom-mui-dialog.tsx | 3 ++- .../criteria-based-filter-edition-dialog.tsx | 3 ++- .../expert/expert-filter-edition-dialog.tsx | 3 ++- .../explicit-naming-filter-edition-dialog.tsx | 3 ++- src/components/filter/filter-creation-dialog.tsx | 3 ++- .../provider/custom-form-provider.tsx | 3 +-- src/hooks/localized-countries-hook.ts | 3 +-- src/index.ts | 11 ++++++----- src/local-storage/lang.ts | 4 +--- src/local-storage/theme.ts | 2 +- src/services/config.ts | 3 ++- src/utils/EquipmentType.ts | 3 ++- src/utils/language.ts | 13 ++++++------- src/utils/theme.ts | 11 +++++++++++ 17 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 src/utils/theme.ts diff --git a/demo/src/app.jsx b/demo/src/app.jsx index ba56734b..099d468d 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -36,10 +36,6 @@ import { equipmentStyles, getFileIcon, initializeAuthenticationDev, - LANG_ENGLISH, - LANG_FRENCH, - LANG_SYSTEM, - LIGHT_THEME, logout, card_error_boundary_en, card_error_boundary_fr, @@ -105,6 +101,7 @@ import inputs_en from '../../src/components/translations/inputs-en'; import inputs_fr from '../../src/components/translations/inputs-fr'; import { EquipmentSearchDialog } from './equipment-search'; import { InlineSearch } from './inline-search'; +import { LIGHT_THEME } from '../../src/utils/theme'; const messages = { en: { diff --git a/src/components/TopBar/TopBar.test.tsx b/src/components/TopBar/TopBar.test.tsx index a60a12f1..d0fed8d7 100644 --- a/src/components/TopBar/TopBar.test.tsx +++ b/src/components/TopBar/TopBar.test.tsx @@ -12,11 +12,12 @@ import { IntlProvider } from 'react-intl'; import { red } from '@mui/material/colors'; import { createTheme, ThemeProvider } from '@mui/material'; import { afterEach, beforeEach, expect, it } from '@jest/globals'; -import TopBar, { LANG_ENGLISH } from './TopBar'; +import TopBar from './TopBar'; import top_bar_en from '../translations/top-bar-en'; import { AppMetadataCommon } from '../../services'; import PowsyblLogo from '../images/powsybl_logo.svg?react'; +import { LANG_ENGLISH } from '../../utils/language'; let container: Element; diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index e3921915..19bd1afa 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -54,6 +54,13 @@ import GridLogo, { GridLogoProps } from './GridLogo'; import AboutDialog, { AboutDialogProps } from './AboutDialog'; import { LogoutProps } from '../Login/Logout'; import { AppMetadataCommon } from '../../services'; +import { + GsLang, + LANG_ENGLISH, + LANG_FRENCH, + LANG_SYSTEM, +} from '../../utils/language'; +import { DARK_THEME, GsTheme, LIGHT_THEME } from '../../utils/theme'; const styles = { grow: { @@ -151,18 +158,9 @@ const CustomListItemIcon = styled(ListItemIcon)({ borderRadius: '25px', }); -export const DARK_THEME = 'Dark'; -export const LIGHT_THEME = 'Light'; -export const LANG_SYSTEM = 'sys'; -export const LANG_ENGLISH = 'en'; -export const LANG_FRENCH = 'fr'; const EN = 'EN'; const FR = 'FR'; -export type GsLangUser = typeof LANG_ENGLISH | typeof LANG_FRENCH; -export type GsLang = GsLangUser | typeof LANG_SYSTEM; -export type GsTheme = typeof LIGHT_THEME | typeof DARK_THEME; - export type TopBarProps = Omit & Omit & Omit & { diff --git a/src/components/dialogs/custom-mui-dialog.tsx b/src/components/dialogs/custom-mui-dialog.tsx index 3ddcb1b9..bafb9771 100644 --- a/src/components/dialogs/custom-mui-dialog.tsx +++ b/src/components/dialogs/custom-mui-dialog.tsx @@ -22,7 +22,8 @@ import CancelButton from '../inputs/react-hook-form/utils/cancel-button'; import CustomFormProvider, { MergedFormContextProps, } from '../inputs/react-hook-form/provider/custom-form-provider'; -import { GsLangUser } from '../TopBar/TopBar'; + +import { GsLangUser } from '../../utils/language'; interface ICustomMuiDialog { open: boolean; diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index 6f361580..be22c5af 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -28,7 +28,8 @@ import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; import { exploreSvc } from '../../../services/instances'; import FilterForm from '../filter-form'; -import { GsLangUser } from '../../TopBar/TopBar'; + +import { GsLangUser } from '../../../utils/language'; export type SelectionCopy = { sourceItemUuid: UUID | null; diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index 6f9d854a..ea6275f9 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -20,7 +20,8 @@ import { saveExpertFilter } from '../utils/filter-api'; import { importExpertRules } from './expert-filter-utils'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { GsLangUser } from '../../TopBar/TopBar'; + +import { GsLangUser } from '../../../utils/language'; const formSchema = yup .object() diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index 91f0984d..df6c1e41 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -24,7 +24,8 @@ import FilterForm from '../filter-form'; import { noSelectionForCopy } from '../../../utils/equipment-types'; import { FilterType } from '../constants/filter-constants'; import FetchStatus from '../../../utils/FetchStatus'; -import { GsLangUser } from '../../TopBar/TopBar'; + +import { GsLangUser } from '../../../utils/language'; const formSchema = yup .object() diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index 3a6147dd..47e5edbe 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -34,7 +34,8 @@ import { getExpertFilterEmptyFormData, } from './expert/expert-filter-form'; import { FilterType } from './constants/filter-constants'; -import { GsLangUser } from '../TopBar/TopBar'; + +import { GsLangUser } from '../../utils/language'; const emptyFormData = { [FieldConstants.NAME]: '', diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index f2785577..8d3c87c5 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -8,8 +8,7 @@ import React, { createContext, PropsWithChildren } from 'react'; import { FormProvider, UseFormReturn } from 'react-hook-form'; import * as yup from 'yup'; -import { getSystemLanguage } from '../../../../utils/language'; -import { GsLangUser } from '../../../TopBar/TopBar'; +import { getSystemLanguage, GsLangUser } from '../../../../utils/language'; type CustomFormContextProps = { removeOptional?: boolean; diff --git a/src/hooks/localized-countries-hook.ts b/src/hooks/localized-countries-hook.ts index c04c3588..9f011c41 100644 --- a/src/hooks/localized-countries-hook.ts +++ b/src/hooks/localized-countries-hook.ts @@ -9,8 +9,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import localizedCountries, { LocalizedCountries } from 'localized-countries'; import countriesFr from 'localized-countries/data/fr.json'; import countriesEn from 'localized-countries/data/en.json'; -import { getComputedLanguage } from '../utils/language'; -import { GsLang, LANG_ENGLISH } from '../components/TopBar/TopBar'; +import { getComputedLanguage, GsLang, LANG_ENGLISH } from '../utils/language'; export default function useLocalizedCountries(language: GsLang | undefined) { const [localizedCountriesModule, setLocalizedCountriesModule] = diff --git a/src/index.ts b/src/index.ts index 38a3d638..16cb1bbc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,14 +114,16 @@ export { DEFAULT_ROW_HEIGHT, } from './components/MuiVirtualizedTable/MuiVirtualizedTable'; +export { DARK_THEME, LIGHT_THEME } from './utils/theme'; +export type { GsTheme } from './utils/theme'; export { - DARK_THEME, - LIGHT_THEME, LANG_SYSTEM, LANG_ENGLISH, LANG_FRENCH, -} from './components/TopBar/TopBar'; -export type { GsLang, GsLangUser, GsTheme } from './components/TopBar/TopBar'; + getSystemLanguage, + getComputedLanguage, +} from './utils/language'; +export type { GsLang, GsLangUser } from './utils/language'; export { USER, @@ -266,5 +268,4 @@ export type { EquipmentInfos } from './utils/EquipmentType'; export { getErrorMessage } from './utils/error'; export * from './utils/api'; export * from './services'; -export * from './utils/language'; export * from './local-storage'; diff --git a/src/local-storage/lang.ts b/src/local-storage/lang.ts index c1d0fc95..f77fd848 100644 --- a/src/local-storage/lang.ts +++ b/src/local-storage/lang.ts @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { GsLang, LANG_SYSTEM } from '../components/TopBar/TopBar'; - -import { getComputedLanguage } from '../utils/language'; +import { getComputedLanguage, GsLang, LANG_SYSTEM } from '../utils/language'; function getLocalStorageLanguageKey(appName: string) { return `${appName.toUpperCase()}_LANGUAGE`; diff --git a/src/local-storage/theme.ts b/src/local-storage/theme.ts index 197b092b..87e61f3b 100644 --- a/src/local-storage/theme.ts +++ b/src/local-storage/theme.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { DARK_THEME, GsTheme } from '../components/TopBar/TopBar'; +import { DARK_THEME, GsTheme } from '../utils/theme'; function getLocalStorageThemeKey(appName: string) { return `${appName.toUpperCase()}_THEME`; diff --git a/src/services/config.ts b/src/services/config.ts index 49757842..3b2b96f9 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -5,10 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { GsLang, GsTheme } from '@gridsuite/commons-ui'; import { LiteralUnion } from 'type-fest'; import { UrlString } from '../utils/api'; import { ApiService, UserGetter } from './base-service'; +import { GsLang } from '../utils/language'; +import { GsTheme } from '../utils/theme'; export const COMMON_APP_NAME = 'common'; diff --git a/src/utils/EquipmentType.ts b/src/utils/EquipmentType.ts index cea40c43..fd074388 100644 --- a/src/utils/EquipmentType.ts +++ b/src/utils/EquipmentType.ts @@ -6,7 +6,8 @@ */ import { Theme } from '@mui/material'; -import { LIGHT_THEME } from '../components/TopBar/TopBar'; + +import { LIGHT_THEME } from './theme'; export const TYPE_TAG_MAX_SIZE = '90px'; export const VL_TAG_MAX_SIZE = '100px'; diff --git a/src/utils/language.ts b/src/utils/language.ts index 623467cb..097c7aad 100644 --- a/src/utils/language.ts +++ b/src/utils/language.ts @@ -5,16 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - GsLang, - GsLangUser, - LANG_ENGLISH, - LANG_FRENCH, - LANG_SYSTEM, -} from '../components/TopBar/TopBar'; +export const LANG_SYSTEM = 'sys'; +export const LANG_ENGLISH = 'en'; +export const LANG_FRENCH = 'fr'; const supportedLanguages = [LANG_FRENCH, LANG_ENGLISH]; +export type GsLangUser = typeof LANG_ENGLISH | typeof LANG_FRENCH; +export type GsLang = GsLangUser | typeof LANG_SYSTEM; + export function getSystemLanguage() { const systemLanguage = navigator.language.split(/[-_]/)[0]; return supportedLanguages.includes(systemLanguage) diff --git a/src/utils/theme.ts b/src/utils/theme.ts new file mode 100644 index 00000000..dc29a9b9 --- /dev/null +++ b/src/utils/theme.ts @@ -0,0 +1,11 @@ +/* + * 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/. + */ + +export const DARK_THEME = 'Dark'; +export const LIGHT_THEME = 'Light'; + +export type GsTheme = typeof LIGHT_THEME | typeof DARK_THEME; From 6e3ab894071aef34268df9704eb44c9f2973d1e9 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 6 Aug 2024 20:31:48 +0200 Subject: [PATCH 17/19] pick 120 --- .eslintrc.json | 52 ++-- .prettierrc.json | 1 + babel.config.json | 4 +- demo/data/EquipmentSearchBar.js | 8 +- demo/data/ReportViewer.js | 9 +- demo/src/FlatParametersTab.jsx | 4 +- demo/src/InputsTab.jsx | 42 +-- demo/src/TableTab.jsx | 63 +---- demo/src/TreeViewFinderConfig.jsx | 64 +---- demo/src/app.jsx | 145 +++------- demo/src/equipment-search.tsx | 25 +- demo/src/inline-search.tsx | 17 +- demo/src/right-resizable-box.jsx | 9 +- jest.config.ts | 4 +- license-checker-config.json | 5 +- src/_mocks_/svg.tsx | 4 +- .../AuthenticationRouter.tsx | 67 +---- .../CardErrorBoundary/card-error-boundary.tsx | 24 +- .../CustomAGGrid/custom-aggrid.style.ts | 3 +- src/components/CustomAGGrid/custom-aggrid.tsx | 96 +++---- .../directory-item-selector.tsx | 120 ++------ .../element-search-dialog.tsx | 21 +- .../element-search-input.tsx | 33 +-- .../ElementSearchDialog/equipment-item.tsx | 19 +- .../ElementSearchDialog/tag-renderer.tsx | 15 +- .../use-element-search.tsx | 5 +- .../ExpandableGroup/expandable-group.tsx | 12 +- .../FlatParameters/FlatParameters.tsx | 110 ++------ src/components/Login/Login.tsx | 32 +-- src/components/Login/Logout.tsx | 29 +- .../MuiVirtualizedTable/ColumnHeader.tsx | 122 +++----- .../KeyedColumnsRowIndexer.tsx | 108 ++----- .../MuiVirtualizedTable.tsx | 264 ++++-------------- src/components/MuiVirtualizedTable/index.ts | 5 +- .../MultipleSelectionDialog.tsx | 23 +- .../OverflowableText/overflowable-text.tsx | 28 +- src/components/ReportViewer/filter-button.tsx | 13 +- .../ReportViewer/log-report-item.ts | 31 +- src/components/ReportViewer/log-report.ts | 20 +- src/components/ReportViewer/log-table.tsx | 34 +-- .../ReportViewer/multi-select-list.tsx | 24 +- src/components/ReportViewer/report-item.tsx | 31 +- src/components/ReportViewer/report-viewer.tsx | 31 +- .../report-viewer-dialog.tsx | 23 +- .../SignInCallbackHandler.tsx | 5 +- .../SilentRenewCallbackHandler.tsx | 5 +- .../SnackbarProvider/SnackbarProvider.tsx | 11 +- src/components/TopBar/AboutDialog.tsx | 162 +++-------- src/components/TopBar/GridLogo.tsx | 32 +-- src/components/TopBar/TopBar.tsx | 153 +++------- .../TreeViewFinder/TreeViewFinder.tsx | 154 +++------- src/components/TreeViewFinder/index.ts | 10 +- src/components/dialogs/custom-mui-dialog.tsx | 25 +- .../description-modification-dialog.tsx | 12 +- .../dialogs/modify-element-selection.tsx | 12 +- .../dialogs/popup-confirmation-dialog.tsx | 9 +- .../criteria-based-filter-edition-dialog.tsx | 33 +-- .../criteria-based-filter-form.tsx | 15 +- .../criteria-based-filter-utils.ts | 82 ++---- .../criteria-based/criteria-based-form.tsx | 35 +-- .../criteria-based/filter-free-properties.tsx | 16 +- .../criteria-based/filter-properties.tsx | 123 +++----- .../filter/criteria-based/filter-property.tsx | 8 +- .../filter/expert/expert-filter-constants.ts | 14 +- .../expert/expert-filter-edition-dialog.tsx | 16 +- .../filter/expert/expert-filter-form.tsx | 13 +- .../filter/expert/expert-filter-utils.ts | 144 +++------- .../filter/expert/styles-expert-filter.css | 26 +- .../explicit-naming-filter-edition-dialog.tsx | 24 +- .../explicit-naming-filter-form.tsx | 85 ++---- .../filter/filter-creation-dialog.tsx | 55 +--- src/components/filter/filter-form.tsx | 19 +- src/components/filter/utils/filter-api.ts | 10 +- .../react-hook-form/ExpandingTextField.tsx | 18 +- .../ag-grid-table/bottom-right-buttons.tsx | 28 +- .../cell-editors/numericEditor.ts | 9 +- .../csv-uploader/csv-uploader.tsx | 64 +---- .../ag-grid-table/custom-ag-grid-table.tsx | 51 +--- .../autocomplete-input.tsx | 40 +-- .../multiple-autocomplete-input.tsx | 8 +- .../booleans/boolean-input.tsx | 11 +- .../booleans/checkbox-input.tsx | 9 +- .../react-hook-form/booleans/switch-input.tsx | 9 +- .../react-hook-form/directory-items-input.tsx | 72 +---- .../error-management/error-input.tsx | 6 +- .../react-hook-form/numbers/integer-input.tsx | 5 +- .../provider/custom-form-provider.tsx | 11 +- .../provider/use-custom-form-context.ts | 5 +- .../inputs/react-hook-form/radio-input.tsx | 17 +- .../inputs/react-hook-form/range-input.tsx | 34 +-- .../select-inputs/countries-input.tsx | 7 +- .../select-inputs/mui-select-input.tsx | 11 +- .../select-inputs/select-input.tsx | 18 +- .../inputs/react-hook-form/slider-input.tsx | 20 +- .../inputs/react-hook-form/text-input.tsx | 37 +-- .../react-hook-form/unique-name-input.tsx | 22 +- .../react-hook-form/utils/field-label.tsx | 6 +- .../react-hook-form/utils/functions.tsx | 22 +- .../react-hook-form/utils/submit-button.tsx | 5 +- .../utils/text-field-with-adornment.tsx | 82 ++---- .../inputs/react-query-builder/add-button.tsx | 10 +- .../group-value-editor.tsx | 19 +- .../rule-value-editor.tsx | 25 +- .../custom-react-query-builder.tsx | 28 +- .../element-value-editor.tsx | 37 +-- .../property-value-editor.tsx | 30 +- .../react-query-builder/remove-button.tsx | 15 +- .../translated-value-editor.tsx | 8 +- .../react-query-builder/use-convert-value.ts | 17 +- .../react-query-builder/value-editor.tsx | 49 +--- src/components/inputs/select-clearable.tsx | 10 +- .../translations/card-error-boundary-en.ts | 6 +- src/components/translations/filter-en.ts | 9 +- .../translations/filter-expert-en.ts | 9 +- .../translations/filter-expert-fr.ts | 6 +- src/components/translations/filter-fr.ts | 9 +- src/components/translations/login-en.ts | 9 +- src/components/translations/login-fr.ts | 9 +- src/hooks/localized-countries-hook.ts | 22 +- src/hooks/predefined-properties-hook.ts | 15 +- src/hooks/useDebounce.ts | 10 +- src/hooks/useSnackMessage.ts | 34 +-- src/index.ts | 47 +--- src/local-storage/lang.ts | 10 +- src/local-storage/theme.ts | 5 +- src/services/apps-metadata.ts | 24 +- src/services/base-service.ts | 58 +--- src/services/config-notification.ts | 16 +- src/services/config.ts | 52 +--- src/services/directory.ts | 23 +- src/services/explore.ts | 46 +-- src/services/index.ts | 16 +- src/services/instances.ts | 5 +- src/services/study.ts | 8 +- src/utils/AuthActions.ts | 32 +-- src/utils/AuthService.ts | 88 ++---- src/utils/EquipmentType.ts | 8 +- src/utils/UserManagerMock.ts | 32 +-- src/utils/api/api-rest.ts | 23 +- src/utils/api/index.ts | 10 +- src/utils/api/utils.ts | 19 +- src/utils/conversion-utils.ts | 12 +- ...-types-for-predefined-properties-mapper.ts | 4 +- src/utils/functions.ts | 3 +- src/utils/language.ts | 4 +- src/utils/styles.ts | 22 +- src/utils/types.ts | 6 +- vite.config.mts | 47 +--- 148 files changed, 1128 insertions(+), 3612 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 847a599f..0e71c6a9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,28 +1,28 @@ { - "root": true, - "extends": [ - "airbnb", - "airbnb-typescript", - "airbnb/hooks", - "plugin:prettier/recommended", - "plugin:react/jsx-runtime" - ], - "parserOptions": { - "project": "./tsconfig.json" - }, - "ignorePatterns": [ - // node_modules is implicitly always ignored - "dist", - "vite.config.mts", - "jest.config.ts", - "jest.setup.ts" - ], - "rules": { - "prettier/prettier": "warn", - "curly": "error", - "no-console": "off", - "react/jsx-props-no-spreading": "off", - "react/require-default-props": "off", - "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }] - } + "root": true, + "extends": [ + "airbnb", + "airbnb-typescript", + "airbnb/hooks", + "plugin:prettier/recommended", + "plugin:react/jsx-runtime" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "ignorePatterns": [ + // node_modules is implicitly always ignored + "dist", + "vite.config.mts", + "jest.config.ts", + "jest.setup.ts" + ], + "rules": { + "prettier/prettier": "warn", + "curly": "error", + "no-console": "off", + "react/jsx-props-no-spreading": "off", + "react/require-default-props": "off", + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }] + } } diff --git a/.prettierrc.json b/.prettierrc.json index 27512b6a..fda48c32 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,6 @@ { "trailingComma": "es5", "tabWidth": 4, + "printWidth": 120, "singleQuote": true } diff --git a/babel.config.json b/babel.config.json index 23889e4e..60dd45eb 100644 --- a/babel.config.json +++ b/babel.config.json @@ -6,7 +6,5 @@ "@babel/preset-typescript", "babel-preset-vite" ], - "plugins": [ - "@babel/plugin-transform-runtime" - ] + "plugins": ["@babel/plugin-transform-runtime"] } diff --git a/demo/data/EquipmentSearchBar.js b/demo/data/EquipmentSearchBar.js index 4e13fabd..229db862 100644 --- a/demo/data/EquipmentSearchBar.js +++ b/demo/data/EquipmentSearchBar.js @@ -152,12 +152,8 @@ const searchEquipments = (searchTerm, equipmentLabelling) => { if (searchTerm) { return getEquipmentsInfosForSearchBar( equipmentLabelling - ? EQUIPMENTS.filter((equipment) => - (equipment.name || equipment.id).includes(searchTerm) - ) - : EQUIPMENTS.filter((equipment) => - equipment.id.includes(searchTerm) - ), + ? EQUIPMENTS.filter((equipment) => (equipment.name || equipment.id).includes(searchTerm)) + : EQUIPMENTS.filter((equipment) => equipment.id.includes(searchTerm)), equipmentLabelling ? (e) => e.name || e.id : (e) => e.id ); } diff --git a/demo/data/ReportViewer.js b/demo/data/ReportViewer.js index 54c6fb8e..e484f57e 100644 --- a/demo/data/ReportViewer.js +++ b/demo/data/ReportViewer.js @@ -40,8 +40,7 @@ const LOGS_JSON = { }, { taskKey: 'postLoading', - defaultName: - 'Post loading process on network CC${numNetworkCc} SC${numNetworkSc}', + defaultName: 'Post loading process on network CC${numNetworkCc} SC${numNetworkSc}', taskValues: { numNetworkCc: { value: 0, @@ -154,8 +153,7 @@ const LOGS_JSON = { }, { reportKey: 'NoMismatchDistribution', - defaultMessage: - 'Iteration ${iteration}: already balanced', + defaultMessage: 'Iteration ${iteration}: already balanced', values: { reportSeverity: { value: 'FATAL', @@ -194,8 +192,7 @@ const LOGS_JSON = { reports: [ { reportKey: 'NoMismatchDistribution', - defaultMessage: - 'Iteration ${iteration}: already balanced', + defaultMessage: 'Iteration ${iteration}: already balanced', values: { reportSeverity: { value: 'WARN', diff --git a/demo/src/FlatParametersTab.jsx b/demo/src/FlatParametersTab.jsx index 405380e1..91453da0 100644 --- a/demo/src/FlatParametersTab.jsx +++ b/demo/src/FlatParametersTab.jsx @@ -219,9 +219,7 @@ function FlatParametersTab() { onChange={onChange} variant="standard" showSeparator - selectionWithDialog={(param) => - param?.possibleValues?.length > 10 - } + selectionWithDialog={(param) => param?.possibleValues?.length > 10} /> diff --git a/demo/src/InputsTab.jsx b/demo/src/InputsTab.jsx index 180c835a..3dcd1c0e 100644 --- a/demo/src/InputsTab.jsx +++ b/demo/src/InputsTab.jsx @@ -68,12 +68,7 @@ const options = [ { id: 'ibra', label: 'inputs/ibra' }, ]; -const basicOptions = [ - 'Kylian Mbappe', - 'Neymar', - 'Lionel Messi', - 'Zlatan Ibrahimovic', -]; +const basicOptions = ['Kylian Mbappe', 'Neymar', 'Lionel Messi', 'Zlatan Ibrahimovic']; const gridSize = 4; @@ -141,20 +136,10 @@ function InputsTab() { /> - + - + - + - + - + - + ({ // https://github.com/bvaughn/react-virtualized/issues/454 '& .ReactVirtualized__Table__headerRow': { flip: false, - paddingRight: - theme.direction === 'rtl' ? '0 !important' : undefined, + paddingRight: theme.direction === 'rtl' ? '0 !important' : undefined, }, }, '& .tableRow': { @@ -88,18 +80,13 @@ const stylesVirtualizedTable = (theme) => ({ }); const stylesEmotion = ({ theme }) => - toNestedGlobalSelectors( - stylesVirtualizedTable(theme), - generateMuiVirtualizedTableClass - ); + toNestedGlobalSelectors(stylesVirtualizedTable(theme), generateMuiVirtualizedTableClass); const StyledVirtualizedTable = styled(MuiVirtualizedTable)(stylesEmotion); function TableTab() { const [usesCustomStyles, setUsesCustomStyles] = useState(true); - const VirtualizedTable = usesCustomStyles - ? StyledVirtualizedTable - : MuiVirtualizedTable; + const VirtualizedTable = usesCustomStyles ? StyledVirtualizedTable : MuiVirtualizedTable; const columns = useMemo( () => [ @@ -174,20 +161,12 @@ function TableTab() { ); const sort = useCallback( (dataKey, reverse, isNumeric) => { - let filtered = rows - .map((r, i) => [r, i]) - .filter(([r]) => !filter || filter(r)); + let filtered = rows.map((r, i) => [r, i]).filter(([r]) => !filter || filter(r)); if (dataKey) { filtered = filtered .map(([r, j]) => [r[dataKey], j]) - .map(([r, j]) => [ - isNumeric ? r : Number(r.replace(/[^0-9.]/g, '')), - j, - ]); // for demo, extract any number from a string.. - filtered.sort( - ([a], [b]) => - evenThenOddOrderingKey(b) - evenThenOddOrderingKey(a) - ); + .map(([r, j]) => [isNumeric ? r : Number(r.replace(/[^0-9.]/g, '')), j]); // for demo, extract any number from a string.. + filtered.sort(([a], [b]) => evenThenOddOrderingKey(b) - evenThenOddOrderingKey(a)); if (reverse) { filtered = filtered.reverse(); } @@ -226,18 +205,10 @@ function TableTab() { function renderParams() { return ( - {mkSwitch( - 'Custom theme (emotion)', - usesCustomStyles, - setUsesCustomStyles - )} + {mkSwitch('Custom theme (emotion)', usesCustomStyles, setUsesCustomStyles)} {mkSwitch('Sortable', sortable, setSortable)} {mkSwitch('Instance renewal', recreates, setRecreates)} - {mkSwitch( - 'Uses external indexer', - isIndexerExternal, - setIndexerIsExternal - )} + {mkSwitch('Uses external indexer', isIndexerExternal, setIndexerIsExternal)} - {mkSwitch( - 'External sort (even then odds)', - doesSort, - setDoesSort - )} + {mkSwitch('External sort (even then odds)', doesSort, setDoesSort)} - {mkSwitch( - 'Defer filter changes', - defersFilterChanges, - setDefersFilterChanges - )} + {mkSwitch('Defer filter changes', defersFilterChanges, setDefersFilterChanges)} console.log('onRowClick', args)} onClick={(...args) => console.log('onClick', args)} onCellClick={(...args) => console.log('onCellClick', args)} diff --git a/demo/src/TreeViewFinderConfig.jsx b/demo/src/TreeViewFinderConfig.jsx index 9dcf5432..dbce87da 100644 --- a/demo/src/TreeViewFinderConfig.jsx +++ b/demo/src/TreeViewFinderConfig.jsx @@ -6,15 +6,7 @@ */ import PropTypes from 'prop-types'; -import { - Checkbox, - FormControl, - FormControlLabel, - FormGroup, - FormLabel, - Radio, - RadioGroup, -} from '@mui/material'; +import { Checkbox, FormControl, FormControlLabel, FormGroup, FormLabel, Radio, RadioGroup } from '@mui/material'; /** * TreeViewFinderConfig documentation: @@ -66,69 +58,39 @@ function TreeViewFinderConfig(props) { }} >
- - Data Format - + Data Format - } - label="Tree" - /> - } - label="List" - /> + } label="Tree" /> + } label="List" />
- - Data update - + Data update - } - label="static" - /> - } - label="dynamic" - /> + } label="static" /> + } label="dynamic" />
- - Selection Type - + Selection Type - } - label="single selection" - /> - } - label="multiselect" - /> + } label="single selection" /> + } label="multiselect" />
@@ -136,11 +98,7 @@ function TreeViewFinderConfig(props) { + } label="Only leaves selection" /> diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 099d468d..f3289da2 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -72,10 +72,7 @@ import PowsyblLogo from '../images/powsybl_logo.svg?react'; import AppPackage from '../../package.json'; import ReportViewerDialog from '../../src/components/ReportViewerDialog'; -import { - TreeViewFinder, - generateTreeViewFinderClass, -} from '../../src/components/TreeViewFinder'; +import { TreeViewFinder, generateTreeViewFinderClass } from '../../src/components/TreeViewFinder'; import TreeViewFinderConfig from './TreeViewFinderConfig'; import { @@ -179,13 +176,8 @@ const TreeViewFinderCustomStyles = (theme) => ({ }); const TreeViewFinderCustomStylesEmotion = ({ theme }) => - toNestedGlobalSelectors( - TreeViewFinderCustomStyles(theme), - generateTreeViewFinderClass - ); -const CustomTreeViewFinder = styled(TreeViewFinder)( - TreeViewFinderCustomStylesEmotion -); + toNestedGlobalSelectors(TreeViewFinderCustomStyles(theme), generateTreeViewFinderClass); +const CustomTreeViewFinder = styled(TreeViewFinder)(TreeViewFinderCustomStylesEmotion); function Crasher() { const [crash, setCrash] = useState(false); @@ -306,12 +298,8 @@ function AppContent({ language, onLanguageClick }) { error: null, }); const [user, setUser] = useState(null); - const [authenticationRouterError, setAuthenticationRouterError] = - useState(null); - const [ - showAuthenticationRouterLoginState, - setShowAuthenticationRouterLoginState, - ] = useState(false); + const [authenticationRouterError, setAuthenticationRouterError] = useState(null); + const [showAuthenticationRouterLoginState, setShowAuthenticationRouterLoginState] = useState(false); const [theme, setTheme] = useState(LIGHT_THEME); @@ -320,17 +308,11 @@ function AppContent({ language, onLanguageClick }) { const [equipmentLabelling, setEquipmentLabelling] = useState(false); const [openReportViewer, setOpenReportViewer] = useState(false); - const [openTreeViewFinderDialog, setOpenTreeViewFinderDialog] = - useState(false); - const [ - openTreeViewFinderDialogCustomDialog, - setOpenTreeViewFinderDialogCustomDialog, - ] = useState(false); + const [openTreeViewFinderDialog, setOpenTreeViewFinderDialog] = useState(false); + const [openTreeViewFinderDialogCustomDialog, setOpenTreeViewFinderDialogCustomDialog] = useState(false); // Can't use lazy initializer because useMatch is a hook - const [initialMatchSilentRenewCallbackUrl] = useState( - useMatch('/silent-renew-callback') - ); + const [initialMatchSilentRenewCallbackUrl] = useState(useMatch('/silent-renew-callback')); // TreeViewFinder data const [nodesTree, setNodesTree] = useState(testDataTree); @@ -379,9 +361,7 @@ function AppContent({ language, onLanguageClick }) { if (equipment != null) { equipment.type === EQUIPMENT_TYPE.SUBSTATION.name ? alert(`Equipment ${equipment.label} found !`) - : alert( - `Equipment ${equipment.label} (${equipment.voltageLevelLabel}) found !` - ); + : alert(`Equipment ${equipment.label} (${equipment.voltageLevelLabel}) found !`); } }; const [searchTermDisableReason] = useState('search disabled'); @@ -399,9 +379,7 @@ function AppContent({ language, onLanguageClick }) { } else if (e.type === 'RESET_AUTHENTICATION_ROUTER_ERROR') { setAuthenticationRouterError(null); } else if (e.type === 'SHOW_AUTH_INFO_LOGIN') { - setShowAuthenticationRouterLoginState( - e.showAuthenticationRouterLogin - ); + setShowAuthenticationRouterLoginState(e.showAuthenticationRouterLogin); } }; @@ -425,11 +403,7 @@ function AppContent({ language, onLanguageClick }) { ]; useEffect(() => { - initializeAuthenticationDev( - dispatch, - initialMatchSilentRenewCallbackUrl != null, - validateUser - ) + initializeAuthenticationDev(dispatch, initialMatchSilentRenewCallbackUrl != null, validateUser) .then((userManager) => { setUserManager({ instance: userManager, @@ -468,21 +442,14 @@ function AppContent({ language, onLanguageClick }) { return a.name.localeCompare(b.name); } - const handleToggleDisableSearch = useCallback( - () => setSearchDisabled((oldState) => !oldState), - [] - ); + const handleToggleDisableSearch = useCallback(() => setSearchDisabled((oldState) => !oldState), []); const aboutTimerVersion = useRef(); const aboutTimerCmpnt = useRef(); function simulateGetGlobalVersion() { console.log('getGlobalVersion() called'); return new Promise( - (resolve, reject) => - (aboutTimerVersion.current = window.setTimeout( - () => resolve('1.0.0-demo'), - 1250 - )) + (resolve, reject) => (aboutTimerVersion.current = window.setTimeout(() => resolve('1.0.0-demo'), 1250)) ); } function simulateGetAdditionalComponents() { @@ -616,21 +583,11 @@ function AppContent({ language, onLanguageClick }) { multiSelect={multiSelect} onlyLeaves={onlyLeaves} sortedAlphabetically={sortedAlphabetically} - onDynamicDataChange={(event) => - setDynamicData(event.target.value === 'dynamic') - } - onDataFormatChange={(event) => - setDataFormat(event.target.value) - } - onSelectionTypeChange={(event) => - setMultiSelect(event.target.value === 'multiselect') - } - onOnlyLeavesChange={(event) => - setOnlyLeaves(event.target.checked) - } - onSortedAlphabeticallyChange={(event) => - setSortedAlphabetically(event.target.checked) - } + onDynamicDataChange={(event) => setDynamicData(event.target.value === 'dynamic')} + onDataFormatChange={(event) => setDataFormat(event.target.value)} + onSelectionTypeChange={(event) => setMultiSelect(event.target.value === 'multiselect')} + onOnlyLeavesChange={(event) => setOnlyLeaves(event.target.checked)} + onSortedAlphabeticallyChange={(event) => setSortedAlphabetically(event.target.checked)} /> @@ -763,10 +714,7 @@ function AppContent({ language, onLanguageClick }) { onChange={() => { setSearchTermDisabled(!searchTermDisabled); // TO TEST search activation after some times - setTimeout( - () => setSearchTermDisabled(false), - 4000 - ); + setTimeout(() => setSearchTermDisabled(false), 4000); }} name="search-disabled" /> @@ -791,21 +739,15 @@ function AppContent({ language, onLanguageClick }) { appColor="#808080" appLogo={} onParametersClick={() => console.log('settings')} - onLogoutClick={() => - logout(dispatch, userManager.instance) - } + onLogoutClick={() => logout(dispatch, userManager.instance)} onLogoClick={() => console.log('logo')} onThemeClick={handleThemeClick} theme={theme} appVersion={AppPackage.version} appLicense={AppPackage.license} globalVersionPromise={simulateGetGlobalVersion} - additionalModulesPromise={ - simulateGetAdditionalComponents - } - onEquipmentLabellingClick={ - handleEquipmentLabellingClick - } + additionalModulesPromise={simulateGetAdditionalComponents} + onEquipmentLabellingClick={handleEquipmentLabellingClick} equipmentLabelling={equipmentLabelling} withElementsSearch searchingLabel={intl.formatMessage({ @@ -818,11 +760,7 @@ function AppContent({ language, onLanguageClick }) { searchTermDisableReason={searchTermDisableReason} elementsFound={equipmentsFound} renderElement={(props) => ( - + )} onLanguageClick={onLanguageClick} language={language} @@ -846,12 +784,7 @@ function AppContent({ language, onLanguageClick }) { {user !== null ? (
- - setTabIndex(newTabIndex) - } - > + setTabIndex(newTabIndex)}> @@ -866,12 +799,8 @@ function AppContent({ language, onLanguageClick }) { - - + + ); diff --git a/demo/src/equipment-search.tsx b/demo/src/equipment-search.tsx index eca1e660..771ce301 100644 --- a/demo/src/equipment-search.tsx +++ b/demo/src/equipment-search.tsx @@ -8,13 +8,7 @@ import { useState } from 'react'; import { Button, TextField } from '@mui/material'; import { Search } from '@mui/icons-material'; import { useIntl } from 'react-intl'; -import { - ElementSearchDialog, - EquipmentItem, - equipmentStyles, - EquipmentType, - useElementSearch, -} from '../../src/index'; +import { ElementSearchDialog, EquipmentItem, equipmentStyles, EquipmentType, useElementSearch } from '../../src/index'; interface AnyElementInterface { id: string; @@ -49,10 +43,9 @@ const searchEquipmentPromise = () => { export function EquipmentSearchDialog() { const [isSearchOpen, setIsSearchOpen] = useState(false); - const { elementsFound, isLoading, searchTerm, updateSearchTerm } = - useElementSearch({ - fetchElements: searchEquipmentPromise, - }); + const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ + fetchElements: searchEquipmentPromise, + }); const intl = useIntl(); @@ -68,18 +61,12 @@ export function EquipmentSearchDialog() { }} elementsFound={elementsFound} renderElement={(props: any) => ( - + )} searchTerm={searchTerm} loading={isLoading} getOptionLabel={(option) => option.label} - isOptionEqualToValue={(option1, option2) => - option1.id === option2.id - } + isOptionEqualToValue={(option1, option2) => option1.id === option2.id} renderInput={(displayedValue, params) => ( ( - + )} searchTerm={searchTerm} loading={isLoading} getOptionLabel={(option) => option.label} - isOptionEqualToValue={(option1, option2) => - option1.id === option2.id - } + isOptionEqualToValue={(option1, option2) => option1.id === option2.id} renderInput={(displayedValue, params) => ( >( - (props, ref) => -); +const SvgrMock = forwardRef>((props, ref) => ); export default SvgrMock; diff --git a/src/components/AuthenticationRouter/AuthenticationRouter.tsx b/src/components/AuthenticationRouter/AuthenticationRouter.tsx index befaf3a8..9cdf1f16 100644 --- a/src/components/AuthenticationRouter/AuthenticationRouter.tsx +++ b/src/components/AuthenticationRouter/AuthenticationRouter.tsx @@ -6,24 +6,13 @@ */ import { Dispatch, useCallback } from 'react'; -import { - Location, - Navigate, - NavigateFunction, - Route, - Routes, -} from 'react-router-dom'; +import { Location, Navigate, NavigateFunction, Route, Routes } from 'react-router-dom'; import { Alert, AlertTitle, Grid } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { UserManager } from 'oidc-client'; import SignInCallbackHandler from '../SignInCallbackHandler'; import { AuthenticationActions } from '../../utils/AuthActions'; -import { - handleSigninCallback, - handleSilentRenewCallback, - login, - logout, -} from '../../utils/AuthService'; +import { handleSigninCallback, handleSilentRenewCallback, login, logout } from '../../utils/AuthService'; import SilentRenewCallbackHandler from '../SilentRenewCallbackHandler'; import Login from '../Login'; import Logout from '../Login/Logout'; @@ -70,15 +59,8 @@ function AuthenticationRouter({ } }, [userManager.instance]); return ( - - {userManager.error !== null && ( -

Error : Getting userManager; {userManager.error}

- )} + + {userManager.error !== null &&

Error : Getting userManager; {userManager.error}

} {signInCallbackError !== null && (

Error : SignIn Callback Error; @@ -100,9 +82,7 @@ function AuthenticationRouter({ element={ } /> @@ -114,9 +94,7 @@ function AuthenticationRouter({ authenticationRouterError == null && ( - login(location, userManager.instance) - } + onLoginClick={() => login(location, userManager.instance)} /> ) } @@ -128,9 +106,7 @@ function AuthenticationRouter({ - logout(dispatch, userManager.instance) - } + onLogoutClick={() => logout(dispatch, userManager.instance)} /> @@ -142,20 +118,13 @@ function AuthenticationRouter({ -

- { - authenticationRouterError.logoutError - .error.message - } -

+

{authenticationRouterError.logoutError.error.message}

)} - {authenticationRouterError?.userValidationError != - null && ( + {authenticationRouterError?.userValidationError != null && ( @@ -163,20 +132,13 @@ function AuthenticationRouter({ -

- { - authenticationRouterError - .userValidationError.error.message - } -

+

{authenticationRouterError.userValidationError.error.message}

)} - {authenticationRouterError?.unauthorizedUserInfo != - null && ( + {authenticationRouterError?.unauthorizedUserInfo != null && ( @@ -184,8 +146,7 @@ function AuthenticationRouter({ diff --git a/src/components/CardErrorBoundary/card-error-boundary.tsx b/src/components/CardErrorBoundary/card-error-boundary.tsx index 71494f9f..c3f86a50 100644 --- a/src/components/CardErrorBoundary/card-error-boundary.tsx +++ b/src/components/CardErrorBoundary/card-error-boundary.tsx @@ -9,10 +9,7 @@ // https://reactjs.org/docs/error-boundaries.html // https://mui.com/material-ui/react-card/#complex-interaction -import { - ExpandMore as ExpandMoreIcon, - Replay as ReplayIcon, -} from '@mui/icons-material'; +import { ExpandMore as ExpandMoreIcon, Replay as ReplayIcon } from '@mui/icons-material'; import { Box, Card, @@ -59,10 +56,7 @@ type CardErrorBoundaryStateSuccess = { hasError: false; }; -type CardErrorBoundaryState = ( - | CardErrorBoundaryStateError - | CardErrorBoundaryStateSuccess -) & { +type CardErrorBoundaryState = (CardErrorBoundaryStateError | CardErrorBoundaryStateSuccess) & { expanded: boolean; }; @@ -126,10 +120,7 @@ class CardErrorBoundary extends Component { - + { - + - - {error.message} - + {error.message} diff --git a/src/components/CustomAGGrid/custom-aggrid.style.ts b/src/components/CustomAGGrid/custom-aggrid.style.ts index bb1613bd..9ad9c15d 100644 --- a/src/components/CustomAGGrid/custom-aggrid.style.ts +++ b/src/components/CustomAGGrid/custom-aggrid.style.ts @@ -16,8 +16,7 @@ export const styles = { position: 'relative', [`&.${CUSTOM_AGGRID_THEME}`]: { - '--ag-value-change-value-highlight-background-color': - theme.aggrid.valueChangeHighlightBackgroundColor, + '--ag-value-change-value-highlight-background-color': theme.aggrid.valueChangeHighlightBackgroundColor, '--ag-selected-row-background-color': theme.aggrid.highlightColor, '--ag-row-hover-color': theme.aggrid.highlightColor, }, diff --git a/src/components/CustomAGGrid/custom-aggrid.tsx b/src/components/CustomAGGrid/custom-aggrid.tsx index abb55ee5..c26f2b5d 100644 --- a/src/components/CustomAGGrid/custom-aggrid.tsx +++ b/src/components/CustomAGGrid/custom-aggrid.tsx @@ -36,60 +36,52 @@ const onColumnResized = (params: ColumnResizedEvent) => { } }; -const CustomAGGrid = React.forwardRef( - (props, ref) => { - const { - shouldHidePinnedHeaderRightBorder = false, - overlayNoRowsTemplate, - loadingOverlayComponent, - loadingOverlayComponentParams, - showOverlay = false, - } = props; - const theme = useTheme(); - const intl = useIntl(); +const CustomAGGrid = React.forwardRef((props, ref) => { + const { + shouldHidePinnedHeaderRightBorder = false, + overlayNoRowsTemplate, + loadingOverlayComponent, + loadingOverlayComponentParams, + showOverlay = false, + } = props; + const theme = useTheme(); + const intl = useIntl(); - const GRID_PREFIX = 'grid.'; + const GRID_PREFIX = 'grid.'; - const getLocaleText = useCallback( - (params: GetLocaleTextParams) => { - const key = GRID_PREFIX + params.key; - return intl.formatMessage({ - id: key, - defaultMessage: params.defaultValue, - }); - }, - [intl] - ); + const getLocaleText = useCallback( + (params: GetLocaleTextParams) => { + const key = GRID_PREFIX + params.key; + return intl.formatMessage({ + id: key, + defaultMessage: params.defaultValue, + }); + }, + [intl] + ); - return ( - - - - ); - } -); + return ( + + + + ); +}); export default CustomAGGrid; diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index 460146a3..af6d1407 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -10,10 +10,7 @@ import { SxProps, Theme } from '@mui/material'; import { UUID } from 'crypto'; import getFileIcon from '../../utils/ElementIcon'; import { ElementType } from '../../utils/ElementType'; -import TreeViewFinder, { - TreeViewFinderNodeProps, - TreeViewFinderProps, -} from '../TreeViewFinder/TreeViewFinder'; +import TreeViewFinder, { TreeViewFinderNodeProps, TreeViewFinderProps } from '../TreeViewFinder/TreeViewFinder'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import { directorySvc, exploreSvc } from '../../services/instances'; @@ -40,10 +37,7 @@ function flattenDownNodes(n: any, cef: (n: any) => any[]): any[] { if (subs.length === 0) { return [n]; } - return Array.prototype.concat( - [n], - ...subs.map((sn: any) => flattenDownNodes(sn, cef)) - ); + return Array.prototype.concat([n], ...subs.map((sn: any) => flattenDownNodes(sn, cef))); } function refreshedUpNodes(m: any[], nn: any): any[] { @@ -54,9 +48,7 @@ function refreshedUpNodes(m: any[], nn: any): any[] { return [nn]; } const parent = m[nn.parentUuid]; - const nextChildren = parent.children.map((c: any) => - c.elementUuid === nn.elementUuid ? nn : c - ); + const nextChildren = parent.children.map((c: any) => (c.elementUuid === nn.elementUuid ? nn : c)); const nextParent = { ...parent, children: nextChildren }; return [nn, ...refreshedUpNodes(m, nextParent)]; } @@ -68,12 +60,7 @@ function refreshedUpNodes(m: any[], nn: any): any[] { * @param nodeId uuid of the node to update children, may be null or undefined (means root) * @param children new value of the node children (shallow nodes) */ -function updatedTree( - prevRoots: any[], - prevMap: any, - nodeId: UUID | null, - children: any[] -) { +function updatedTree(prevRoots: any[], prevMap: any, nodeId: UUID | null, children: any[]) { const nextChildren = children .sort((a, b) => a.elementName.localeCompare(b.elementName)) .map((n: any) => { @@ -109,12 +96,8 @@ function updatedTree( return [prevRoots, prevMap]; } - const nextUuids = new Set( - children ? children.map((n) => n.elementUuid) : [] - ); - const prevUuids = prevChildren - ? prevChildren.map((n: any) => n.elementUuid) - : []; + const nextUuids = new Set(children ? children.map((n) => n.elementUuid) : []); + const prevUuids = prevChildren ? prevChildren.map((n: any) => n.elementUuid) : []; const mayNodeId = nodeId ? [nodeId] : []; const nonCopyUuids = new Set([ @@ -123,11 +106,7 @@ function updatedTree( ...Array.prototype.concat( ...prevUuids .filter((u: UUID) => !nextUuids.has(u)) - .map((u: UUID) => - flattenDownNodes(prevMap[u], (n) => n.children).map( - (n) => n.elementUuid - ) - ) + .map((u: UUID) => flattenDownNodes(prevMap[u], (n) => n.children).map((n) => n.elementUuid)) ), ]); @@ -146,10 +125,7 @@ function updatedTree( ...refreshedUpNodes(prevMap, nextNode).map((n) => [n.elementUuid, n]), ]); - const nextRoots = - nodeId === null - ? nextChildren - : prevRoots.map((r) => nextMap[r.elementUuid]); + const nextRoots = nodeId === null ? nextChildren : prevRoots.map((r) => nextMap[r.elementUuid]); return [nextRoots, nextMap]; } @@ -171,10 +147,7 @@ interface DirectoryItemSelectorProps extends TreeViewFinderProps { expanded?: UUID[]; } -function sortHandlingDirectories( - a: TreeViewFinderNodeProps, - b: TreeViewFinderNodeProps -): number { +function sortHandlingDirectories(a: TreeViewFinderNodeProps, b: TreeViewFinderNodeProps): number { // If children property is set it means it's a directory, they are handled differently in order to keep them at the top of the list if (a.children && !b.children) { return -1; @@ -202,10 +175,7 @@ function DirectoryItemSelector({ const rootsRef = useRef([]); rootsRef.current = rootDirectories; const { snackError } = useSnackMessage(); - const contentFilter = useCallback( - () => new Set([ElementType.DIRECTORY, ...types]), - [types] - ); + const contentFilter = useCallback(() => new Set([ElementType.DIRECTORY, ...types]), [types]); const convertChildren = useCallback((children: any[]): any[] => { return children.map((e) => { @@ -214,14 +184,8 @@ function DirectoryItemSelector({ name: e.elementName, specificMetadata: e.specificMetadata, icon: getFileIcon(e.type, styles.icon as SxProps), - children: - e.type === ElementType.DIRECTORY - ? convertChildren(e.children) - : undefined, - childrenCount: - e.type === ElementType.DIRECTORY - ? e.subdirectoriesCount - : undefined, + children: e.type === ElementType.DIRECTORY ? convertChildren(e.children) : undefined, + childrenCount: e.type === ElementType.DIRECTORY ? e.subdirectoriesCount : undefined, }; }); }, []); @@ -235,14 +199,9 @@ function DirectoryItemSelector({ icon: getFileIcon(e.type, styles.icon as SxProps), children: e.type === ElementType.DIRECTORY - ? convertChildren( - nodeMap.current[e.elementUuid].children - ) - : undefined, - childrenCount: - e.type === ElementType.DIRECTORY - ? e.subdirectoriesCount + ? convertChildren(nodeMap.current[e.elementUuid].children) : undefined, + childrenCount: e.type === ElementType.DIRECTORY ? e.subdirectoriesCount : undefined, }; }); }, @@ -251,12 +210,7 @@ function DirectoryItemSelector({ const addToDirectory = useCallback( (nodeId: UUID, content: any[]) => { - const [nrs, mdr] = updatedTree( - rootsRef.current, - nodeMap.current, - nodeId, - content - ); + const [nrs, mdr] = updatedTree(rootsRef.current, nodeMap.current, nodeId, content); setRootDirectories(nrs); nodeMap.current = mdr; setData(convertRoots(nrs)); @@ -268,12 +222,7 @@ function DirectoryItemSelector({ directorySvc .fetchRootFolders(types) .then((newData) => { - const [nrs, mdr] = updatedTree( - rootsRef.current, - nodeMap.current, - null, - newData - ); + const [nrs, mdr] = updatedTree(rootsRef.current, nodeMap.current, null, newData); setRootDirectories(nrs); nodeMap.current = mdr; setData(convertRoots(nrs)); @@ -292,38 +241,25 @@ function DirectoryItemSelector({ directorySvc .fetchDirectoryContent(nodeId, typeList) .then((children) => { - const childrenMatchedTypes = children.filter((item: any) => - contentFilter().has(item.type) - ); + const childrenMatchedTypes = children.filter((item: any) => contentFilter().has(item.type)); - if ( - childrenMatchedTypes.length > 0 && - equipmentTypes && - equipmentTypes.length > 0 - ) { + if (childrenMatchedTypes.length > 0 && equipmentTypes && equipmentTypes.length > 0) { exploreSvc .fetchElementsInfos( - childrenMatchedTypes.map( - (e: any) => e.elementUuid - ), + childrenMatchedTypes.map((e: any) => e.elementUuid), types, equipmentTypes ) .then((childrenWithMetadata) => { const filtredChildren = itemFilter - ? childrenWithMetadata.filter( - (val: any) => { - // Accept every directory - if ( - val.type === - ElementType.DIRECTORY - ) { - return true; - } - // otherwise filter with the custom itemFilter func - return itemFilter(val); + ? childrenWithMetadata.filter((val: any) => { + // Accept every directory + if (val.type === ElementType.DIRECTORY) { + return true; } - ) + // otherwise filter with the custom itemFilter func + return itemFilter(val); + }) : childrenWithMetadata; // update directory content addToDirectory(nodeId, filtredChildren); @@ -334,9 +270,7 @@ function DirectoryItemSelector({ } }) .catch((error) => { - console.warn( - `Could not update subs (and content) of '${nodeId}' : ${error.message}` - ); + console.warn(`Could not update subs (and content) of '${nodeId}' : ${error.message}`); }); }, [types, equipmentTypes, itemFilter, contentFilter, addToDirectory] diff --git a/src/components/ElementSearchDialog/element-search-dialog.tsx b/src/components/ElementSearchDialog/element-search-dialog.tsx index c3bdbdde..6b7bdc9c 100644 --- a/src/components/ElementSearchDialog/element-search-dialog.tsx +++ b/src/components/ElementSearchDialog/element-search-dialog.tsx @@ -7,13 +7,9 @@ import { useCallback } from 'react'; import { Dialog, DialogContent } from '@mui/material'; -import { - ElementSearchInput, - ElementSearchInputProps, -} from './element-search-input'; +import { ElementSearchInput, ElementSearchInputProps } from './element-search-input'; -export interface ElementSearchDialogProps - extends ElementSearchInputProps { +export interface ElementSearchDialogProps extends ElementSearchInputProps { onClose?: () => void; open: boolean; } @@ -27,18 +23,9 @@ export function ElementSearchDialog(props: ElementSearchDialogProps) { }, [onSearchTermChange, onClose]); return ( - + - + ); diff --git a/src/components/ElementSearchDialog/element-search-input.tsx b/src/components/ElementSearchDialog/element-search-input.tsx index f1653b7b..d547d69c 100644 --- a/src/components/ElementSearchDialog/element-search-input.tsx +++ b/src/components/ElementSearchDialog/element-search-input.tsx @@ -4,11 +4,7 @@ * 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 { - Autocomplete, - AutocompleteProps, - AutocompleteRenderInputParams, -} from '@mui/material'; +import { Autocomplete, AutocompleteProps, AutocompleteRenderInputParams } from '@mui/material'; import { HTMLAttributes, ReactNode, useMemo } from 'react'; import { useIntl } from 'react-intl'; @@ -20,13 +16,7 @@ export type RenderElementProps = HTMLAttributes & { export interface ElementSearchInputProps extends Pick< AutocompleteProps, - | 'sx' - | 'size' - | 'loadingText' - | 'loading' - | 'disableClearable' - | 'getOptionDisabled' - | 'PaperComponent' + 'sx' | 'size' | 'loadingText' | 'loading' | 'disableClearable' | 'getOptionDisabled' | 'PaperComponent' > { searchTerm: string; onSearchTermChange: (searchTerm: string) => void; @@ -35,18 +25,13 @@ export interface ElementSearchInputProps isOptionEqualToValue: (option1: T, option2: T) => boolean; elementsFound: T[]; renderElement: (props: RenderElementProps) => ReactNode; - renderInput: ( - searchTerm: string, - props: AutocompleteRenderInputParams - ) => ReactNode; + renderInput: (searchTerm: string, props: AutocompleteRenderInputParams) => ReactNode; searchTermDisabled?: boolean; searchTermDisableReason?: string; showResults?: boolean; } -export function ElementSearchInput( - props: Readonly> -) { +export function ElementSearchInput(props: Readonly>) { const { elementsFound, loading, @@ -99,11 +84,7 @@ export function ElementSearchInput( }} onChange={(_event, newValue, reason) => { // when calling this method with reason == "selectOption", newValue can't be null or of type "string", since an option has been clicked on - if ( - newValue != null && - typeof newValue !== 'string' && - reason === 'selectOption' - ) { + if (newValue != null && typeof newValue !== 'string' && reason === 'selectOption') { onSelectionChange(newValue); } }} @@ -121,9 +102,7 @@ export function ElementSearchInput( inputValue, }) } - renderInput={(params: AutocompleteRenderInputParams) => - renderInput(displayedValue, params) - } + renderInput={(params: AutocompleteRenderInputParams) => renderInput(displayedValue, params)} getOptionLabel={(option) => { if (typeof option === 'string') { return option; diff --git a/src/components/ElementSearchDialog/equipment-item.tsx b/src/components/ElementSearchDialog/equipment-item.tsx index 249f4123..ec534ee0 100644 --- a/src/components/ElementSearchDialog/equipment-item.tsx +++ b/src/components/ElementSearchDialog/equipment-item.tsx @@ -50,25 +50,14 @@ export function EquipmentItem({ /* override li.key otherwise it will use label which could be duplicated */ return (
  • - + {!showsJustText && ( - + )} ); } diff --git a/src/components/ElementSearchDialog/use-element-search.tsx b/src/components/ElementSearchDialog/use-element-search.tsx index 8d2f6881..a29f2d81 100644 --- a/src/components/ElementSearchDialog/use-element-search.tsx +++ b/src/components/ElementSearchDialog/use-element-search.tsx @@ -87,10 +87,7 @@ const useElementSearch = ( [fetchElements, snackError] ); - const debouncedSearchMatchingElements = useDebounce( - searchMatchingElements, - SEARCH_FETCH_TIMEOUT_MILLIS - ); + const debouncedSearchMatchingElements = useDebounce(searchMatchingElements, SEARCH_FETCH_TIMEOUT_MILLIS); const updateSearchTerm = useCallback( (newSearchTerm: string) => { diff --git a/src/components/ExpandableGroup/expandable-group.tsx b/src/components/ExpandableGroup/expandable-group.tsx index 320b1e9c..a7bb1fe0 100644 --- a/src/components/ExpandableGroup/expandable-group.tsx +++ b/src/components/ExpandableGroup/expandable-group.tsx @@ -4,13 +4,7 @@ * 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 { - Accordion, - AccordionDetails, - AccordionSummary, - Theme, - Typography, -} from '@mui/material'; +import { Accordion, AccordionDetails, AccordionSummary, Theme, Typography } from '@mui/material'; import { PropsWithChildren, ReactNode, useState } from 'react'; import { ExpandCircleDown, ExpandMore } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; @@ -69,9 +63,7 @@ function ExpandableGroup({ renderHeader, children }: ExpandableGroupProps) { renderHeader )} - - {children} - + {children} ); } diff --git a/src/components/FlatParameters/FlatParameters.tsx b/src/components/FlatParameters/FlatParameters.tsx index 25914205..0ed0915c 100644 --- a/src/components/FlatParameters/FlatParameters.tsx +++ b/src/components/FlatParameters/FlatParameters.tsx @@ -81,9 +81,7 @@ function longestCommonPrefix(stringList: string[]) { if (!stringList?.length) { return ''; } - let prefix = stringList.reduce((acc: string, str: string) => - str.length < acc.length ? str : acc - ); + let prefix = stringList.reduce((acc: string, str: string) => (str.length < acc.length ? str : acc)); stringList.forEach((str) => { while (!str.startsWith(prefix)) { @@ -188,11 +186,7 @@ export function FlatParameters({ } if (onChange) { if (param.type === 'STRING_LIST') { - onChange( - paramName, - value ? value.toString() : null, - isInEdition - ); + onChange(paramName, value ? value.toString() : null, isInEdition); } else { onChange(paramName, value, isInEdition); } @@ -230,9 +224,7 @@ export function FlatParameters({ if (initValues && Object.hasOwn(initValues, param.name)) { if (param.type === 'BOOLEAN') { // on the server side, we only store String, so we eventually need to convert before - return initValues[param.name] === 'false' - ? false - : initValues[param.name]; // 'true' is truthly + return initValues[param.name] === 'false' ? false : initValues[param.name]; // 'true' is truthly } if (param.type !== 'STRING_LIST') { return initValues[param.name]; @@ -256,10 +248,7 @@ export function FlatParameters({ return value?.replace(',', '.') || ''; }; - const getStringListValue = ( - allValues: string[], - selectValues: string[] - ) => { + const getStringListValue = (allValues: string[], selectValues: string[]) => { if (!selectValues || !selectValues.length) { return intl.formatMessage({ id: 'flat_parameters/none' }); } @@ -275,18 +264,11 @@ export function FlatParameters({ const fieldValue = mixInitAndDefault(param); switch (param.type) { case 'BOOLEAN': - return ( - onFieldChange(e.target.checked, param)} - /> - ); + return onFieldChange(e.target.checked, param)} />; case 'DOUBLE': { const err = Number.isNaN(fieldValue) || - (typeof fieldValue !== 'number' && - !!fieldValue && - Number.isNaN(fieldValue - 0)); + (typeof fieldValue !== 'number' && !!fieldValue && Number.isNaN(fieldValue - 0)); return ( { const m = FloatRE.exec(e.target.value); if (m) { - onFieldChange( - outputTransformFloatString(e.target.value), - param - ); + onFieldChange(outputTransformFloatString(e.target.value), param); } }} error={err} @@ -329,30 +308,20 @@ export function FlatParameters({ ); case 'STRING_LIST': if (param.possibleValues) { - const allOptions = sortPossibleValues( - param.name, - param.possibleValues - ).map(({ id }) => id); + const allOptions = sortPossibleValues(param.name, param.possibleValues).map(({ id }) => id); const withDialog = selectionWithDialog(param); if (withDialog) { return ( <> - setOpenSelector(true) - } - > + setOpenSelector(true)}> ), @@ -362,9 +331,7 @@ export function FlatParameters({ options={allOptions} titleId={getSelectionDialogName(param.name)} open={openSelector} - getOptionLabel={(option) => - getTranslatedValue(param.name, option) - } + getOptionLabel={(option) => getTranslatedValue(param.name, option)} selectedOptions={fieldValue} handleClose={() => setOpenSelector(false)} handleValidate={(selectedOptions) => { @@ -380,29 +347,16 @@ export function FlatParameters({ fullWidth multiple size="small" - options={sortPossibleValues( - param.name, - param.possibleValues - ).map((v) => v.id)} - getOptionLabel={(option) => - getTranslatedValue(param.name, option) - } + options={sortPossibleValues(param.name, param.possibleValues).map((v) => v.id)} + getOptionLabel={(option) => getTranslatedValue(param.name, option)} onChange={(e, value) => onFieldChange(value, param)} value={fieldValue} renderTags={(values, getTagProps) => { return values.map((value, index) => ( - + )); }} - renderInput={(inputProps) => ( - - )} + renderInput={(inputProps) => } /> ); } @@ -419,17 +373,10 @@ export function FlatParameters({ value={fieldValue} renderTags={(values, getTagProps) => { return values.map((value, index) => ( - + )); }} - renderInput={(inputProps) => ( - - )} + renderInput={(inputProps) => } /> ); @@ -447,10 +394,7 @@ export function FlatParameters({ sx={{ minWidth: '4em' }} variant={variant} > - {sortPossibleValues( - param.name, - param.possibleValues - ).map((value) => ( + {sortPossibleValues(param.name, param.possibleValues).map((value) => ( {value.message} @@ -480,29 +424,17 @@ export function FlatParameters({ - } + title={} enterDelay={1200} key={param.name} > - + {renderField(param)} - {showSeparator && index !== paramsAsArray.length - 1 && ( - - )} + {showSeparator && index !== paramsAsArray.length - 1 && } ))} diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 82df44a1..4d4121f5 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -4,15 +4,7 @@ * 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 { - Avatar, - Box, - Button, - Container, - Link, - Theme, - Typography, -} from '@mui/material'; +import { Avatar, Box, Button, Container, Link, Theme, Typography } from '@mui/material'; import { LockOutlined as LockOutlinedIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; @@ -50,29 +42,15 @@ function Login({ onLoginClick, disabled }: LoginProps) { - {' '} - ? + ? - - + {'Copyright © '} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} diff --git a/src/components/Login/Logout.tsx b/src/components/Login/Logout.tsx index 2a7c095b..8ea21e29 100644 --- a/src/components/Login/Logout.tsx +++ b/src/components/Login/Logout.tsx @@ -5,15 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - Avatar, - Box, - Button, - Container, - Link, - Theme, - Typography, -} from '@mui/material'; +import { Avatar, Box, Button, Container, Link, Theme, Typography } from '@mui/material'; import { LogoutOutlined as LogoutOutlinedIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; @@ -60,24 +52,11 @@ function Logout({ onLogoutClick, disabled }: LogoutProps) { - {' '} - ? + ? - diff --git a/src/components/MuiVirtualizedTable/ColumnHeader.tsx b/src/components/MuiVirtualizedTable/ColumnHeader.tsx index c5b4f517..5517ee97 100644 --- a/src/components/MuiVirtualizedTable/ColumnHeader.tsx +++ b/src/components/MuiVirtualizedTable/ColumnHeader.tsx @@ -5,16 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - ComponentProps, - forwardRef, - MouseEvent, - ReactNode, - useCallback, - useMemo, - useRef, - useState, -} from 'react'; +import { ComponentProps, forwardRef, MouseEvent, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import { ArrowDownward as ArrowDownwardIcon, @@ -77,16 +68,10 @@ interface SortButtonProps { // so lesser values are at top, so the upward arrow function SortButton({ signedRank = 0, ...props }: SortButtonProps) { const sortRank = Math.abs(signedRank); - const visibilityStyle = - (!signedRank || undefined) && - (props.headerHovered ? styles.hovered : styles.transparent); + const visibilityStyle = (!signedRank || undefined) && (props.headerHovered ? styles.hovered : styles.transparent); return ( - {signedRank >= 0 ? ( - - ) : ( - - )} + {signedRank >= 0 ? : } {sortRank > 1 && !props.hovered && {sortRank}} ); @@ -100,8 +85,7 @@ interface FilterButtonProps { function FilterButton(props: FilterButtonProps) { const { filterLevel, headerHovered, onClick } = props; - const visibilityStyle = - !filterLevel && (headerHovered ? styles.hovered : styles.transparent); + const visibilityStyle = !filterLevel && (headerHovered ? styles.hovered : styles.transparent); return ( ( - (props, ref) => { - const { - className, - label, - numeric, - sortSignedRank, - filterLevel, - onSortClick, - onFilterClick, - onContextMenu, - style, - } = props; +export const ColumnHeader = forwardRef((props, ref) => { + const { className, label, numeric, sortSignedRank, filterLevel, onSortClick, onFilterClick, onContextMenu, style } = + props; - const [hovered, setHovered] = useState(false); - const onHover = useCallback((evt: Event) => { - setHovered(evt.type === 'mouseenter'); - }, []); + const [hovered, setHovered] = useState(false); + const onHover = useCallback((evt: Event) => { + setHovered(evt.type === 'mouseenter'); + }, []); - const topmostDiv = useRef(); + const topmostDiv = useRef(); - const handleFilterClick = useMemo(() => { - if (!onFilterClick) { - return undefined; - } - return (evt: MouseEvent) => { - onFilterClick(evt); - }; - }, [onFilterClick]); + const handleFilterClick = useMemo(() => { + if (!onFilterClick) { + return undefined; + } + return (evt: MouseEvent) => { + onFilterClick(evt); + }; + }, [onFilterClick]); - return ( - // @ts-ignore it does not let us define Box with onMouseEnter/onMouseLeave attributes with 'div' I think, not sure though - - {/* we cheat here to get the _variable_ height */} - - {label} - - {onSortClick && ( - - )} - {handleFilterClick && ( - - )} + return ( + // @ts-ignore it does not let us define Box with onMouseEnter/onMouseLeave attributes with 'div' I think, not sure though + + {/* we cheat here to get the _variable_ height */} + + {label} - ); - } -); + {onSortClick && } + {handleFilterClick && ( + + )} + + ); +}); export default styled(ColumnHeader)({}); diff --git a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx index ff9cb5c0..1517d50d 100644 --- a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx @@ -61,11 +61,7 @@ export const noOpHelper = Object.freeze({ maintainsStats: false, initStat: () => undefined, updateStat: (colStat: ColStat, value: number) => {}, - accepts: ( - value: any, - userParams: any[] | undefined, - outerParams: any[] | undefined - ) => { + accepts: (value: any, userParams: any[] | undefined, outerParams: any[] | undefined) => { return true; }, }); @@ -77,26 +73,14 @@ const numericHelper = Object.freeze({ return { imin: null, imax: null }; }, updateStat: (colStat: ColStat, value: number) => { - if ( - colStat.imin === undefined || - colStat.imin === null || - colStat.imin > value - ) { + if (colStat.imin === undefined || colStat.imin === null || colStat.imin > value) { colStat.imin = value; } - if ( - colStat.imax === undefined || - colStat.imax === null || - colStat.imax < value - ) { + if (colStat.imax === undefined || colStat.imax === null || colStat.imax < value) { colStat.imax = value; } }, - accepts: ( - value: number, - userParams: any[] | undefined, - outerParams: any[] | undefined - ) => { + accepts: (value: number, userParams: any[] | undefined, outerParams: any[] | undefined) => { return true; }, }); @@ -115,11 +99,7 @@ export const collectibleHelper = Object.freeze({ m[cellValue] += 1; } }, - accepts: ( - value: number, - userParams: any[] | undefined, - outerParams: any[] | undefined - ) => { + accepts: (value: number, userParams: any[] | undefined, outerParams: any[] | undefined) => { return !userParams || userParams.some((v) => v === value); }, }); @@ -326,21 +306,19 @@ const groupAndSort = ( // @ts-ignore I don't know how to fix this one const groups = groupRows(groupingColumnsCount, columns, indexedArray); - const interGroupSortingComparator = - makeCompositeComparatorFromCodedColumns( - codedColumns, - columns, - (ar) => ar[0][0] - ); + const interGroupSortingComparator = makeCompositeComparatorFromCodedColumns( + codedColumns, + columns, + (ar) => ar[0][0] + ); groups.sort(interGroupSortingComparator); - const intraGroupSortingComparator = - makeCompositeComparatorFromCodedColumns( - codedColumns, - columns, - // @ts-ignore I don't know how to fix this one - (ar) => ar[0] - ); + const intraGroupSortingComparator = makeCompositeComparatorFromCodedColumns( + codedColumns, + columns, + // @ts-ignore I don't know how to fix this one + (ar) => ar[0] + ); indexedArray = []; groups.forEach((group: any) => { @@ -363,19 +341,11 @@ export class KeyedColumnsRowIndexer { versionSetter: ((version: number) => void) | null; - byColFilter: Record< - string, - { userParams?: any[]; outerParams?: any[] } - > | null; + byColFilter: Record | null; byRowFilter: ((row: RowProps) => boolean) | null; - delegatorCallback: - | (( - instance: KeyedColumnsRowIndexer, - callback: (input: any) => void - ) => void) - | null; + delegatorCallback: ((instance: KeyedColumnsRowIndexer, callback: (input: any) => void) => void) | null; filterVersion: number; @@ -544,24 +514,14 @@ export class KeyedColumnsRowIndexer { // Does not mutate any internal // returns an array of indexes in rows given to preFilter - makeGroupAndSortIndirector = ( - preFilteredRowPairs: FilteredRows | null, - columns: CustomColumnProps[] - ) => { + makeGroupAndSortIndirector = (preFilteredRowPairs: FilteredRows | null, columns: CustomColumnProps[]) => { if (!preFilteredRowPairs) { return null; } - const codedColumns = !this.sortingState - ? null - : codedColumnsFromKeyAndDirection(this.sortingState, columns); + const codedColumns = !this.sortingState ? null : codedColumnsFromKeyAndDirection(this.sortingState, columns); const groupingColumnsCount = this.groupingCount; - const indexedArray = groupAndSort( - preFilteredRowPairs, - codedColumns, - groupingColumnsCount, - columns - ); + const indexedArray = groupAndSort(preFilteredRowPairs, codedColumns, groupingColumnsCount, columns); return !indexedArray ? null : indexedArray.map((k) => k[1]); }; @@ -598,8 +558,7 @@ export class KeyedColumnsRowIndexer { this.lastUsedRank = 1; } else { const wasAtIdx = keyAndDirections.findIndex((p) => p[0] === colKey); - const wasFuzzyDir = - wasAtIdx < 0 ? 0 : keyAndDirections[wasAtIdx][1]; + const wasFuzzyDir = wasAtIdx < 0 ? 0 : keyAndDirections[wasAtIdx][1]; const wasSignDir = giveDirSignFor(wasFuzzyDir); if (change_way === ChangeWays.SIMPLE) { @@ -616,10 +575,7 @@ export class KeyedColumnsRowIndexer { this.sortingState?.splice(wasAtIdx, 1); } const nextSign = wasSignDir ? -wasSignDir : 1; - const nextKD: [string, string | undefined] = [ - colKey, - canonicalForSign(nextSign), - ]; + const nextKD: [string, string | undefined] = [colKey, canonicalForSign(nextSign)]; this.sortingState?.unshift(nextKD); } } else if (change_way === ChangeWays.TAIL) { @@ -629,9 +585,7 @@ export class KeyedColumnsRowIndexer { return false; } else if (!(this.isThreeState && wasSignDir === -1)) { // @ts-ignore could be null but hard to handle with such accesses - this.sortingState[wasAtIdx][1] = canonicalForSign( - -wasSignDir - ); + this.sortingState[wasAtIdx][1] = canonicalForSign(-wasSignDir); } else { this.sortingState?.splice(wasAtIdx, 1); } @@ -643,10 +597,7 @@ export class KeyedColumnsRowIndexer { ) { return false; } - this.sortingState?.splice(this.lastUsedRank - 1, 0, [ - colKey, - canonicalForSign(1), - ]); + this.sortingState?.splice(this.lastUsedRank - 1, 0, [colKey, canonicalForSign(1)]); } else if (!(this.isThreeState && wasSignDir === -1)) { // @ts-ignore could be null but hard to handle with such accesses this.sortingState[wasAtIdx][1] = canonicalForSign(-wasSignDir); @@ -700,11 +651,7 @@ export class KeyedColumnsRowIndexer { return colFilter[isForUser ? 'userParams' : 'outerParams']; }; - setColFilterParams = ( - colKey: string | null, - params: any[] | null, - isForUser: boolean - ) => { + setColFilterParams = (colKey: string | null, params: any[] | null, isForUser: boolean) => { if (!colKey) { if (params) { throw new Error('column key has to be defined'); @@ -718,8 +665,7 @@ export class KeyedColumnsRowIndexer { if (!this.byColFilter) { this.byColFilter = {}; } - let colFilter: { userParams?: any[]; outerParams?: any[] } = - this.byColFilter[colKey]; + let colFilter: { userParams?: any[]; outerParams?: any[] } = this.byColFilter[colKey]; if (!colFilter) { colFilter = {}; this.byColFilter[colKey] = colFilter; diff --git a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx index ed434e3a..5dcc6b88 100644 --- a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx +++ b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx @@ -8,49 +8,18 @@ /** * This class has been taken from 'Virtualized Table' example at https://material-ui.com/components/tables/ */ -import { - createRef, - PureComponent, - ReactElement, - ReactNode, - MouseEvent, - KeyboardEvent, - MutableRefObject, -} from 'react'; +import { createRef, PureComponent, ReactElement, ReactNode, MouseEvent, KeyboardEvent, MutableRefObject } from 'react'; import { FormattedMessage } from 'react-intl'; import clsx from 'clsx'; import memoize from 'memoize-one'; -import { - Autocomplete, - Chip, - IconButton, - Popover, - SxProps, - TableCell, - TextField, -} from '@mui/material'; +import { Autocomplete, Chip, IconButton, Popover, SxProps, TableCell, TextField } from '@mui/material'; import { styled } from '@mui/system'; import { GetApp as GetAppIcon } from '@mui/icons-material'; -import { - AutoSizer, - Column, - ColumnProps, - RowMouseEventHandlerParams, - Table, - TableCellProps, -} from 'react-virtualized'; +import { AutoSizer, Column, ColumnProps, RowMouseEventHandlerParams, Table, TableCellProps } from 'react-virtualized'; import CsvDownloader from 'react-csv-downloader'; import { OverflowableText } from '../OverflowableText/overflowable-text'; -import { - makeComposeClasses, - toNestedGlobalSelectors, -} from '../../utils/styles'; -import { - ChangeWays, - collectibleHelper, - getHelper, - KeyedColumnsRowIndexer, -} from './KeyedColumnsRowIndexer'; +import { makeComposeClasses, toNestedGlobalSelectors } from '../../utils/styles'; +import { ChangeWays, collectibleHelper, getHelper, KeyedColumnsRowIndexer } from './KeyedColumnsRowIndexer'; import { ColumnHeader } from './ColumnHeader'; function getTextWidth(text: any): number { @@ -126,8 +95,7 @@ const defaultTooltipSx = { }; //TODO do we need to export this to clients (index.js) ? -export const generateMuiVirtualizedTableClass = (className: string) => - `MuiVirtualizedTable-${className}`; +export const generateMuiVirtualizedTableClass = (className: string) => `MuiVirtualizedTable-${className}`; const composeClasses = makeComposeClasses(generateMuiVirtualizedTableClass); interface AmongChooserProps { @@ -156,14 +124,7 @@ const AmongChooser = (props: AmongChooserProps) => { renderInput={(props) => } renderTags={(val, getTagsProps) => { return val.map((code, index) => { - return ( - - ); + return ; }); }} /> @@ -208,10 +169,7 @@ export interface RowProps { notClickable?: boolean; } -const initIndexer = ( - props: CustomColumnProps, - versionSetter: (version: number) => void -) => { +const initIndexer = (props: CustomColumnProps, versionSetter: (version: number) => void) => { if (!props.sortable) { return null; } @@ -254,9 +212,7 @@ const reorderIndex = memoize( }; } - const highestCodedColumn = !indexer - ? 0 - : indexer.highestCodedColumn(columns); + const highestCodedColumn = !indexer ? 0 : indexer.highestCodedColumn(columns); if (sortFromProps && highestCodedColumn) { const colIdx = Math.abs(highestCodedColumn) - 1; let reorderedIndex = sortFromProps( @@ -279,17 +235,8 @@ const reorderIndex = memoize( } } if (indexer) { - const prefiltered = preFilterData( - columns, - rows, - filterFromProps, - indexer, - indirectionVersion - ); - const reorderedIndex = indexer.makeGroupAndSortIndirector( - prefiltered, - columns - ); + const prefiltered = preFilterData(columns, rows, filterFromProps, indexer, indirectionVersion); + const reorderedIndex = indexer.makeGroupAndSortIndirector(prefiltered, columns); return makeIndexRecord(reorderedIndex, rows); } if (filterFromProps) { @@ -333,10 +280,7 @@ export interface MuiVirtualizedTableState { }; } -class MuiVirtualizedTable extends PureComponent< - MuiVirtualizedTableProps, - MuiVirtualizedTableState -> { +class MuiVirtualizedTable extends PureComponent { static defaultProps = { headerHeight: DEFAULT_HEADER_HEIGHT, rowHeight: DEFAULT_ROW_HEIGHT, @@ -363,10 +307,7 @@ class MuiVirtualizedTable extends PureComponent< rootMargin: '0px', threshold: 0.1, }; - this.observer = new IntersectionObserver( - this._computeHeaderSize, - options - ); + this.observer = new IntersectionObserver(this._computeHeaderSize, options); this.dropDownVisible = false; this.state = { headerHeight: this.props.headerHeight, @@ -383,10 +324,7 @@ class MuiVirtualizedTable extends PureComponent< }; componentDidUpdate(oldProps: MuiVirtualizedTableProps) { - if ( - oldProps.indexer !== this.props.indexer || - oldProps.sortable !== this.props.sortable - ) { + if (oldProps.indexer !== this.props.indexer || oldProps.sortable !== this.props.sortable) { this.setState((state) => { return { indexer: initIndexer(this.props, this.setVersion), @@ -433,10 +371,7 @@ class MuiVirtualizedTable extends PureComponent< /* calculate the header (and min size if exists) * NB : ignores the possible icons */ - let size = Math.max( - col.minWidth || 0, - this.computeDataWidth(col.label) - ); + let size = Math.max(col.minWidth || 0, this.computeDataWidth(col.label)); /* calculate for each row the width, and keep the max */ for (let i = 0; i < rows.length; ++i) { const gotRow = rowGetter(i); @@ -481,8 +416,7 @@ class MuiVirtualizedTable extends PureComponent< popoverAnchorEl: null, popoverColKey: null, deferredFilterChange: null, - indirectionVersion: - state.indirectionVersion + (bumpsVersion ? 1 : 0), + indirectionVersion: state.indirectionVersion + (bumpsVersion ? 1 : 0), }; }); }; @@ -525,9 +459,7 @@ class MuiVirtualizedTable extends PureComponent< setValue={(newVal) => { this.onFilterParamsChange(newVal, colKey); }} - onDropDownVisibility={(visible) => - (this.dropDownVisible = visible) - } + onDropDownVisibility={(visible) => (this.dropDownVisible = visible)} /> ); }; @@ -553,9 +485,7 @@ class MuiVirtualizedTable extends PureComponent< this.setState({ deferredFilterChange: { newVal: newVal, colKey }, }); - } else if ( - this.state.indexer?.setColFilterUserParams(colKey, nonEmpty) - ) { + } else if (this.state.indexer?.setColFilterUserParams(colKey, nonEmpty)) { this.setState({ indirectionVersion: this.state.indirectionVersion + 1, }); @@ -587,11 +517,7 @@ class MuiVirtualizedTable extends PureComponent< } }; - filterClickHandler = ( - _: MouseEvent, - target: Element | undefined, - columnIndex: number - ) => { + filterClickHandler = (_: MouseEvent, target: Element | undefined, columnIndex: number) => { // ColumnHeader to (header) TableCell const retargeted = target?.parentNode ?? target; @@ -600,13 +526,7 @@ class MuiVirtualizedTable extends PureComponent< this.openPopover(retargeted, colKey); }; - sortableHeader = ({ - label, - columnIndex, - }: { - label: string; - columnIndex: number; - }) => { + sortableHeader = ({ label, columnIndex }: { label: string; columnIndex: number }) => { const { columns } = this.props; const indexer = this.state.indexer; const colKey = columns[columnIndex].dataKey; @@ -614,22 +534,14 @@ class MuiVirtualizedTable extends PureComponent< const userParams = indexer?.getColFilterUserParams(colKey); const numeric = columns[columnIndex].numeric; - const prefiltered = preFilterData( - columns, - this.props.rows, - this.props.filter, - indexer, - indexer?.filterVersion - ); + const prefiltered = preFilterData(columns, this.props.rows, this.props.filter, indexer, indexer?.filterVersion); const colStat = prefiltered?.colsStats?.[colKey]; let filterLevel = 0; if (userParams?.length) { filterLevel += 1; if (!colStat?.seen) { filterLevel += 2; - } else if ( - userParams.filter((v: string) => !colStat.seen[v]).length - ) { + } else if (userParams.filter((v: string) => !colStat.seen[v]).length) { filterLevel += 2; } } @@ -651,10 +563,7 @@ class MuiVirtualizedTable extends PureComponent< sortSignedRank={signedRank} filterLevel={filterLevel} numeric={numeric ?? false} - onSortClick={( - ev: MouseEvent, - name?: Element - ) => { + onSortClick={(ev: MouseEvent, name?: Element) => { this.sortClickHandler(ev, name, columnIndex); }} onFilterClick={onFilterClick} @@ -687,64 +596,45 @@ class MuiVirtualizedTable extends PureComponent< composeClasses(classes, cssFlexContainer), index % 2 === 0 && composeClasses(classes, cssRowBackgroundDark), index % 2 !== 0 && composeClasses(classes, cssRowBackgroundLight), - rowGetter(index)?.notClickable === true && - composeClasses(classes, cssNoClick), // Allow to define a row as not clickable + rowGetter(index)?.notClickable === true && composeClasses(classes, cssNoClick), // Allow to define a row as not clickable { - [composeClasses(classes, cssTableRowHover)]: - index !== -1 && onRowClick != null, + [composeClasses(classes, cssTableRowHover)]: index !== -1 && onRowClick != null, } ); }; onClickableRowClick = (event: RowMouseEventHandlerParams) => { - if ( - event.rowData?.notClickable !== true || - event.event?.shiftKey || - event.event?.ctrlKey - ) { + if (event.rowData?.notClickable !== true || event.event?.shiftKey || event.event?.ctrlKey) { //@ts-ignore onRowClick is possibly undefined this.props.onRowClick(event); } }; cellRenderer = ({ cellData, columnIndex, rowIndex }: TableCellProps) => { - const { columns, classes, rowHeight, onCellClick, rows, tooltipSx } = - this.props; + const { columns, classes, rowHeight, onCellClick, rows, tooltipSx } = this.props; - let displayedValue = this.getDisplayValue( - columns[columnIndex], - cellData - ); + let displayedValue = this.getDisplayValue(columns[columnIndex], cellData); return ( { if ( onCellClick && @@ -772,10 +662,7 @@ class MuiVirtualizedTable extends PureComponent< displayedValue = cellData; } else if (isNaN(cellData)) { displayedValue = ''; - } else if ( - column.fractionDigits === undefined || - column.fractionDigits === 0 - ) { + } else if (column.fractionDigits === undefined || column.fractionDigits === 0) { displayedValue = Math.round(cellData); } else { displayedValue = Number(cellData).toFixed(column.fractionDigits); @@ -823,9 +710,7 @@ class MuiVirtualizedTable extends PureComponent< )} variant="head" style={{ height: this.state.headerHeight }} - align={ - columns[columnIndex].numeric || false ? 'right' : 'left' - } + align={columns[columnIndex].numeric || false ? 'right' : 'left'} ref={(e: Element) => this._registerObserver(e)} > {this.props.sortable && this.state.indexer @@ -867,23 +752,15 @@ class MuiVirtualizedTable extends PureComponent< this.onClickableRowClick } rowCount={reorderedIndex?.length ?? otherProps.rows.length} - rowClassName={({ index }) => - this.getRowClassName({ index, rowGetter }) - } + rowClassName={({ index }) => this.getRowClassName({ index, rowGetter })} rowGetter={({ index }) => rowGetter(index)} > {otherProps.columns.map(({ dataKey, ...other }, index) => { return ( { let tempHeaders: { displayName: string; id: string }[] = []; columns.forEach((col: CustomColumnProps) => { - if ( - exportCSVDataKeys !== undefined && - exportCSVDataKeys.find((el: string) => el === col.dataKey) - ) { + if (exportCSVDataKeys !== undefined && exportCSVDataKeys.find((el: string) => el === col.dataKey)) { tempHeaders.push({ displayName: col.label, id: col.dataKey, @@ -962,15 +835,8 @@ class MuiVirtualizedTable extends PureComponent< this.props.sort ); - const sizes = this.sizes( - this.props.columns, - this.props.rows, - rowGetter - ); - const csvHeaders = this.csvHeaders( - this.props.columns, - this.props.exportCSVDataKeys - ); + const sizes = this.sizes(this.props.columns, this.props.rows, rowGetter); + const csvHeaders = this.csvHeaders(this.props.columns, this.props.exportCSVDataKeys); return (
    - - + + @@ -1006,15 +865,7 @@ class MuiVirtualizedTable extends PureComponent< )}
    - {({ height, width }) => - this.makeSizedTable( - height, - width, - sizes, - viewIndexToModel, - rowGetter - ) - } + {({ height, width }) => this.makeSizedTable(height, width, sizes, viewIndexToModel, rowGetter)}
    {this.state.popoverAnchorEl && ( @@ -1041,9 +892,6 @@ class MuiVirtualizedTable extends PureComponent< } } -const nestedGlobalSelectorsStyles = toNestedGlobalSelectors( - defaultStyles, - generateMuiVirtualizedTableClass -); +const nestedGlobalSelectorsStyles = toNestedGlobalSelectors(defaultStyles, generateMuiVirtualizedTableClass); export default styled(MuiVirtualizedTable)(nestedGlobalSelectorsStyles); diff --git a/src/components/MuiVirtualizedTable/index.ts b/src/components/MuiVirtualizedTable/index.ts index e9eee2f3..f238d56d 100644 --- a/src/components/MuiVirtualizedTable/index.ts +++ b/src/components/MuiVirtualizedTable/index.ts @@ -5,8 +5,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { - default as MuiVirtualizedTable, - generateMuiVirtualizedTableClass, -} from './MuiVirtualizedTable'; +export { default as MuiVirtualizedTable, generateMuiVirtualizedTableClass } from './MuiVirtualizedTable'; export { KeyedColumnsRowIndexer, ChangeWays } from './KeyedColumnsRowIndexer'; diff --git a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx index f2dce50e..1c1bb248 100644 --- a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx +++ b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx @@ -62,18 +62,11 @@ function MultipleSelectionDialog({ - } + label={} control={ } @@ -90,14 +83,8 @@ function MultipleSelectionDialog({ label={label} control={ - handleOptionSelection( - optionId - ) - } + checked={selectedIds.includes(optionId)} + onChange={() => handleOptionSelection(optionId)} /> } /> diff --git a/src/components/OverflowableText/overflowable-text.tsx b/src/components/OverflowableText/overflowable-text.tsx index cf313425..78847d72 100644 --- a/src/components/OverflowableText/overflowable-text.tsx +++ b/src/components/OverflowableText/overflowable-text.tsx @@ -4,14 +4,7 @@ * 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 { - ReactElement, - useCallback, - useLayoutEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { ReactElement, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { Box, BoxProps, SxProps, Theme, Tooltip, styled } from '@mui/material'; import { Style } from 'node:util'; @@ -57,10 +50,7 @@ export const OverflowableText = styled( }: OverflowableTextProps) => { const element = useRef(); - const isMultiLine = useMemo( - () => maxLineCount && maxLineCount > 1, - [maxLineCount] - ); + const isMultiLine = useMemo(() => maxLineCount && maxLineCount > 1, [maxLineCount]); const [overflowed, setOverflowed] = useState(false); @@ -70,13 +60,9 @@ export const OverflowableText = styled( } if (isMultiLine) { - setOverflowed( - element.current.scrollHeight > element.current.clientHeight - ); + setOverflowed(element.current.scrollHeight > element.current.clientHeight); } else { - setOverflowed( - element.current.scrollWidth > element.current.clientWidth - ); + setOverflowed(element.current.scrollWidth > element.current.clientWidth); } }, [isMultiLine, setOverflowed, element]); @@ -114,11 +100,7 @@ export const OverflowableText = styled( {...props} ref={element} className={className} - sx={ - isMultiLine - ? multilineOverflowStyle(maxLineCount) - : overflowStyle.overflow - } + sx={isMultiLine ? multilineOverflowStyle(maxLineCount) : overflowStyle.overflow} > {children || text} diff --git a/src/components/ReportViewer/filter-button.tsx b/src/components/ReportViewer/filter-button.tsx index 14613766..1071b654 100644 --- a/src/components/ReportViewer/filter-button.tsx +++ b/src/components/ReportViewer/filter-button.tsx @@ -30,9 +30,7 @@ const styles = { export interface FilterButtonProps { selectedItems: Record; - setSelectedItems: ( - func: (items: Record) => Record - ) => void; + setSelectedItems: (func: (items: Record) => Record) => void; } /** @@ -42,10 +40,7 @@ export interface FilterButtonProps { * @param {Function} setSelectedItems - Setter needed to update the list underlying data */ -export function FilterButton({ - selectedItems, - setSelectedItems, -}: FilterButtonProps) { +export function FilterButton({ selectedItems, setSelectedItems }: FilterButtonProps) { const [initialState] = useState(selectedItems); const [anchorEl, setAnchorEl] = useState(null); @@ -67,9 +62,7 @@ export function FilterButton({ }; const isInitialStateModified = useMemo(() => { - return Object.keys(selectedItems).some( - (key) => initialState[key] !== selectedItems[key] - ); + return Object.keys(selectedItems).some((key) => initialState[key] !== selectedItems[key]); }, [initialState, selectedItems]); return ( diff --git a/src/components/ReportViewer/log-report-item.ts b/src/components/ReportViewer/log-report-item.ts index 37a7e068..f3a05614 100644 --- a/src/components/ReportViewer/log-report-item.ts +++ b/src/components/ReportViewer/log-report-item.ts @@ -16,35 +16,22 @@ export default class LogReportItem { log: string; - static resolveTemplateMessage( - templateMessage: string, - templateValues: Record - ) { + static resolveTemplateMessage(templateMessage: string, templateValues: Record) { const templateVars: Record = {}; Object.entries(templateValues).forEach(([key, value]) => { templateVars[key] = value.value; }); - return templateMessage.replace( - /\${([^{}]*)}/g, - function resolveTemplate(a, b) { - const r = templateVars[b]; - return typeof r === 'string' || typeof r === 'number' - ? r.toString() - : a; - } - ); + return templateMessage.replace(/\${([^{}]*)}/g, function resolveTemplate(a, b) { + const r = templateVars[b]; + return typeof r === 'string' || typeof r === 'number' ? r.toString() : a; + }); } constructor(jsonReport: SubReport, reportId: string) { this.key = jsonReport.reportKey; - this.log = LogReportItem.resolveTemplateMessage( - jsonReport.defaultMessage, - jsonReport.values - ); + this.log = LogReportItem.resolveTemplateMessage(jsonReport.defaultMessage, jsonReport.values); this.reportId = reportId; - this.severity = LogReportItem.initSeverity( - jsonReport.values.reportSeverity as unknown as ReportValue - ); + this.severity = LogReportItem.initSeverity(jsonReport.values.reportSeverity as unknown as ReportValue); } getLog() { @@ -78,9 +65,7 @@ export default class LogReportItem { } Object.values(LogSeverities).some((value) => { - const severityFound = (jsonSeverity.value as string).includes( - value.name - ); + const severityFound = (jsonSeverity.value as string).includes(value.name); if (severityFound) { severity = value; } diff --git a/src/components/ReportViewer/log-report.ts b/src/components/ReportViewer/log-report.ts index cbffeb9b..c10b0111 100644 --- a/src/components/ReportViewer/log-report.ts +++ b/src/components/ReportViewer/log-report.ts @@ -27,10 +27,7 @@ export default class LogReport { constructor(jsonReporter: Report, parentReportId?: string) { this.id = uuid4(); this.key = jsonReporter.taskKey; - this.title = LogReportItem.resolveTemplateMessage( - jsonReporter.defaultName, - jsonReporter.taskValues - ); + this.title = LogReportItem.resolveTemplateMessage(jsonReporter.defaultName, jsonReporter.taskValues); this.subReports = []; this.logs = []; this.parentReportId = parentReportId; @@ -54,23 +51,16 @@ export default class LogReport { } getAllLogs(): LogReportItem[] { - return this.getLogs().concat( - this.getSubReports().flatMap((r) => r.getAllLogs()) - ); + return this.getLogs().concat(this.getSubReports().flatMap((r) => r.getAllLogs())); } init(jsonReporter: Report) { - jsonReporter.subReporters.map((value) => - this.subReports.push(new LogReport(value, this.id)) - ); - jsonReporter.reports.map((value) => - this.logs.push(new LogReportItem(value, this.id)) - ); + jsonReporter.subReporters.map((value) => this.subReports.push(new LogReport(value, this.id))); + jsonReporter.reports.map((value) => this.logs.push(new LogReportItem(value, this.id))); } getHighestSeverity(currentSeverity = LogSeverities.UNKNOWN): LogSeverity { - const reduceFct = (p: LogSeverity, c: LogSeverity) => - p.level < c.level ? c : p; + const reduceFct = (p: LogSeverity, c: LogSeverity) => (p.level < c.level ? c : p); const highestSeverity = this.getLogs() .map((r) => r.getSeverity()) diff --git a/src/components/ReportViewer/log-table.tsx b/src/components/ReportViewer/log-table.tsx index 86945628..49335a32 100644 --- a/src/components/ReportViewer/log-table.tsx +++ b/src/components/ReportViewer/log-table.tsx @@ -26,8 +26,7 @@ const styles = { // https://github.com/bvaughn/react-virtualized/issues/454 '& .ReactVirtualized__Table__headerRow': { flip: false, - paddingRight: - theme.direction === 'rtl' ? '0 !important' : undefined, + paddingRight: theme.direction === 'rtl' ? '0 !important' : undefined, }, }), header: { variant: 'header' }, @@ -39,17 +38,10 @@ export interface LogTableProps { logs: LogReportItem[]; onRowClick: (data: any) => void; selectedSeverity: Record; - setSelectedSeverity: ( - func: (items: Record) => Record - ) => void; + setSelectedSeverity: (func: (items: Record) => Record) => void; } -function LogTable({ - logs, - onRowClick, - selectedSeverity, - setSelectedSeverity, -}: LogTableProps) { +function LogTable({ logs, onRowClick, selectedSeverity, setSelectedSeverity }: LogTableProps) { const intl = useIntl(); const theme = useTheme(); @@ -75,26 +67,17 @@ function LogTable({ const COLUMNS_DEFINITIONS = [ { - label: intl - .formatMessage({ id: 'report_viewer/severity' }) - .toUpperCase(), + label: intl.formatMessage({ id: 'report_viewer/severity' }).toUpperCase(), id: 'severity', dataKey: 'severity', width: SEVERITY_COLUMN_FIXED_WIDTH, maxWidth: SEVERITY_COLUMN_FIXED_WIDTH, minWidth: SEVERITY_COLUMN_FIXED_WIDTH, cellRenderer: severityCellRender, - extra: ( - - ), + extra: , }, { - label: intl - .formatMessage({ id: 'report_viewer/message' }) - .toUpperCase(), + label: intl.formatMessage({ id: 'report_viewer/message' }).toUpperCase(), id: 'message', dataKey: 'message', width: SEVERITY_COLUMN_FIXED_WIDTH, @@ -142,10 +125,7 @@ function LogTable({ const filter = useCallback( (row: { severity: any }) => { return ( - row.severity && - Object.entries(selectedSeverity).some( - ([key, value]) => key === row.severity && value - ) + row.severity && Object.entries(selectedSeverity).some(([key, value]) => key === row.severity && value) ); }, [selectedSeverity] diff --git a/src/components/ReportViewer/multi-select-list.tsx b/src/components/ReportViewer/multi-select-list.tsx index 2522cc3a..8a061b91 100644 --- a/src/components/ReportViewer/multi-select-list.tsx +++ b/src/components/ReportViewer/multi-select-list.tsx @@ -5,14 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - Checkbox, - CheckboxProps, - FormControlLabel, - Menu, - MenuItem, - MenuProps, -} from '@mui/material'; +import { Checkbox, CheckboxProps, FormControlLabel, Menu, MenuItem, MenuProps } from '@mui/material'; const styles = { label: { @@ -40,12 +33,7 @@ export interface MultiSelectListProps { * @param {Object} anchor - Determines where the menu will appear on screen */ -export function MultiSelectList({ - selectedItems, - handleChange, - handleClose, - anchor, -}: MultiSelectListProps) { +export function MultiSelectList({ selectedItems, handleChange, handleClose, anchor }: MultiSelectListProps) { const open = Boolean(anchor); return ( @@ -53,13 +41,7 @@ export function MultiSelectList({ return ( - } + control={} label={key} sx={styles.label} /> diff --git a/src/components/ReportViewer/report-item.tsx b/src/components/ReportViewer/report-item.tsx index d65bbf3b..c320bbf2 100644 --- a/src/components/ReportViewer/report-item.tsx +++ b/src/components/ReportViewer/report-item.tsx @@ -34,19 +34,16 @@ const styles = { color: 'var(--tree-view-color)', }, // same as mui v4 behavior on label text only right after clicking in contrast to after moving away with arrow keys. - '&.Mui-selected .MuiTreeItem-label:hover, &.Mui-selected.Mui-focused .MuiTreeItem-label': - { - borderRadius: theme.spacing(2), - backgroundColor: alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + - theme.palette.action.hoverOpacity - ), - }, - '&.Mui-focused .MuiTreeItem-label, &:hover .MuiTreeItem-label, &Mui-selected .MuiTreeItem-label': - { - backgroundColor: 'transparent', - }, + '&.Mui-selected .MuiTreeItem-label:hover, &.Mui-selected.Mui-focused .MuiTreeItem-label': { + borderRadius: theme.spacing(2), + backgroundColor: alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity + ), + }, + '&.Mui-focused .MuiTreeItem-label, &:hover .MuiTreeItem-label, &Mui-selected .MuiTreeItem-label': { + backgroundColor: 'transparent', + }, }), group: (theme: Theme) => ({ marginLeft: '10px', @@ -108,13 +105,7 @@ function ReportItem(props: PropsWithChildren) { '& .MuiTreeItem-label': styles.label, }} label={ - +
  • diff --git a/src/components/dialogs/description-modification-dialog.tsx b/src/components/dialogs/description-modification-dialog.tsx index da796f36..2db39941 100644 --- a/src/components/dialogs/description-modification-dialog.tsx +++ b/src/components/dialogs/description-modification-dialog.tsx @@ -20,16 +20,11 @@ export interface IDescriptionModificationDialog { description: string; open: boolean; onClose: () => void; - updateElement: ( - uuid: string, - data: Record - ) => Promise; + updateElement: (uuid: string, data: Record) => Promise; } const schema = yup.object().shape({ - [FieldConstants.DESCRIPTION]: yup - .string() - .max(500, 'descriptionLimitError'), + [FieldConstants.DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'), }); function DescriptionModificationDialog({ @@ -62,8 +57,7 @@ function DescriptionModificationDialog({ const onSubmit = useCallback( (data: { description: string }) => { updateElement(elementUuid, { - [FieldConstants.DESCRIPTION]: - data[FieldConstants.DESCRIPTION].trim(), + [FieldConstants.DESCRIPTION]: data[FieldConstants.DESCRIPTION].trim(), }).catch((error: any) => { snackError({ messageTxt: error.message, diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index 6d72a4a8..684d386e 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -46,15 +46,9 @@ function ModifyElementSelection(props: ModifyElementSelectionProps) { useEffect(() => { if (directory) { - directorySvc - .fetchDirectoryElementPath(directory) - .then((res: any) => { - setActiveDirectoryName( - res - .map((element: any) => element.elementName.trim()) - .join('/') - ); - }); + directorySvc.fetchDirectoryElementPath(directory).then((res: any) => { + setActiveDirectoryName(res.map((element: any) => element.elementName.trim()).join('/')); + }); } }, [directory]); diff --git a/src/components/dialogs/popup-confirmation-dialog.tsx b/src/components/dialogs/popup-confirmation-dialog.tsx index 26ed036d..a3166667 100644 --- a/src/components/dialogs/popup-confirmation-dialog.tsx +++ b/src/components/dialogs/popup-confirmation-dialog.tsx @@ -30,13 +30,8 @@ function PopupConfirmationDialog({ handlePopupConfirmation, }: PopupConfirmationDialogProps) { return ( - - - Confirmation - + + Confirmation diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index be22c5af..dffc3faf 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -5,21 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useState, -} from 'react'; +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { UUID } from 'crypto'; import FieldConstants from '../../../utils/field-constants'; -import { - backToFrontTweak, - frontToBackTweak, -} from './criteria-based-filter-utils'; +import { backToFrontTweak, frontToBackTweak } from './criteria-based-filter-utils'; import CustomMuiDialog from '../../dialogs/custom-mui-dialog'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { criteriaBasedFilterSchema } from './criteria-based-filter-form'; @@ -64,9 +55,7 @@ interface CriteriaBasedFilterEditionDialogProps { broadcastChannel: BroadcastChannel; getFilterById: (id: string) => Promise; selectionForCopy: SelectionCopy; - setSelelectionForCopy: ( - selection: SelectionCopy - ) => Dispatch>; + setSelelectionForCopy: (selection: SelectionCopy) => Dispatch>; activeDirectory?: UUID; language?: GsLangUser; } @@ -109,8 +98,7 @@ function CriteriaBasedFilterEditionDialog({ setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ [FieldConstants.NAME]: name, - [FieldConstants.FILTER_TYPE]: - FilterType.CRITERIA_BASED.id, + [FieldConstants.FILTER_TYPE]: FilterType.CRITERIA_BASED.id, ...backToFrontTweak(response), }); }) @@ -127,10 +115,7 @@ function CriteriaBasedFilterEditionDialog({ const onSubmit = useCallback( (filterForm: any) => { exploreSvc - .saveFilter( - frontToBackTweak(id, filterForm), - filterForm[FieldConstants.NAME] - ) + .saveFilter(frontToBackTweak(id, filterForm), filterForm[FieldConstants.NAME]) .then(() => { if (selectionForCopy.sourceItemUuid === id) { setSelelectionForCopy(noSelectionForCopy); @@ -145,13 +130,7 @@ function CriteriaBasedFilterEditionDialog({ }); }); }, - [ - broadcastChannel, - id, - selectionForCopy.sourceItemUuid, - snackError, - setSelelectionForCopy, - ] + [broadcastChannel, id, selectionForCopy.sourceItemUuid, snackError, setSelelectionForCopy] ); const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; diff --git a/src/components/filter/criteria-based/criteria-based-filter-form.tsx b/src/components/filter/criteria-based/criteria-based-filter-form.tsx index ab9ca4fa..f21b7481 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-form.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-form.tsx @@ -5,16 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Grid from '@mui/material/Grid'; -import FilterProperties, { - filterPropertiesYupSchema, -} from './filter-properties'; +import FilterProperties, { filterPropertiesYupSchema } from './filter-properties'; import FieldConstants from '../../../utils/field-constants'; import yup from '../../../utils/yup-config'; import CriteriaBasedForm from './criteria-based-form'; -import { - getCriteriaBasedFormData, - getCriteriaBasedSchema, -} from './criteria-based-filter-utils'; +import { getCriteriaBasedFormData, getCriteriaBasedSchema } from './criteria-based-filter-utils'; import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; import { FreePropertiesTypes } from './filter-free-properties'; @@ -34,11 +29,7 @@ function CriteriaBasedFilterForm() { diff --git a/src/components/filter/criteria-based/criteria-based-filter-utils.ts b/src/components/filter/criteria-based/criteria-based-filter-utils.ts index a1e9a1f4..05c0eb27 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-utils.ts +++ b/src/components/filter/criteria-based/criteria-based-filter-utils.ts @@ -6,12 +6,7 @@ */ import FieldConstants from '../../../utils/field-constants'; -import { - PROPERTY_NAME, - PROPERTY_VALUES, - PROPERTY_VALUES_1, - PROPERTY_VALUES_2, -} from './filter-property'; +import { PROPERTY_NAME, PROPERTY_VALUES, PROPERTY_VALUES_1, PROPERTY_VALUES_2 } from './filter-property'; import { FilterType } from '../constants/filter-constants'; import { PredefinedProperties } from '../../../utils/types'; import yup from '../../../utils/yup-config'; @@ -34,36 +29,26 @@ export const getCriteriaBasedSchema = (extraFields: any) => ({ ...extraFields, }), }); -export const getCriteriaBasedFormData = ( - criteriaValues: any, - extraFields: any -) => ({ +export const getCriteriaBasedFormData = (criteriaValues: any, extraFields: any) => ({ [FieldConstants.CRITERIA_BASED]: { - [FieldConstants.COUNTRIES]: - criteriaValues?.[FieldConstants.COUNTRIES] ?? [], - [FieldConstants.COUNTRIES_1]: - criteriaValues?.[FieldConstants.COUNTRIES_1] ?? [], - [FieldConstants.COUNTRIES_2]: - criteriaValues?.[FieldConstants.COUNTRIES_2] ?? [], + [FieldConstants.COUNTRIES]: criteriaValues?.[FieldConstants.COUNTRIES] ?? [], + [FieldConstants.COUNTRIES_1]: criteriaValues?.[FieldConstants.COUNTRIES_1] ?? [], + [FieldConstants.COUNTRIES_2]: criteriaValues?.[FieldConstants.COUNTRIES_2] ?? [], ...getRangeInputDataForm( FieldConstants.NOMINAL_VOLTAGE, - criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE] ?? - DEFAULT_RANGE_VALUE + criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE] ?? DEFAULT_RANGE_VALUE ), ...getRangeInputDataForm( FieldConstants.NOMINAL_VOLTAGE_1, - criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_1] ?? - DEFAULT_RANGE_VALUE + criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_1] ?? DEFAULT_RANGE_VALUE ), ...getRangeInputDataForm( FieldConstants.NOMINAL_VOLTAGE_2, - criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_2] ?? - DEFAULT_RANGE_VALUE + criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_2] ?? DEFAULT_RANGE_VALUE ), ...getRangeInputDataForm( FieldConstants.NOMINAL_VOLTAGE_3, - criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_3] ?? - DEFAULT_RANGE_VALUE + criteriaValues?.[FieldConstants.NOMINAL_VOLTAGE_3] ?? DEFAULT_RANGE_VALUE ), ...extraFields, }, @@ -127,55 +112,33 @@ export const backToFrontTweak = (response: any) => { }); const ret = { - [FieldConstants.EQUIPMENT_TYPE]: - response[FieldConstants.EQUIPMENT_TYPE], + [FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE], ...getCriteriaBasedFormData(response.equipmentFilterForm, { - [FieldConstants.ENERGY_SOURCE]: - response.equipmentFilterForm[FieldConstants.ENERGY_SOURCE], - [FreePropertiesTypes.SUBSTATION_FILTER_PROPERTIES]: - filterSubstationProperties, + [FieldConstants.ENERGY_SOURCE]: response.equipmentFilterForm[FieldConstants.ENERGY_SOURCE], + [FreePropertiesTypes.SUBSTATION_FILTER_PROPERTIES]: filterSubstationProperties, [FreePropertiesTypes.FREE_FILTER_PROPERTIES]: filterFreeProperties, }), }; return ret; }; -function isNominalVoltageEmpty( - nominalVoltage: Record -): boolean { - return ( - nominalVoltage[FieldConstants.VALUE_1] === null && - nominalVoltage[FieldConstants.VALUE_2] === null - ); +function isNominalVoltageEmpty(nominalVoltage: Record): boolean { + return nominalVoltage[FieldConstants.VALUE_1] === null && nominalVoltage[FieldConstants.VALUE_2] === null; } // The server expect them to be null if the user don't fill them, unlike contingency list function cleanNominalVoltages(formValues: any) { const cleanedFormValues = { ...formValues }; - if ( - isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE]) - ) { + if (isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE])) { cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE] = null; } - if ( - isNominalVoltageEmpty( - cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_1] - ) - ) { + if (isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_1])) { cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_1] = null; } - if ( - isNominalVoltageEmpty( - cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_2] - ) - ) { + if (isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_2])) { cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_2] = null; } - if ( - isNominalVoltageEmpty( - cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_3] - ) - ) { + if (isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_3])) { cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_3] = null; } return cleanedFormValues; @@ -194,9 +157,7 @@ function cleanNominalVoltages(formValues: any) { */ export const frontToBackTweak = (id?: string, filter?: any) => { const filterSubstationProperties = - filter[FieldConstants.CRITERIA_BASED][ - FreePropertiesTypes.SUBSTATION_FILTER_PROPERTIES - ]; + filter[FieldConstants.CRITERIA_BASED][FreePropertiesTypes.SUBSTATION_FILTER_PROPERTIES]; const ret = { id, type: FilterType.CRITERIA_BASED.id, @@ -230,10 +191,7 @@ export const frontToBackTweak = (id?: string, filter?: any) => { eff.freeProperties1 = props1; eff.freeProperties2 = props2; - const filterFreeProperties = - filter[FieldConstants.CRITERIA_BASED][ - FreePropertiesTypes.FREE_FILTER_PROPERTIES - ]; + const filterFreeProperties = filter[FieldConstants.CRITERIA_BASED][FreePropertiesTypes.FREE_FILTER_PROPERTIES]; // in the back end we store everything in a field called equipmentFilterForm delete eff[FreePropertiesTypes.FREE_FILTER_PROPERTIES]; const freeProps: any = {}; diff --git a/src/components/filter/criteria-based/criteria-based-form.tsx b/src/components/filter/criteria-based/criteria-based-form.tsx index c60be383..7746dc1e 100644 --- a/src/components/filter/criteria-based/criteria-based-form.tsx +++ b/src/components/filter/criteria-based/criteria-based-form.tsx @@ -19,10 +19,7 @@ export interface CriteriaBasedFormProps { defaultValues: Record; } -function CriteriaBasedForm({ - equipments, - defaultValues, -}: CriteriaBasedFormProps) { +function CriteriaBasedForm({ equipments, defaultValues }: CriteriaBasedFormProps) { const { getValues, setValue } = useFormContext(); const { snackError } = useSnackMessage(); @@ -39,18 +36,12 @@ function CriteriaBasedForm({ }, [snackError, equipments, watchEquipmentType]); const openConfirmationPopup = () => { - return ( - JSON.stringify(getValues(FieldConstants.CRITERIA_BASED)) !== - JSON.stringify(defaultValues) - ); + return JSON.stringify(getValues(FieldConstants.CRITERIA_BASED)) !== JSON.stringify(defaultValues); }; const handleResetOnConfirmation = () => { Object.keys(defaultValues).forEach((field) => - setValue( - `${FieldConstants.CRITERIA_BASED}.${field}`, - defaultValues[field] - ) + setValue(`${FieldConstants.CRITERIA_BASED}.${field}`, defaultValues[field]) ); }; @@ -70,17 +61,15 @@ function CriteriaBasedForm({
    {watchEquipmentType && equipments[watchEquipmentType] && - equipments[watchEquipmentType].fields.map( - (equipment: any, index: number) => { - const EquipmentForm = equipment.renderer; - const uniqueKey = `${watchEquipmentType}-${index}`; - return ( - - - - ); - } - )} + equipments[watchEquipmentType].fields.map((equipment: any, index: number) => { + const EquipmentForm = equipment.renderer; + const uniqueKey = `${watchEquipmentType}-${index}`; + return ( + + + + ); + })} ); } diff --git a/src/components/filter/criteria-based/filter-free-properties.tsx b/src/components/filter/criteria-based/filter-free-properties.tsx index 18bac83f..0bb11fc8 100644 --- a/src/components/filter/criteria-based/filter-free-properties.tsx +++ b/src/components/filter/criteria-based/filter-free-properties.tsx @@ -33,16 +33,12 @@ interface FilterFreePropertiesProps { predefined: PredefinedProperties; } -function FilterFreeProperties({ - freePropertiesType, - predefined, -}: FilterFreePropertiesProps) { +function FilterFreeProperties({ freePropertiesType, predefined }: FilterFreePropertiesProps) { const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE, }); const isForLineOrHvdcLineSubstation = - (watchEquipmentType === Line.type || - watchEquipmentType === Hvdc.type) && + (watchEquipmentType === Line.type || watchEquipmentType === Hvdc.type) && freePropertiesType === FreePropertiesTypes.SUBSTATION_FILTER_PROPERTIES; const fieldName = `${FieldConstants.CRITERIA_BASED}.${freePropertiesType}`; @@ -75,17 +71,13 @@ function FilterFreeProperties({ : [{ name: PROPERTY_VALUES, label: 'PropertyValues' }]; const title = useMemo(() => { - return freePropertiesType === FreePropertiesTypes.FREE_FILTER_PROPERTIES - ? 'FreeProps' - : 'SubstationFreeProps'; + return freePropertiesType === FreePropertiesTypes.FREE_FILTER_PROPERTIES ? 'FreeProps' : 'SubstationFreeProps'; }, [freePropertiesType]); return ( <> - - {(formattedTitle) =>

    {formattedTitle}

    } -
    + {(formattedTitle) =>

    {formattedTitle}

    }
    {filterProperties.map((prop, index) => ( diff --git a/src/components/filter/criteria-based/filter-properties.tsx b/src/components/filter/criteria-based/filter-properties.tsx index 7bcf089d..ebd98780 100644 --- a/src/components/filter/criteria-based/filter-properties.tsx +++ b/src/components/filter/criteria-based/filter-properties.tsx @@ -22,15 +22,8 @@ import { import { areArrayElementsUnique } from '../../../utils/functions'; import FieldConstants from '../../../utils/field-constants'; import yup from '../../../utils/yup-config'; -import FilterFreeProperties, { - FreePropertiesTypes, -} from './filter-free-properties'; -import { - PROPERTY_NAME, - PROPERTY_VALUES, - PROPERTY_VALUES_1, - PROPERTY_VALUES_2, -} from './filter-property'; +import FilterFreeProperties, { FreePropertiesTypes } from './filter-free-properties'; +import { PROPERTY_NAME, PROPERTY_VALUES, PROPERTY_VALUES_1, PROPERTY_VALUES_2 } from './filter-property'; import usePredefinedProperties from '../../../hooks/predefined-properties-hook'; import { FilterType } from '../constants/filter-constants'; @@ -47,8 +40,7 @@ function propertyValuesTest( return true; } const equipmentType = rootLevelForm.value[FieldConstants.EQUIPMENT_TYPE]; - const isForLineOrHvdcLine = - equipmentType === Line.type || equipmentType === Hvdc.type; + const isForLineOrHvdcLine = equipmentType === Line.type || equipmentType === Hvdc.type; if (doublePropertyValues) { return isForLineOrHvdcLine ? values?.length! > 0 : true; } @@ -64,50 +56,36 @@ export const filterPropertiesYupSchema = { [PROPERTY_VALUES]: yup .array() .of(yup.string()) - .test( - 'can not be empty if not line', - 'YupRequired', - (values, context) => - propertyValuesTest(values, context, false) + .test('can not be empty if not line', 'YupRequired', (values, context) => + propertyValuesTest(values, context, false) ), [PROPERTY_VALUES_1]: yup .array() .of(yup.string()) - .test( - 'can not be empty if line', - 'YupRequired', - (values, context) => - propertyValuesTest(values, context, true) + .test('can not be empty if line', 'YupRequired', (values, context) => + propertyValuesTest(values, context, true) ), [PROPERTY_VALUES_2]: yup .array() .of(yup.string()) - .test( - 'can not be empty if line', - 'YupRequired', - (values, context) => - propertyValuesTest(values, context, true) + .test('can not be empty if line', 'YupRequired', (values, context) => + propertyValuesTest(values, context, true) ), }) ) - .test( - 'distinct names', - 'filterPropertiesNameUniquenessError', - (properties, context) => { - // with context.from[length - 1], we can access to the root fields of the form - const rootLevelForm = context.from![context.from!.length - 1]; - const filterType = - rootLevelForm.value[FieldConstants.FILTER_TYPE]; - if (filterType !== FilterType.CRITERIA_BASED.id) { - // we don't test if we are not in a criteria based form - return true; - } - const names = properties! // never null / undefined - .filter((prop) => !!prop[PROPERTY_NAME]) - .map((prop) => prop[PROPERTY_NAME]); - return areArrayElementsUnique(names); + .test('distinct names', 'filterPropertiesNameUniquenessError', (properties, context) => { + // with context.from[length - 1], we can access to the root fields of the form + const rootLevelForm = context.from![context.from!.length - 1]; + const filterType = rootLevelForm.value[FieldConstants.FILTER_TYPE]; + if (filterType !== FilterType.CRITERIA_BASED.id) { + // we don't test if we are not in a criteria based form + return true; } - ), + const names = properties! // never null / undefined + .filter((prop) => !!prop[PROPERTY_NAME]) + .map((prop) => prop[PROPERTY_NAME]); + return areArrayElementsUnique(names); + }), [FreePropertiesTypes.FREE_FILTER_PROPERTIES]: yup .array() .of( @@ -116,42 +94,32 @@ export const filterPropertiesYupSchema = { [PROPERTY_VALUES]: yup .array() .of(yup.string()) - .test( - 'can not be empty if not line', - 'YupRequired', - (values, context) => - propertyValuesTest(values, context, false) + .test('can not be empty if not line', 'YupRequired', (values, context) => + propertyValuesTest(values, context, false) ), }) ) - .test( - 'distinct names', - 'filterPropertiesNameUniquenessError', - (properties, context) => { - // with context.from[length - 1], we can access to the root fields of the form - const rootLevelForm = context.from![context.from!.length - 1]; - const filterType = - rootLevelForm.value[FieldConstants.FILTER_TYPE]; - if (filterType !== FilterType.CRITERIA_BASED.id) { - // we don't test if we are not in a criteria based form - return true; - } - const names = properties! // never null / undefined - .filter((prop) => !!prop[PROPERTY_NAME]) - .map((prop) => prop[PROPERTY_NAME]); - return areArrayElementsUnique(names); + .test('distinct names', 'filterPropertiesNameUniquenessError', (properties, context) => { + // with context.from[length - 1], we can access to the root fields of the form + const rootLevelForm = context.from![context.from!.length - 1]; + const filterType = rootLevelForm.value[FieldConstants.FILTER_TYPE]; + if (filterType !== FilterType.CRITERIA_BASED.id) { + // we don't test if we are not in a criteria based form + return true; } - ), + const names = properties! // never null / undefined + .filter((prop) => !!prop[PROPERTY_NAME]) + .map((prop) => prop[PROPERTY_NAME]); + return areArrayElementsUnique(names); + }), }; function FilterProperties() { const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE, }); - const [equipmentPredefinedProps, setEquipmentType] = - usePredefinedProperties(watchEquipmentType); - const [substationPredefinedProps, setSubstationType] = - usePredefinedProperties(null); + const [equipmentPredefinedProps, setEquipmentType] = usePredefinedProperties(watchEquipmentType); + const [substationPredefinedProps, setSubstationType] = usePredefinedProperties(null); const displayEquipmentProperties = useMemo(() => { return ( @@ -167,10 +135,7 @@ function FilterProperties() { }, [watchEquipmentType]); const displaySubstationProperties = useMemo(() => { - return ( - watchEquipmentType !== Substation.type && - watchEquipmentType !== null - ); + return watchEquipmentType !== Substation.type && watchEquipmentType !== null; }, [watchEquipmentType]); useEffect(() => { @@ -188,22 +153,16 @@ function FilterProperties() { watchEquipmentType && ( - - {(txt) =>

    {txt}

    } -
    + {(txt) =>

    {txt}

    }
    {displayEquipmentProperties && ( )} {displaySubstationProperties && ( )} diff --git a/src/components/filter/criteria-based/filter-property.tsx b/src/components/filter/criteria-based/filter-property.tsx index fb9182ed..140f8269 100644 --- a/src/components/filter/criteria-based/filter-property.tsx +++ b/src/components/filter/criteria-based/filter-property.tsx @@ -29,8 +29,7 @@ interface FilterPropertyProps { } function FilterProperty(props: FilterPropertyProps) { - const { propertyType, index, predefined, valuesFields, handleDelete } = - props; + const { propertyType, index, predefined, valuesFields, handleDelete } = props; const { setValue } = useFormContext(); const watchName = useWatch({ @@ -52,10 +51,7 @@ function FilterProperty(props: FilterPropertyProps) { // We reset values when name change const onNameChange = useCallback(() => { valuesFields.forEach((valuesField) => - setValue( - `${FieldConstants.CRITERIA_BASED}.${propertyType}[${index}].${valuesField.name}`, - [] - ) + setValue(`${FieldConstants.CRITERIA_BASED}.${propertyType}[${index}].${valuesField.name}`, []) ); }, [setValue, index, valuesFields, propertyType]); diff --git a/src/components/filter/expert/expert-filter-constants.ts b/src/components/filter/expert/expert-filter-constants.ts index 3a2655d2..3d21a5e8 100644 --- a/src/components/filter/expert/expert-filter-constants.ts +++ b/src/components/filter/expert/expert-filter-constants.ts @@ -6,12 +6,7 @@ */ import { Field } from 'react-querybuilder'; -import { - CombinatorType, - DataType, - FieldType, - OperatorType, -} from './expert-filter.type'; +import { CombinatorType, DataType, FieldType, OperatorType } from './expert-filter.type'; export enum RULES { EMPTY_RULE = 'emptyRule', @@ -1042,12 +1037,7 @@ export const FIELDS_OPTIONS = { }; export const fields: Record = { - SUBSTATION: [ - FIELDS_OPTIONS.ID, - FIELDS_OPTIONS.NAME, - FIELDS_OPTIONS.COUNTRY, - FIELDS_OPTIONS.PROPERTY, - ], + SUBSTATION: [FIELDS_OPTIONS.ID, FIELDS_OPTIONS.NAME, FIELDS_OPTIONS.COUNTRY, FIELDS_OPTIONS.PROPERTY], VOLTAGE_LEVEL: [ FIELDS_OPTIONS.ID, FIELDS_OPTIONS.NAME, diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index ea6275f9..ed1d8b76 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -86,11 +86,8 @@ function ExpertFilterEditionDialog({ reset({ [FieldConstants.NAME]: name, [FieldConstants.FILTER_TYPE]: FilterType.EXPERT.id, - [FieldConstants.EQUIPMENT_TYPE]: - response[FieldConstants.EQUIPMENT_TYPE], - [EXPERT_FILTER_QUERY]: importExpertRules( - response[EXPERT_FILTER_QUERY] - ), + [FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE], + [EXPERT_FILTER_QUERY]: importExpertRules(response[EXPERT_FILTER_QUERY]), }); }) .catch((error: { message: any }) => { @@ -127,14 +124,7 @@ function ExpertFilterEditionDialog({ }); } }, - [ - broadcastChannel, - id, - onClose, - selectionForCopy.sourceItemUuid, - snackError, - setSelectionForCopy, - ] + [broadcastChannel, id, onClose, selectionForCopy.sourceItemUuid, snackError, setSelectionForCopy] ); const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; diff --git a/src/components/filter/expert/expert-filter-form.tsx b/src/components/filter/expert/expert-filter-form.tsx index 1083dc2c..133d2fce 100644 --- a/src/components/filter/expert/expert-filter-form.tsx +++ b/src/components/filter/expert/expert-filter-form.tsx @@ -71,8 +71,7 @@ export const expertFilterSchema = { is: FilterType.EXPERT.id, then: (schema: yup.Schema) => schema.when([FieldConstants.EQUIPMENT_TYPE], { - is: (equipmentType: string) => - isSupportedEquipmentType(equipmentType), + is: (equipmentType: string) => isSupportedEquipmentType(equipmentType), then: rqbQuerySchemaValidator, }), }), @@ -139,13 +138,9 @@ function ExpertFilterForm() { validateButtonLabel="button.changeType" />
    - {watchEquipmentType && - isSupportedEquipmentType(watchEquipmentType) && ( - - )} + {watchEquipmentType && isSupportedEquipmentType(watchEquipmentType) && ( + + )}
    ); } diff --git a/src/components/filter/expert/expert-filter-utils.ts b/src/components/filter/expert/expert-filter-utils.ts index 6b6ad0e9..5c53e41b 100644 --- a/src/components/filter/expert/expert-filter-utils.ts +++ b/src/components/filter/expert/expert-filter-utils.ts @@ -30,16 +30,8 @@ import { RuleGroupTypeExport, RuleTypeExport, } from './expert-filter.type'; -import { - FIELDS_OPTIONS, - OPERATOR_OPTIONS, - RULES, -} from './expert-filter-constants'; -import { - isBlankOrEmpty, - microUnitToUnit, - unitToMicroUnit, -} from '../../../utils/conversion-utils'; +import { FIELDS_OPTIONS, OPERATOR_OPTIONS, RULES } from './expert-filter-constants'; +import { isBlankOrEmpty, microUnitToUnit, unitToMicroUnit } from '../../../utils/conversion-utils'; const microUnits = [ FieldType.SHUNT_CONDUCTANCE_1, @@ -86,18 +78,14 @@ const getFieldData = (fieldName: string) => { */ const getDataType = (fieldName: string, operator: string) => { // particular case => set dataType to FILTER_UUID when exporting rule with operator IS_PART_OF or IS_NOT_PART_OF - if ( - operator === OPERATOR_OPTIONS.IS_PART_OF.name || - operator === OPERATOR_OPTIONS.IS_NOT_PART_OF.name - ) { + if (operator === OPERATOR_OPTIONS.IS_PART_OF.name || operator === OPERATOR_OPTIONS.IS_NOT_PART_OF.name) { return DataType.FILTER_UUID; } // particular case => set dataType to BOOLEAN when exporting composite rule REMOTE_REGULATED_TERMINAL with operator EXISTS or NOT_EXISTS if ( fieldName === FieldType.REMOTE_REGULATED_TERMINAL && - (operator === OPERATOR_OPTIONS.EXISTS.name || - operator === OPERATOR_OPTIONS.NOT_EXISTS.name) + (operator === OPERATOR_OPTIONS.EXISTS.name || operator === OPERATOR_OPTIONS.NOT_EXISTS.name) ) { return DataType.BOOLEAN; } @@ -109,9 +97,7 @@ const getDataType = (fieldName: string, operator: string) => { }; export const getOperators = (fieldName: string, intl: IntlShape) => { - const field = Object.values(FIELDS_OPTIONS).find( - (fieldOption) => fieldOption.name === fieldName - ); + const field = Object.values(FIELDS_OPTIONS).find((fieldOption) => fieldOption.name === fieldName); switch (field?.dataType) { case DataType.STRING: { @@ -138,9 +124,7 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { if (field.name === FieldType.ID) { // When the ID is selected, the operators EXISTS and NOT_EXISTS must be removed. stringOperators = stringOperators.filter( - (operator) => - operator !== OPERATOR_OPTIONS.EXISTS && - operator !== OPERATOR_OPTIONS.NOT_EXISTS + (operator) => operator !== OPERATOR_OPTIONS.EXISTS && operator !== OPERATOR_OPTIONS.NOT_EXISTS ); } return stringOperators.map((operator) => ({ @@ -171,10 +155,7 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { // particular case if (field.name === FieldType.AUTOMATE) { // take only EXISTS and NOT_EXISTS - booleanOperators = [ - OPERATOR_OPTIONS.EXISTS, - OPERATOR_OPTIONS.NOT_EXISTS, - ]; + booleanOperators = [OPERATOR_OPTIONS.EXISTS, OPERATOR_OPTIONS.NOT_EXISTS]; } return booleanOperators.map((operator) => ({ name: operator.name, @@ -193,9 +174,7 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { field.name === FieldType.SVAR_REGULATION_MODE ) { // When one of above field is selected, the operator IN must be removed. - enumOperators = enumOperators.filter( - (operator) => operator !== OPERATOR_OPTIONS.IN - ); + enumOperators = enumOperators.filter((operator) => operator !== OPERATOR_OPTIONS.IN); } return enumOperators.map((operator) => ({ name: operator.name, @@ -239,9 +218,7 @@ function changeValueUnit(value: any, field: FieldType) { } export function exportExpertRules(query: RuleGroupType): RuleGroupTypeExport { - function transformRule( - rule: RuleType - ): RuleTypeExport | RuleGroupTypeExport { + function transformRule(rule: RuleType): RuleTypeExport | RuleGroupTypeExport { const isValueAnArray = Array.isArray(rule.value); const dataType = getDataType(rule.field, rule.operator) as DataType; @@ -257,9 +234,8 @@ export function exportExpertRules(query: RuleGroupType): RuleGroupTypeExport { field: rule.field as FieldType, operator: dataType !== DataType.PROPERTY - ? (Object.values(OPERATOR_OPTIONS).find( - (operator) => operator.name === rule.operator - )?.customName as OperatorType) + ? (Object.values(OPERATOR_OPTIONS).find((operator) => operator.name === rule.operator) + ?.customName as OperatorType) : rule.value.propertyOperator, value: !isValueAnArray && @@ -273,29 +249,20 @@ export function exportExpertRules(query: RuleGroupType): RuleGroupTypeExport { ? changeValueUnit(rule.value, rule.field as FieldType) : undefined, dataType, - propertyName: - dataType === DataType.PROPERTY - ? rule.value.propertyName - : undefined, - propertyValues: - dataType === DataType.PROPERTY - ? rule.value.propertyValues - : undefined, + propertyName: dataType === DataType.PROPERTY ? rule.value.propertyName : undefined, + propertyValues: dataType === DataType.PROPERTY ? rule.value.propertyValues : undefined, }; } - function transformCompositeRule( - compositeRule: RuleType - ): RuleGroupTypeExport { + function transformCompositeRule(compositeRule: RuleType): RuleGroupTypeExport { const compositeGroup = compositeRule.value as CompositeGroup; - const transformedRules = Object.entries(compositeGroup.rules).map( - ([field, rule]) => - transformRule({ - ...rule, - field, - operator: rule.operator, - value: rule.value, - }) + const transformedRules = Object.entries(compositeGroup.rules).map(([field, rule]) => + transformRule({ + ...rule, + field, + operator: rule.operator, + value: rule.value, + }) ); return { @@ -304,9 +271,8 @@ export function exportExpertRules(query: RuleGroupType): RuleGroupTypeExport { dataType: DataType.COMBINATOR, rules: transformedRules, // two additional attributes to distinct a composite rule from a normal rule group - operator: Object.values(OPERATOR_OPTIONS).find( - (operator) => operator.name === compositeRule.operator - )?.customName as OperatorType, + operator: Object.values(OPERATOR_OPTIONS).find((operator) => operator.name === compositeRule.operator) + ?.customName as OperatorType, field: compositeRule.field as FieldType, }; } @@ -346,17 +312,13 @@ export function importExpertRules(query: RuleGroupTypeExport): RuleGroupType { return rule.values .map((value) => parseFloat(value as string)) .map((numberValue) => { - return microUnits.includes(rule.field) - ? unitToMicroUnit(numberValue)! - : numberValue; + return microUnits.includes(rule.field) ? unitToMicroUnit(numberValue)! : numberValue; }) .sort((a, b) => a - b); } return rule.values.sort(); } - return microUnits.includes(rule.field) - ? unitToMicroUnit(parseFloat(rule.value as string)) - : rule.value; + return microUnits.includes(rule.field) ? unitToMicroUnit(parseFloat(rule.value as string)) : rule.value; } function transformRule(rule: RuleTypeExport): RuleType { @@ -365,9 +327,8 @@ export function importExpertRules(query: RuleGroupTypeExport): RuleGroupType { field: rule.field, operator: rule.dataType !== DataType.PROPERTY - ? (Object.values(OPERATOR_OPTIONS).find( - (operator) => operator.customName === rule.operator - )?.name as string) + ? (Object.values(OPERATOR_OPTIONS).find((operator) => operator.customName === rule.operator) + ?.name as string) : OPERATOR_OPTIONS.IS.name, value: parseValue(rule), }; @@ -390,9 +351,8 @@ export function importExpertRules(query: RuleGroupTypeExport): RuleGroupType { return { id: group.id, field: group.field as FieldType, - operator: Object.values(OPERATOR_OPTIONS).find( - (operator) => operator.customName === group.operator - )?.name as string, + operator: Object.values(OPERATOR_OPTIONS).find((operator) => operator.customName === group.operator) + ?.name as string, value: { combinator: group.combinator, rules: transformedRules, @@ -431,11 +391,7 @@ export function countRules(query: RuleGroupTypeAny): number { if ('rules' in query) { const group = query as RuleGroupType; - return group.rules.reduce( - (sum, ruleOrGroup) => - sum + countRules(ruleOrGroup as RuleGroupTypeAny), - 0 - ); + return group.rules.reduce((sum, ruleOrGroup) => sum + countRules(ruleOrGroup as RuleGroupTypeAny), 0); } return 1; } @@ -457,8 +413,7 @@ export const queryValidator: QueryValidator = (query) => { if ( rule.id && - (rule.operator === OPERATOR_OPTIONS.EXISTS.name || - rule.operator === OPERATOR_OPTIONS.NOT_EXISTS.name) + (rule.operator === OPERATOR_OPTIONS.EXISTS.name || rule.operator === OPERATOR_OPTIONS.NOT_EXISTS.name) ) { // In the case of (NOT_)EXISTS operator, because we do not have a second value to evaluate, we force a valid result. result[rule.id] = { @@ -471,10 +426,7 @@ export const queryValidator: QueryValidator = (query) => { valid: false, reasons: [RULES.EMPTY_RULE], }; - } else if ( - Number.isNaN(parseFloat(rule.value[0])) || - Number.isNaN(parseFloat(rule.value[1])) - ) { + } else if (Number.isNaN(parseFloat(rule.value[0])) || Number.isNaN(parseFloat(rule.value[1]))) { result[rule.id] = { valid: false, reasons: [RULES.INCORRECT_RULE], @@ -485,29 +437,17 @@ export const queryValidator: QueryValidator = (query) => { reasons: [RULES.BETWEEN_RULE], }; } - } else if ( - rule.id && - rule.operator === OPERATOR_OPTIONS.IN.name && - !rule.value?.length - ) { + } else if (rule.id && rule.operator === OPERATOR_OPTIONS.IN.name && !rule.value?.length) { result[rule.id] = { valid: false, reasons: [RULES.EMPTY_RULE], }; - } else if ( - rule.id && - isStringInput && - (rule.value || '').trim() === '' - ) { + } else if (rule.id && isStringInput && (rule.value || '').trim() === '') { result[rule.id] = { valid: false, reasons: [RULES.EMPTY_RULE], }; - } else if ( - rule.id && - isNumberInput && - Number.isNaN(parseFloat(rule.value)) - ) { + } else if (rule.id && isNumberInput && Number.isNaN(parseFloat(rule.value))) { result[rule.id] = { valid: false, reasons: [RULES.INCORRECT_RULE], @@ -535,9 +475,7 @@ export const queryValidator: QueryValidator = (query) => { }; } else if (rule.id && dataType === DataType.COMBINATOR) { // based on FIELDS_OPTIONS configuration and composite group, validate for each children composite rule in a composite group - const childrenFields = Object.keys( - getFieldData(rule.field).children ?? {} - ); + const childrenFields = Object.keys(getFieldData(rule.field).children ?? {}); const compositeGroup = rule.value as CompositeGroup; // call validate recursively @@ -599,15 +537,9 @@ export function getNumberOfSiblings(path: number[], query: RuleGroupTypeAny) { } // Remove a rule or group and its parents if they become empty -export function recursiveRemove( - query: RuleGroupTypeAny, - path: number[] -): RuleGroupTypeAny { +export function recursiveRemove(query: RuleGroupTypeAny, path: number[]): RuleGroupTypeAny { // If it's an only child, we also need to remove and check the parent group (but not the root) - if ( - getNumberOfSiblings(path, query) === 1 && - path.toString() !== [0].toString() - ) { + if (getNumberOfSiblings(path, query) === 1 && path.toString() !== [0].toString()) { return recursiveRemove(query, getParentPath(path)); } // Otherwise, we can safely remove it diff --git a/src/components/filter/expert/styles-expert-filter.css b/src/components/filter/expert/styles-expert-filter.css index c8099ab3..6b57ecd7 100644 --- a/src/components/filter/expert/styles-expert-filter.css +++ b/src/components/filter/expert/styles-expert-filter.css @@ -58,10 +58,11 @@ position: relative; } -.queryBuilder-branches .rule::before, .queryBuilder-branches .rule::after, +.queryBuilder-branches .rule::before, +.queryBuilder-branches .rule::after, .queryBuilder-branches .ruleGroup .ruleGroup::before, .queryBuilder-branches .ruleGroup .ruleGroup::after { - content: ""; + content: ''; width: 0.5rem; left: calc(-0.5rem - 1px); border-color: #969696; @@ -94,7 +95,8 @@ display: none; } -.queryBuilder-branches .ruleGroup .ruleGroup::before, .queryBuilder-branches .ruleGroup .ruleGroup::after { +.queryBuilder-branches .ruleGroup .ruleGroup::before, +.queryBuilder-branches .ruleGroup .ruleGroup::after { left: calc(calc(-0.5rem - 1px) - 1px); } @@ -133,25 +135,33 @@ } /* DnD section, copied from original, just 'border-bottom-color: lightgrey' change */ -[data-inlinecombinators=disabled] .dndOver.rule, [data-inlinecombinators=disabled] .dndOver.ruleGroup-header { +[data-inlinecombinators='disabled'] .dndOver.rule, +[data-inlinecombinators='disabled'] .dndOver.ruleGroup-header { border-bottom-width: 2px; border-bottom-style: dashed; border-bottom-color: lightgrey; padding-bottom: 0.5rem; } -[data-inlinecombinators=disabled] .dndOver.rule.dndCopy, [data-inlinecombinators=disabled] .dndOver.ruleGroup-header.dndCopy { +[data-inlinecombinators='disabled'] .dndOver.rule.dndCopy, +[data-inlinecombinators='disabled'] .dndOver.ruleGroup-header.dndCopy { border-bottom-color: #669933; } -[data-inlinecombinators=enabled] .dndOver.rule:last-child, [data-inlinecombinators=enabled] .dndOver.ruleGroup-header, [data-inlinecombinators=enabled] .dndOver.rule + .betweenRules, [data-inlinecombinators=enabled] .dndOver.betweenRules { +[data-inlinecombinators='enabled'] .dndOver.rule:last-child, +[data-inlinecombinators='enabled'] .dndOver.ruleGroup-header, +[data-inlinecombinators='enabled'] .dndOver.rule + .betweenRules, +[data-inlinecombinators='enabled'] .dndOver.betweenRules { border-bottom-width: 2px; border-bottom-style: dashed; border-bottom-color: lightgrey; padding-bottom: 0.5rem; } -[data-inlinecombinators=enabled] .dndOver.rule:last-child.dndCopy, [data-inlinecombinators=enabled] .dndOver.ruleGroup-header.dndCopy, [data-inlinecombinators=enabled] .dndOver.rule + .betweenRules.dndCopy, [data-inlinecombinators=enabled] .dndOver.betweenRules.dndCopy { +[data-inlinecombinators='enabled'] .dndOver.rule:last-child.dndCopy, +[data-inlinecombinators='enabled'] .dndOver.ruleGroup-header.dndCopy, +[data-inlinecombinators='enabled'] .dndOver.rule + .betweenRules.dndCopy, +[data-inlinecombinators='enabled'] .dndOver.betweenRules.dndCopy { border-bottom-color: #669933; } @@ -165,4 +175,4 @@ cursor: move; } -/* end DnD section */ \ No newline at end of file +/* end DnD section */ diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index df6c1e41..da7a0f10 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -15,10 +15,7 @@ import { saveExplicitNamingFilter } from '../utils/filter-api'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import CustomMuiDialog from '../../dialogs/custom-mui-dialog'; import yup from '../../../utils/yup-config'; -import { - explicitNamingFilterSchema, - FILTER_EQUIPMENTS_ATTRIBUTES, -} from './explicit-naming-filter-form'; +import { explicitNamingFilterSchema, FILTER_EQUIPMENTS_ATTRIBUTES } from './explicit-naming-filter-form'; import FieldConstants from '../../../utils/field-constants'; import FilterForm from '../filter-form'; import { noSelectionForCopy } from '../../../utils/equipment-types'; @@ -88,13 +85,9 @@ function ExplicitNamingFilterEditionDialog({ setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ [FieldConstants.NAME]: name, - [FieldConstants.FILTER_TYPE]: - FilterType.EXPLICIT_NAMING.id, - [FieldConstants.EQUIPMENT_TYPE]: - response[FieldConstants.EQUIPMENT_TYPE], - [FILTER_EQUIPMENTS_ATTRIBUTES]: response[ - FILTER_EQUIPMENTS_ATTRIBUTES - ].map((row: any) => ({ + [FieldConstants.FILTER_TYPE]: FilterType.EXPLICIT_NAMING.id, + [FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE], + [FILTER_EQUIPMENTS_ATTRIBUTES]: response[FILTER_EQUIPMENTS_ATTRIBUTES].map((row: any) => ({ [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), ...row, })), @@ -133,14 +126,7 @@ function ExplicitNamingFilterEditionDialog({ }); } }, - [ - broadcastChannel, - id, - selectionForCopy, - onClose, - snackError, - setSelectionForCopy, - ] + [broadcastChannel, id, selectionForCopy, onClose, snackError, setSelectionForCopy] ); const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index 1fa00847..5ab368b5 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -45,46 +45,24 @@ export const explicitNamingFilterSchema = { }) ) // we remove empty lines - .compact( - (row) => !row[DISTRIBUTION_KEY] && !row[FieldConstants.EQUIPMENT_ID] - ) + .compact((row) => !row[DISTRIBUTION_KEY] && !row[FieldConstants.EQUIPMENT_ID]) .when([FieldConstants.FILTER_TYPE], { is: FilterType.EXPLICIT_NAMING.id, then: (schema) => - schema - .min(1, 'emptyFilterError') - .when([FieldConstants.EQUIPMENT_TYPE], { - is: (equipmentType: string) => - isGeneratorOrLoad(equipmentType), - then: (innerSchema) => - innerSchema - .test( - 'noKeyWithoutId', - 'distributionKeyWithMissingIdError', - (array) => { - return !array!.some( - (row) => - !row[ - FieldConstants.EQUIPMENT_ID - ] - ); - } - ) - .test( - 'ifOneKeyThenKeyEverywhere', - 'missingDistributionKeyError', - (array) => { - return !( - array!.some( - (row) => row[DISTRIBUTION_KEY] - ) && - array!.some( - (row) => !row[DISTRIBUTION_KEY] - ) - ); - } - ), - }), + schema.min(1, 'emptyFilterError').when([FieldConstants.EQUIPMENT_TYPE], { + is: (equipmentType: string) => isGeneratorOrLoad(equipmentType), + then: (innerSchema) => + innerSchema + .test('noKeyWithoutId', 'distributionKeyWithMissingIdError', (array) => { + return !array!.some((row) => !row[FieldConstants.EQUIPMENT_ID]); + }) + .test('ifOneKeyThenKeyEverywhere', 'missingDistributionKeyError', (array) => { + return !( + array!.some((row) => row[DISTRIBUTION_KEY]) && + array!.some((row) => !row[DISTRIBUTION_KEY]) + ); + }), + }), }), }; @@ -121,9 +99,7 @@ interface ExplicitNamingFilterFormProps { sourceFilterForExplicitNamingConversion?: FilterForExplicitConversionProps; } -function ExplicitNamingFilterForm({ - sourceFilterForExplicitNamingConversion, -}: ExplicitNamingFilterFormProps) { +function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversion }: ExplicitNamingFilterFormProps) { const intl = useIntl(); const { snackError } = useSnackMessage(); @@ -134,10 +110,7 @@ function ExplicitNamingFilterForm({ }); useEffect(() => { - if ( - watchEquipmentType && - !((watchEquipmentType as EquipmentType) in FILTER_EQUIPMENTS) - ) { + if (watchEquipmentType && !((watchEquipmentType as EquipmentType) in FILTER_EQUIPMENTS)) { snackError({ headerId: 'obsoleteFilter', }); @@ -148,10 +121,7 @@ function ExplicitNamingFilterForm({ useEffect(() => { if (sourceFilterForExplicitNamingConversion) { - setValue( - FieldConstants.EQUIPMENT_TYPE, - sourceFilterForExplicitNamingConversion.equipmentType - ); + setValue(FieldConstants.EQUIPMENT_TYPE, sourceFilterForExplicitNamingConversion.equipmentType); } }, [sourceFilterForExplicitNamingConversion, setValue]); @@ -165,8 +135,7 @@ function ExplicitNamingFilterForm({ field: FieldConstants.EQUIPMENT_ID, editable: true, singleClickEdit: true, - valueParser: (params: ValueParserParams) => - params.newValue?.trim() ?? null, + valueParser: (params: ValueParserParams) => params.newValue?.trim() ?? null, }, ]; if (forGeneratorOrLoad) { @@ -190,13 +159,9 @@ function ExplicitNamingFilterForm({ ); const csvFileHeaders = useMemo(() => { - const newCsvFileHeaders = [ - intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID }), - ]; + const newCsvFileHeaders = [intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID })]; if (forGeneratorOrLoad) { - newCsvFileHeaders.push( - intl.formatMessage({ id: DISTRIBUTION_KEY }) - ); + newCsvFileHeaders.push(intl.formatMessage({ id: DISTRIBUTION_KEY })); } return newCsvFileHeaders; }, [intl, forGeneratorOrLoad]); @@ -216,8 +181,7 @@ function ExplicitNamingFilterForm({ const openConfirmationPopup = () => { return getValues(FILTER_EQUIPMENTS_ATTRIBUTES).some( - (row: FilterTableRow) => - row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] + (row: FilterTableRow) => row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] ); }; @@ -227,10 +191,7 @@ function ExplicitNamingFilterForm({ const onStudySelected = (studyUuid: UUID) => { studySvc - .exportFilter( - studyUuid, - sourceFilterForExplicitNamingConversion?.id - ) + .exportFilter(studyUuid, sourceFilterForExplicitNamingConversion?.id) .then((matchingEquipments: any) => { setValue( FILTER_EQUIPMENTS_ATTRIBUTES, diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index 47e5edbe..29db25c5 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -9,11 +9,7 @@ import { useCallback } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { UUID } from 'crypto'; -import { - saveCriteriaBasedFilter, - saveExpertFilter, - saveExplicitNamingFilter, -} from './utils/filter-api'; +import { saveCriteriaBasedFilter, saveExpertFilter, saveExplicitNamingFilter } from './utils/filter-api'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import CustomMuiDialog from '../dialogs/custom-mui-dialog'; import { @@ -28,11 +24,7 @@ import { import FieldConstants from '../../utils/field-constants'; import yup from '../../utils/yup-config'; import FilterForm from './filter-form'; -import { - EXPERT_FILTER_QUERY, - expertFilterSchema, - getExpertFilterEmptyFormData, -} from './expert/expert-filter-form'; +import { EXPERT_FILTER_QUERY, expertFilterSchema, getExpertFilterEmptyFormData } from './expert/expert-filter-form'; import { FilterType } from './constants/filter-constants'; import { GsLangUser } from '../../utils/language'; @@ -52,9 +44,7 @@ const formSchema = yup .object() .shape({ [FieldConstants.NAME]: yup.string().trim().required('nameEmpty'), - [FieldConstants.DESCRIPTION]: yup - .string() - .max(500, 'descriptionLimitError'), + [FieldConstants.DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'), [FieldConstants.FILTER_TYPE]: yup.string().required(), [FieldConstants.EQUIPMENT_TYPE]: yup.string().required(), ...criteriaBasedFilterSchema, @@ -97,10 +87,7 @@ function FilterCreationDialog({ const onSubmit = useCallback( (filterForm: any) => { - if ( - filterForm[FieldConstants.FILTER_TYPE] === - FilterType.EXPLICIT_NAMING.id - ) { + if (filterForm[FieldConstants.FILTER_TYPE] === FilterType.EXPLICIT_NAMING.id) { saveExplicitNamingFilter( filterForm[FILTER_EQUIPMENTS_ATTRIBUTES], true, @@ -116,23 +103,13 @@ function FilterCreationDialog({ onClose, activeDirectory ); - } else if ( - filterForm[FieldConstants.FILTER_TYPE] === - FilterType.CRITERIA_BASED.id - ) { - saveCriteriaBasedFilter( - filterForm, - activeDirectory, - onClose, - (error: any) => { - snackError({ - messageTxt: error, - }); - } - ); - } else if ( - filterForm[FieldConstants.FILTER_TYPE] === FilterType.EXPERT.id - ) { + } else if (filterForm[FieldConstants.FILTER_TYPE] === FilterType.CRITERIA_BASED.id) { + saveCriteriaBasedFilter(filterForm, activeDirectory, onClose, (error: any) => { + snackError({ + messageTxt: error, + }); + }); + } else if (filterForm[FieldConstants.FILTER_TYPE] === FilterType.EXPERT.id) { saveExpertFilter( null, filterForm[EXPERT_FILTER_QUERY], @@ -160,11 +137,7 @@ function FilterCreationDialog({ onSave={onSubmit} formSchema={formSchema} formMethods={formMethods} - titleId={ - sourceFilterForExplicitNamingConversion - ? 'convertIntoExplicitNamingFilter' - : 'createNewFilter' - } + titleId={sourceFilterForExplicitNamingConversion ? 'convertIntoExplicitNamingFilter' : 'createNewFilter'} removeOptional disabledSave={!!nameError || !!isValidating} language={language} @@ -172,9 +145,7 @@ function FilterCreationDialog({ ); diff --git a/src/components/filter/filter-form.tsx b/src/components/filter/filter-form.tsx index 770e046d..7a772842 100644 --- a/src/components/filter/filter-form.tsx +++ b/src/components/filter/filter-form.tsx @@ -30,20 +30,13 @@ interface FilterFormProps { } function FilterForm(props: FilterFormProps) { - const { - sourceFilterForExplicitNamingConversion, - creation, - activeDirectory, - } = props; + const { sourceFilterForExplicitNamingConversion, creation, activeDirectory } = props; const { setValue } = useFormContext(); const filterType = useWatch({ name: FieldConstants.FILTER_TYPE }); // We do this because setValue don't set the field dirty - const handleChange = ( - _event: React.ChangeEvent, - value: string - ) => { + const handleChange = (_event: React.ChangeEvent, value: string) => { setValue(FieldConstants.FILTER_TYPE, value); }; @@ -87,14 +80,10 @@ function FilterForm(props: FilterFormProps) { )} )} - {filterType === FilterType.CRITERIA_BASED.id && ( - - )} + {filterType === FilterType.CRITERIA_BASED.id && } {filterType === FilterType.EXPLICIT_NAMING.id && ( )} {filterType === FilterType.EXPERT.id && } diff --git a/src/components/filter/utils/filter-api.ts b/src/components/filter/utils/filter-api.ts index f6b81977..62df1f4b 100644 --- a/src/components/filter/utils/filter-api.ts +++ b/src/components/filter/utils/filter-api.ts @@ -26,8 +26,7 @@ export const saveExplicitNamingFilter = ( ) => { // we remove unnecessary fields from the table let cleanedTableValues; - const isGeneratorOrLoad = - equipmentType === Generator.type || equipmentType === Load.type; + const isGeneratorOrLoad = equipmentType === Generator.type || equipmentType === Load.type; if (isGeneratorOrLoad) { cleanedTableValues = tableValues.map((row) => ({ [FieldConstants.EQUIPMENT_ID]: row[FieldConstants.EQUIPMENT_ID], @@ -84,12 +83,7 @@ export const saveCriteriaBasedFilter = ( ) => { const filterForBack = frontToBackTweak(undefined, filter); // no need ID for creation exploreSvc - .createFilter( - filterForBack, - filter[FieldConstants.NAME], - filter[FieldConstants.DESCRIPTION], - activeDirectory - ) + .createFilter(filterForBack, filter[FieldConstants.NAME], filter[FieldConstants.DESCRIPTION], activeDirectory) .then(() => { onClose(); }) diff --git a/src/components/inputs/react-hook-form/ExpandingTextField.tsx b/src/components/inputs/react-hook-form/ExpandingTextField.tsx index 9022a05c..e749ca35 100644 --- a/src/components/inputs/react-hook-form/ExpandingTextField.tsx +++ b/src/components/inputs/react-hook-form/ExpandingTextField.tsx @@ -63,30 +63,18 @@ function ExpandingTextField({ resize: 'none', // or 'horizontal' for horizontal resizing }, }, - helperText: ( - {descriptionCounter} - ), + helperText: {descriptionCounter}, FormHelperTextProps: { sx: { ml: 'auto', - color: (theme: Theme) => - isOverTheLimit - ? theme.palette.error.main - : theme.palette.text.secondary, + color: (theme: Theme) => (isOverTheLimit ? theme.palette.error.main : theme.palette.text.secondary), }, }, ...(rowsToDisplay && { rows: rowsToDisplay }), ...(sx && { sx }), ...textFieldFormProps, }; - return ( - - ); + return ; } export default ExpandingTextField; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx index d661d1f2..a6d706a7 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx @@ -56,9 +56,7 @@ function BottomRightButtons({ {csvProps && ( - setUploaderOpen(true)} - > + setUploaderOpen(true)}> )} - + - + - + - + diff --git a/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts b/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts index 144747c5..e114920a 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts +++ b/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts @@ -44,10 +44,7 @@ class NumericEditor implements ICellEditorComp { if (event.preventDefault) { event.preventDefault(); } - } else if ( - NumericEditor.isNavigationKey(event) || - NumericEditor.isBackspace(event) - ) { + } else if (NumericEditor.isNavigationKey(event) || NumericEditor.isBackspace(event)) { event.stopPropagation(); } }); @@ -55,9 +52,7 @@ class NumericEditor implements ICellEditorComp { // only start edit if key pressed is a number, not a letter // FM : I added ',' and '.' const isNotANumber = - params.eventKey && - params.eventKey.length === 1 && - '1234567890,.'.indexOf(params.eventKey) < 0; + params.eventKey && params.eventKey.length === 1 && '1234567890,.'.indexOf(params.eventKey) < 0; this.cancelBeforeStart = !!isNotANumber; } diff --git a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx index 7b668501..f4c7e96d 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx @@ -53,8 +53,7 @@ function CsvUploader({ const intl = useIntl(); const { CSVReader } = useCSVReader(); const [importedData, setImportedData] = useState([]); - const [isConfirmationPopupOpen, setIsConfirmationPopupOpen] = - useState(false); + const [isConfirmationPopupOpen, setIsConfirmationPopupOpen] = useState(false); const data = useMemo(() => { const newData = [...[fileHeaders]]; @@ -77,9 +76,7 @@ function CsvUploader({ // validate the headers for (let i = 0; i < fileHeaders.length; i++) { if (fileHeaders[i] !== '' && rows[0][i] !== fileHeaders[i]) { - setCreateError( - intl.formatMessage({ id: 'wrongCsvHeadersError' }) - ); + setCreateError(intl.formatMessage({ id: 'wrongCsvHeadersError' })); return false; } } @@ -126,15 +123,8 @@ function CsvUploader({ (val) => val && Object.keys(val) - .filter( - (key) => key !== FieldConstants.AG_GRID_ROW_UUID - ) - .some( - (e) => - val[e] !== undefined && - val[e] !== null && - String(val[e]).trim().length > 0 - ) + .filter((key) => key !== FieldConstants.AG_GRID_ROW_UUID) + .some((e) => val[e] !== undefined && val[e] !== null && String(val[e]).trim().length > 0) ); if (isValuesInTable && getResultsFromImportedData().length > 0) { @@ -160,30 +150,17 @@ function CsvUploader({ }; const renderConfirmationCsvData = () => { return ( - - - Confirmation - + + Confirmation - - {intl.formatMessage({ id: 'keepCSVDataMessage' })} - + {intl.formatMessage({ id: 'keepCSVDataMessage' })} - - @@ -200,11 +177,7 @@ function CsvUploader({ - + @@ -219,21 +192,12 @@ function CsvUploader({ }} config={{ // We use | for multi values in one cell, then we remove it from the default value for this config, to avoid delimiter autodetection - delimitersToGuess: [ - ',', - ' ', - ';', - RECORD_SEP, - UNIT_SEP, - ], + delimitersToGuess: [',', ' ', ';', RECORD_SEP, UNIT_SEP], }} > {({ getRootProps, acceptedFile }: any) => ( - - {createError !== '' && ( - {createError} - )} + {createError !== '' && {createError}}

    diff --git a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx index 564f33ca..97ae5c87 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx @@ -12,11 +12,7 @@ import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; import { Grid, useTheme } from '@mui/material'; import { useIntl } from 'react-intl'; -import { - CellEditingStoppedEvent, - ColumnState, - SortChangedEvent, -} from 'ag-grid-community'; +import { CellEditingStoppedEvent, ColumnState, SortChangedEvent } from 'ag-grid-community'; import BottomRightButtons from './bottom-right-buttons'; import FieldConstants from '../../../../utils/field-constants'; @@ -79,8 +75,7 @@ const style = (customProps: any) => ({ }, '& .Mui-focused .MuiOutlinedInput-root': { // borders moves row height - outline: - 'var(--ag-borders-input) var(--ag-input-focus-border-color)', + outline: 'var(--ag-borders-input) var(--ag-input-focus-border-color)', outlineOffset: '-1px', backgroundColor: theme.agGridBackground.color, }, @@ -132,19 +127,12 @@ function CustomAgGridTable({ const rowData = watch(name); const isFirstSelected = Boolean( - rowData?.length && - gridApi?.api - .getRowNode(rowData[0][FieldConstants.AG_GRID_ROW_UUID]) - ?.isSelected() + rowData?.length && gridApi?.api.getRowNode(rowData[0][FieldConstants.AG_GRID_ROW_UUID])?.isSelected() ); const isLastSelected = Boolean( rowData?.length && - gridApi?.api - .getRowNode( - rowData[rowData.length - 1][FieldConstants.AG_GRID_ROW_UUID] - ) - ?.isSelected() + gridApi?.api.getRowNode(rowData[rowData.length - 1][FieldConstants.AG_GRID_ROW_UUID])?.isSelected() ); const noRowSelected = selectedRows.length === 0; @@ -152,9 +140,7 @@ function CustomAgGridTable({ const getIndex = useCallback( (val: any) => { return getValues(name).findIndex( - (row: any) => - row[FieldConstants.AG_GRID_ROW_UUID] === - val[FieldConstants.AG_GRID_ROW_UUID] + (row: any) => row[FieldConstants.AG_GRID_ROW_UUID] === val[FieldConstants.AG_GRID_ROW_UUID] ); }, [getValues, name] @@ -244,20 +230,13 @@ function CustomAgGridTable({ ); const onSortChanged = useCallback((event: SortChangedEvent) => { - const isAnycolumnhasSort = event.api - .getColumnState() - .some((col: ColumnState) => col.sort); + const isAnycolumnhasSort = event.api.getColumnState().some((col: ColumnState) => col.sort); setIsSortApplied(isAnycolumnhasSort); }, []); return ( - + - move(getIndex(e.node.data), e.overIndex) - } + onRowDragEnd={(e) => move(getIndex(e.node.data), e.overIndex)} suppressBrowserResizeObserver columnDefs={columnDefs} detailRowAutoHeight onSelectionChanged={() => { setSelectedRows(gridApi.api.getSelectedRows()); }} - onRowDataUpdated={ - newRowAdded ? onRowDataUpdated : undefined - } + onRowDataUpdated={newRowAdded ? onRowDataUpdated : undefined} onCellEditingStopped={onCellEditingStopped} onSortChanged={onSortChanged} - getRowId={(row) => - row.data[FieldConstants.AG_GRID_ROW_UUID] - } + getRowId={(row) => row.data[FieldConstants.AG_GRID_ROW_UUID]} pagination={pagination} paginationPageSize={paginationPageSize} suppressRowClickSelection={suppressRowClickSelection} alwaysShowVerticalScroll={alwaysShowVerticalScroll} - stopEditingWhenCellsLoseFocus={ - stopEditingWhenCellsLoseFocus - } + stopEditingWhenCellsLoseFocus={stopEditingWhenCellsLoseFocus} {...props} /> diff --git a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx index 45d0f15a..97a2f6f4 100644 --- a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx +++ b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx @@ -5,31 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - Autocomplete, - AutocompleteProps, - TextField, - TextFieldProps, -} from '@mui/material'; +import { Autocomplete, AutocompleteProps, TextField, TextFieldProps } from '@mui/material'; import { useController } from 'react-hook-form'; -import { - genHelperError, - genHelperPreviousValue, - identity, - isFieldRequired, -} from '../utils/functions'; +import { genHelperError, genHelperPreviousValue, identity, isFieldRequired } from '../utils/functions'; import FieldLabel from '../utils/field-label'; import useCustomFormContext from '../provider/use-custom-form-context'; import { Option } from '../../../../utils/types'; export interface AutocompleteInputProps extends Omit< - AutocompleteProps< - Option, - boolean | undefined, - boolean | undefined, - boolean | undefined - >, + AutocompleteProps, // we already defined them in our custom Autocomplete 'value' | 'onChange' | 'renderInput' > { @@ -42,10 +27,7 @@ export interface AutocompleteInputProps previousValue?: string; allowNewValue?: boolean; onChangeCallback?: () => void; - formProps?: Omit< - TextFieldProps, - 'value' | 'onChange' | 'inputRef' | 'inputProps' | 'InputProps' - >; + formProps?: Omit; } function AutocompleteInput({ @@ -61,8 +43,7 @@ function AutocompleteInput({ formProps, ...props }: AutocompleteInputProps) { - const { validationSchema, getValues, removeOptional } = - useCustomFormContext(); + const { validationSchema, getValues, removeOptional } = useCustomFormContext(); const { field: { onChange, value, ref }, fieldState: { error }, @@ -77,10 +58,7 @@ function AutocompleteInput({ } // otherwise, we check if user input matches with one of the options - const matchingOption = options.find( - (option: Option) => - typeof option !== 'string' && option.id === newValue - ); + const matchingOption = options.find((option: Option) => typeof option !== 'string' && option.id === newValue); // if it does, we send the matching option to react hook form if (matchingOption) { onChange(outputTransform(matchingOption)); @@ -111,11 +89,7 @@ function AutocompleteInput({ label: FieldLabel({ label, optional: - !isFieldRequired( - name, - validationSchema, - getValues() - ) && + !isFieldRequired(name, validationSchema, getValues()) && !props?.disabled && !removeOptional, }), diff --git a/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx b/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx index b00d55cd..8e1f1545 100644 --- a/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx +++ b/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx @@ -28,9 +28,7 @@ function MultipleAutocompleteInput({ name, ...props }: any) { const outputTransform = (values: any[]) => { const newValues = values.map((val) => val.trim()); - return newValues.filter( - (val, index) => newValues.indexOf(val) === index - ); + return newValues.filter((val, index) => newValues.indexOf(val) === index); }; return ( @@ -42,9 +40,7 @@ function MultipleAutocompleteInput({ name, ...props }: any) { clearOnBlur disableClearable outputTransform={outputTransform} - onInputChange={(_: unknown, val: string) => - setUnsavedInput(val.trim() ?? '') - } + onInputChange={(_: unknown, val: string) => setUnsavedInput(val.trim() ?? '')} onBlur={handleOnBlur} blurOnSelect={false} multiple diff --git a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx index 060518f0..f972f08e 100644 --- a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx @@ -34,9 +34,7 @@ function BooleanInput({ name, label, formProps, Input }: BooleanInputProps) { const CustomInput = ( ) => - handleChangeValue(e) - } + onChange={(e: ChangeEvent) => handleChangeValue(e)} inputRef={ref} inputProps={{ 'aria-label': 'primary checkbox', @@ -46,12 +44,7 @@ function BooleanInput({ name, label, formProps, Input }: BooleanInputProps) { ); if (label) { - return ( - - ); + return ; } return CustomInput; diff --git a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx index c5b7d56f..fad14f08 100644 --- a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx @@ -15,14 +15,7 @@ export interface CheckboxInputProps { } function CheckboxInput({ name, label, formProps }: CheckboxInputProps) { - return ( - - ); + return ; } export default CheckboxInput; diff --git a/src/components/inputs/react-hook-form/booleans/switch-input.tsx b/src/components/inputs/react-hook-form/booleans/switch-input.tsx index eb24b1b8..a075e52a 100644 --- a/src/components/inputs/react-hook-form/booleans/switch-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/switch-input.tsx @@ -15,14 +15,7 @@ export interface SwitchInputProps { } function SwitchInput({ name, label, formProps }: SwitchInputProps) { - return ( - - ); + return ; } export default SwitchInput; diff --git a/src/components/inputs/react-hook-form/directory-items-input.tsx b/src/components/inputs/react-hook-form/directory-items-input.tsx index f78c4810..49350152 100644 --- a/src/components/inputs/react-hook-form/directory-items-input.tsx +++ b/src/components/inputs/react-hook-form/directory-items-input.tsx @@ -5,14 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - Chip, - FormControl, - Grid, - IconButton, - Theme, - Tooltip, -} from '@mui/material'; +import { Chip, FormControl, Grid, IconButton, Theme, Tooltip } from '@mui/material'; import FolderIcon from '@mui/icons-material/Folder'; import { useCallback, useMemo, useState } from 'react'; import { useController, useFieldArray } from 'react-hook-form'; @@ -94,8 +87,7 @@ function DirectoryItemsInput({ const [expanded, setExpanded] = useState([]); const [multiSelect, setMultiSelect] = useState(true); const types = useMemo(() => [elementType], [elementType]); - const [directoryItemSelectorOpen, setDirectoryItemSelectorOpen] = - useState(false); + const [directoryItemSelectorOpen, setDirectoryItemSelectorOpen] = useState(false); const { fields: elements, append, @@ -121,22 +113,14 @@ function DirectoryItemsInput({ // if we select a chip and return a new values, we remove it to be replaced if (selected?.length > 0 && values?.length > 0) { selected.forEach((chip) => { - remove( - getValues(name).findIndex( - (item: any) => item.id === chip - ) - ); + remove(getValues(name).findIndex((item: any) => item.id === chip)); }); } values.forEach((value) => { const { icon, children, ...otherElementAttributes } = value; // Check if the element is already present - if ( - getValues(name).find( - (v: any) => v?.id === otherElementAttributes.id - ) !== undefined - ) { + if (getValues(name).find((v: any) => v?.id === otherElementAttributes.id) !== undefined) { snackError({ messageTxt: '', headerId: 'directory_items_input/ElementAlreadyUsed', @@ -150,16 +134,7 @@ function DirectoryItemsInput({ setDirectoryItemSelectorOpen(false); setSelected([]); }, - [ - append, - getValues, - snackError, - name, - onRowChanged, - onChange, - selected, - remove, - ] + [append, getValues, snackError, name, onRowChanged, onChange, selected, remove] ); const removeElements = useCallback( @@ -176,18 +151,14 @@ function DirectoryItemsInput({ const chips = getValues(name) as any[]; const chip = chips.at(index)?.id; if (chip) { - directorySvc - .fetchDirectoryElementPath(chip) - .then((response: any[]) => { - const path = response - .filter((e) => e.elementUuid !== chip) - .map((e) => e.elementUuid); + directorySvc.fetchDirectoryElementPath(chip).then((response: any[]) => { + const path = response.filter((e) => e.elementUuid !== chip).map((e) => e.elementUuid); - setExpanded(path); - setSelected([chip]); - setDirectoryItemSelectorOpen(true); - setMultiSelect(false); - }); + setExpanded(path); + setSelected([chip]); + setDirectoryItemSelectorOpen(true); + setMultiSelect(false); + }); } }, [getValues, name] @@ -206,14 +177,7 @@ function DirectoryItemsInput({ {elements?.length === 0 && label && ( )} {elements?.length > 0 && ( @@ -226,11 +190,7 @@ function DirectoryItemsInput({ onClick={() => handleChipClick(index)} label={ - } + text={} sx={{ width: '100%' }} /> } @@ -258,9 +218,7 @@ function DirectoryItemsInput({ - {!hideErrorMessage && ( - - )} + {!hideErrorMessage && } React.ReactNode; + InputField: ({ message }: { message: string | React.ReactNode }) => React.ReactNode; } function ErrorInput({ name, InputField }: ErrorInputProps) { diff --git a/src/components/inputs/react-hook-form/numbers/integer-input.tsx b/src/components/inputs/react-hook-form/numbers/integer-input.tsx index c6d93728..41001789 100644 --- a/src/components/inputs/react-hook-form/numbers/integer-input.tsx +++ b/src/components/inputs/react-hook-form/numbers/integer-input.tsx @@ -12,10 +12,7 @@ function IntegerInput(props: TextInputProps) { if (value === '-') { return value; } - return value === null || - (typeof value === 'number' && Number.isNaN(value)) - ? '' - : value.toString(); + return value === null || (typeof value === 'number' && Number.isNaN(value)) ? '' : value.toString(); }; const outputTransform = (value: string) => { diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index 8d3c87c5..20c98b25 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -16,8 +16,7 @@ type CustomFormContextProps = { language?: GsLangUser; }; -export type MergedFormContextProps = UseFormReturn & - CustomFormContextProps; +export type MergedFormContextProps = UseFormReturn & CustomFormContextProps; type CustomFormProviderProps = PropsWithChildren; @@ -28,13 +27,7 @@ export const CustomFormContext = createContext({ }); function CustomFormProvider(props: CustomFormProviderProps) { - const { - validationSchema, - removeOptional, - language, - children, - ...formMethods - } = props; + const { validationSchema, removeOptional, language, children, ...formMethods } = props; return ( diff --git a/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts b/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts index 75b6efcc..62d395a2 100644 --- a/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts +++ b/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts @@ -7,10 +7,7 @@ import { useFormContext } from 'react-hook-form'; import { useContext } from 'react'; -import { - CustomFormContext, - MergedFormContextProps, -} from './custom-form-provider'; +import { CustomFormContext, MergedFormContextProps } from './custom-form-provider'; const useCustomFormContext = (): MergedFormContextProps => { const formMethods = useFormContext(); diff --git a/src/components/inputs/react-hook-form/radio-input.tsx b/src/components/inputs/react-hook-form/radio-input.tsx index 830abc13..9352662e 100644 --- a/src/components/inputs/react-hook-form/radio-input.tsx +++ b/src/components/inputs/react-hook-form/radio-input.tsx @@ -5,14 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - FormControl, - FormControlLabel, - FormLabel, - Radio, - RadioGroup, - RadioGroupProps, -} from '@mui/material'; +import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, RadioGroupProps } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { useController } from 'react-hook-form'; import FieldLabel from './utils/field-label'; @@ -42,13 +35,7 @@ function RadioInput({ name, label, id, options, formProps }: RadioInputProps) { )} - + {options.map((option) => ( } diff --git a/src/components/inputs/react-hook-form/range-input.tsx b/src/components/inputs/react-hook-form/range-input.tsx index 67f91900..175a91ce 100644 --- a/src/components/inputs/react-hook-form/range-input.tsx +++ b/src/components/inputs/react-hook-form/range-input.tsx @@ -17,8 +17,7 @@ import FieldConstants from '../../../utils/field-constants'; const style = { inputLegend: (theme: any) => ({ - backgroundImage: - 'linear-gradient(rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.16))', + backgroundImage: 'linear-gradient(rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.16))', backgroundColor: theme.palette.background.paper, padding: '0 8px 0 8px', }), @@ -46,22 +45,16 @@ export const getRangeInputSchema = (name: string) => ({ [name]: yup.object().shape( { [FieldConstants.OPERATION_TYPE]: yup.string(), - [FieldConstants.VALUE_1]: yup - .number() - .when([FieldConstants.OPERATION_TYPE, FieldConstants.VALUE_2], { - is: (operationType: string, value2: unknown) => - operationType === RangeType.RANGE.id && value2 !== null, - then: (schema) => schema.required(), - otherwise: (schema) => schema.nullable(), - }), - [FieldConstants.VALUE_2]: yup - .number() - .when([FieldConstants.OPERATION_TYPE, FieldConstants.VALUE_1], { - is: (operationType: string, value1: unknown) => - operationType === RangeType.RANGE.id && value1 !== null, - then: (schema) => schema.required(), - otherwise: (schema) => schema.nullable(), - }), + [FieldConstants.VALUE_1]: yup.number().when([FieldConstants.OPERATION_TYPE, FieldConstants.VALUE_2], { + is: (operationType: string, value2: unknown) => operationType === RangeType.RANGE.id && value2 !== null, + then: (schema) => schema.required(), + otherwise: (schema) => schema.nullable(), + }), + [FieldConstants.VALUE_2]: yup.number().when([FieldConstants.OPERATION_TYPE, FieldConstants.VALUE_1], { + is: (operationType: string, value1: unknown) => operationType === RangeType.RANGE.id && value1 !== null, + then: (schema) => schema.required(), + otherwise: (schema) => schema.nullable(), + }), }, [[FieldConstants.VALUE_1, FieldConstants.VALUE_2]] ), @@ -77,10 +70,7 @@ function RangeInput({ name, label }: RangeInputProps) { name: `${name}.${FieldConstants.OPERATION_TYPE}`, }); - const isOperationTypeRange = useMemo( - () => watchOperationType === RangeType.RANGE.id, - [watchOperationType] - ); + const isOperationTypeRange = useMemo(() => watchOperationType === RangeType.RANGE.id, [watchOperationType]); const firstValueField = ( val.map((code: string, index: number) => ( - + )) } /> diff --git a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx index 27f74a55..cacac0c4 100644 --- a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx @@ -15,11 +15,7 @@ interface MuiSelectInputProps { } // This input use Mui select instead of Autocomplete which can be needed some time (like in FormControl) -function MuiSelectInput({ - name, - options, - ...props -}: MuiSelectInputProps & SelectProps) { +function MuiSelectInput({ name, options, ...props }: MuiSelectInputProps & SelectProps) { const { field: { value, onChange }, } = useController({ @@ -29,10 +25,7 @@ function MuiSelectInput({ return ( - {(fieldData.operators as OperatorOption[])?.map( - (option) => ( - - {intl.formatMessage({ id: option.label })} - - ) - )} + {(fieldData.operators as OperatorOption[])?.map((option) => ( + + {intl.formatMessage({ id: option.label })} + + ))} diff --git a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx index ea98d503..78c49e5d 100644 --- a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx +++ b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx @@ -10,13 +10,7 @@ import { QueryBuilderDnD } from '@react-querybuilder/dnd'; import * as ReactDnD from 'react-dnd'; import * as ReactDndHtml5Backend from 'react-dnd-html5-backend'; import { QueryBuilderMaterial } from '@react-querybuilder/material'; -import { - ActionWithRulesAndAddersProps, - Field, - formatQuery, - QueryBuilder, - RuleGroupTypeAny, -} from 'react-querybuilder'; +import { ActionWithRulesAndAddersProps, Field, formatQuery, QueryBuilder, RuleGroupTypeAny } from 'react-querybuilder'; import { useIntl } from 'react-intl'; import { useFormContext } from 'react-hook-form'; import { useCallback, useMemo } from 'react'; @@ -28,11 +22,7 @@ import ValueSelector from './value-selector'; import { COMBINATOR_OPTIONS } from '../../filter/expert/expert-filter-constants'; import ErrorInput from '../react-hook-form/error-management/error-input'; import FieldErrorAlert from '../react-hook-form/error-management/field-error-alert'; -import { - countRules, - getOperators, - queryValidator, -} from '../../filter/expert/expert-filter-utils'; +import { countRules, getOperators, queryValidator } from '../../filter/expert/expert-filter-utils'; import RemoveButton from './remove-button'; interface CustomReactQueryBuilderProps { @@ -67,13 +57,11 @@ function CustomReactQueryBuilder(props: CustomReactQueryBuilderProps) { (newQuery: RuleGroupTypeAny) => { const oldQuery = getValues(name); const hasQueryChanged = - formatQuery(oldQuery, 'json_without_ids') !== - formatQuery(newQuery, 'json_without_ids'); + formatQuery(oldQuery, 'json_without_ids') !== formatQuery(newQuery, 'json_without_ids'); const hasAddedRules = countRules(newQuery) > countRules(oldQuery); setValue(name, newQuery, { shouldDirty: hasQueryChanged, - shouldValidate: - isSubmitted && hasQueryChanged && !hasAddedRules, + shouldValidate: isSubmitted && hasQueryChanged && !hasAddedRules, }); }, [getValues, setValue, isSubmitted, name] @@ -90,18 +78,14 @@ function CustomReactQueryBuilder(props: CustomReactQueryBuilderProps) { <> - + - getOperators(fieldName, intl) - } + getOperators={(fieldName) => getOperators(fieldName, intl)} validator={queryValidator} controlClassnames={{ queryBuilder: 'queryBuilder-branches', diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index 0bca4ddd..3d55ecda 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -23,16 +23,7 @@ interface ElementValueEditorProps { } function ElementValueEditor(props: ElementValueEditorProps) { - const { - defaultValue, - name, - elementType, - equipmentTypes, - titleId, - hideErrorMessage, - itemFilter, - onChange, - } = props; + const { defaultValue, name, elementType, equipmentTypes, titleId, hideErrorMessage, itemFilter, onChange } = props; const { setValue } = useCustomFormContext(); useEffect(() => { @@ -43,20 +34,18 @@ function ElementValueEditor(props: ElementValueEditorProps) { defaultValue[0].length > 0 && uuidValidate(defaultValue[0]) ) { - exploreSvc - .fetchElementsInfos(defaultValue) - .then((childrenWithMetadata) => { - setValue( - name, - childrenWithMetadata.map((v: any) => { - return { - id: v.elementUuid, - name: v.elementName, - specificMetadata: v.specificMetadata, - }; - }) - ); - }); + exploreSvc.fetchElementsInfos(defaultValue).then((childrenWithMetadata) => { + setValue( + name, + childrenWithMetadata.map((v: any) => { + return { + id: v.elementUuid, + name: v.elementName, + specificMetadata: v.specificMetadata, + }; + }) + ); + }); } }, [name, defaultValue, elementType, setValue]); diff --git a/src/components/inputs/react-query-builder/property-value-editor.tsx b/src/components/inputs/react-query-builder/property-value-editor.tsx index f4d37bf1..5f85fa0f 100644 --- a/src/components/inputs/react-query-builder/property-value-editor.tsx +++ b/src/components/inputs/react-query-builder/property-value-editor.tsx @@ -28,11 +28,9 @@ function PropertyValueEditor(props: ExpertFilterPropertyProps) { const valid = useValid(valueEditorProps); const intl = useIntl(); - const { propertyName, propertyOperator, propertyValues } = - valueEditorProps?.value ?? {}; + const { propertyName, propertyOperator, propertyValues } = valueEditorProps?.value ?? {}; - const [equipmentPredefinedProps, setEquipmentType] = - usePredefinedProperties(equipmentType); + const [equipmentPredefinedProps, setEquipmentType] = usePredefinedProperties(equipmentType); useEffect(() => { setEquipmentType(equipmentType); @@ -43,8 +41,7 @@ function PropertyValueEditor(props: ExpertFilterPropertyProps) { }, [equipmentPredefinedProps]); const predefinedValues = useMemo(() => { - const predefinedForName: string[] = - equipmentPredefinedProps?.[propertyName]; + const predefinedForName: string[] = equipmentPredefinedProps?.[propertyName]; if (!predefinedForName) { return []; @@ -57,8 +54,7 @@ function PropertyValueEditor(props: ExpertFilterPropertyProps) { let updatedValue = { ...valueEditorProps?.value, [FieldConstants.PROPERTY_OPERATOR]: - valueEditorProps?.value?.propertyOperator ?? - PROPERTY_VALUE_OPERATORS[0].customName, + valueEditorProps?.value?.propertyOperator ?? PROPERTY_VALUE_OPERATORS[0].customName, [field]: value, }; // Reset the property values when the property name changes @@ -82,9 +78,7 @@ function PropertyValueEditor(props: ExpertFilterPropertyProps) { freeSolo autoSelect forcePopupIcon - renderInput={(params) => ( - - )} + renderInput={(params) => } onChange={(event, value: any) => { onChange(FieldConstants.PROPERTY_NAME, value); }} @@ -92,10 +86,7 @@ function PropertyValueEditor(props: ExpertFilterPropertyProps) {