diff --git a/.eslintrc.json b/.eslintrc.json index 5a4c5a4c72..ea1edb9167 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "extends": ["react-app", "plugin:prettier/recommended"], "ignorePatterns": [ // node_modules is implicitly always ignored - "build" + "build", + "coverage" ], "rules": { "prettier/prettier": "warn", diff --git a/.github/config/.licenserc.yaml b/.github/config/.licenserc.yaml index 17acef9400..eba10c7b0f 100644 --- a/.github/config/.licenserc.yaml +++ b/.github/config/.licenserc.yaml @@ -22,5 +22,6 @@ header: - '**/*.svg' - '**/*.css' - '**/*.conf' + - '**/*.properties' comment: on-failure diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 018e4675a4..99f3637ebf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,12 @@ name: CI -on: [push] +on: + push: + branches: + - 'main' + tags: + - 'v[0-9]*' + pull_request: jobs: license-headers: @@ -26,6 +32,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. with: + fetch-depth: 0 persist-credentials: false - name: Parse tag @@ -39,9 +46,15 @@ jobs: npm install npm run licenses-check npm run lint - npm run test + npm run test:coverage npm run build + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - name: Build and publish Docker image - Main if: github.ref == 'refs/heads/main' uses: elgohr/Publish-Docker-Github-Action@33a481be3e179353cb7793a92b57cf9a6c985860 # v4 diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json new file mode 100644 index 0000000000..80871e3dd1 --- /dev/null +++ b/.sonarlint/connectedMode.json @@ -0,0 +1,4 @@ +{ + "sonarCloudOrganization": "gridsuite", + "projectKey": "gridsuite_gridstudy-app" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d2af34c4b5..292e7a3721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.65.1", + "@gridsuite/commons-ui": "0.65.2", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -3203,9 +3203,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.65.1", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.65.1.tgz", - "integrity": "sha512-sm4GEUwITSJbi7BxDhc68CuSuUPovNRosa1TRiK0OSZQMI6hevntl/H26KlMtqCVBI1/Fde9vN8rToEOWtfC9Q==", + "version": "0.65.2", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.65.2.tgz", + "integrity": "sha512-DC1RNjQSceo46ld96xCM26z99T/DuR+0YHDgzEC2Sx0wEuEeN+siBtqME0CCbBJWtZMqZMjx5s4MwUNVYrFp1g==", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", diff --git a/package.json b/package.json index 07a22a4d7b..dd5ea9e086 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.65.1", + "@gridsuite/commons-ui": "0.65.2", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -59,6 +59,7 @@ "build": "tsc && vite build", "serve": "vite preview", "test": "jest", + "test:coverage": "jest --coverage", "lint": "eslint . --ext js,mjs,jsx,ts,mts,tsx --max-warnings 0", "licenses-check": "license-checker --summary --excludePrivatePackages --production --onlyAllow \"$( jq -r .onlyAllow[] license-checker-config.json | tr '\n' ';')\" --excludePackages \"$( jq -r .excludePackages[] license-checker-config.json | tr '\n' ';')\"" }, diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000000..8be02fde3a --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectKey=gridsuite_gridstudy-app +sonar.organization=gridsuite +sonar.projectBaseDir=src +sonar.javascript.lcov.reportPaths=./coverage/lcov.info diff --git a/src/components/diagrams/diagram-pane.jsx b/src/components/diagrams/diagram-pane.jsx index 74b6687ad1..cb669d4b05 100644 --- a/src/components/diagrams/diagram-pane.jsx +++ b/src/components/diagrams/diagram-pane.jsx @@ -44,7 +44,7 @@ import { useNameOrId } from '../utils/equipmentInfosHandler'; import { syncDiagramStateWithSessionStorage } from '../../redux/session-storage/diagram-state'; import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content'; import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content'; -import { useDebounce, useSnackMessage } from '@gridsuite/commons-ui'; +import { OverflowableText, useDebounce, useSnackMessage } from '@gridsuite/commons-ui'; import { setNetworkAreaDiagramNbVoltageLevels } from '../../redux/actions'; import { useIntl } from 'react-intl'; import { getSubstationSingleLineDiagram, getVoltageLevelSingleLineDiagram } from '../../services/study/network'; @@ -263,6 +263,10 @@ const styles = { position: 'absolute', marginLeft: '3em', }, + minimizedDiagramTitle: { + maxWidth: '17em', + paddingTop: '4px', + }, separator: { flexGrow: 1, display: 'flex', @@ -1010,7 +1014,12 @@ export function DiagramPane({ studyUuid, showInSpreadsheet, currentNode, visible ) } - label={getDiagramTitle(diagramView)} + label={ + + } onClick={() => handleOpenDiagramView(diagramView.id, diagramView.svgType)} onDelete={() => handleCloseDiagramView(diagramView.id, diagramView.svgType)} /> diff --git a/src/components/diagrams/diagram.jsx b/src/components/diagrams/diagram.jsx index bf574117e7..dcbe5737f6 100644 --- a/src/components/diagrams/diagram.jsx +++ b/src/components/diagrams/diagram.jsx @@ -130,31 +130,28 @@ const Diagram = (props) => { showCloseControl onClose={onCloseHandler} /> - - {props.warningToDisplay ? ( - + + {props.warningToDisplay ? ( - - ) : ( - - {props.children} - - - )} + ) : ( + <>{props.children} + )} + + ); diff --git a/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx b/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx index 19a9fbe41a..44dbd8a299 100644 --- a/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx +++ b/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx @@ -27,65 +27,93 @@ const dynamicCssRules = [ cssSelector: '.nad-edge-infos', // data on edges (arrows and values) belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 2200, + threshold: 2500, thresholdStatus: THRESHOLD_STATUS.ABOVE, }, { cssSelector: '.nad-label-box', // tooltips linked to nodes belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 3000, + threshold: 3500, thresholdStatus: THRESHOLD_STATUS.ABOVE, }, { cssSelector: '.nad-text-edges', // visual link between nodes and their tooltip belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 3000, + threshold: 3500, thresholdStatus: THRESHOLD_STATUS.ABOVE, }, { cssSelector: '[class^="nad-vl0to30"], [class*=" nad-vl0to30"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 4000, + threshold: 12000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, { cssSelector: '[class^="nad-vl30to50"], [class*=" nad-vl30to50"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 4000, + threshold: 12000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, { cssSelector: '[class^="nad-vl50to70"], [class*=" nad-vl50to70"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 9000, + threshold: 27000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, { cssSelector: '[class^="nad-vl70to120"], [class*=" nad-vl70to120"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 9000, + threshold: 27000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, { cssSelector: '[class^="nad-vl120to180"], [class*=" nad-vl120to180"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 12000, + threshold: 36000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, { cssSelector: '[class^="nad-vl180to300"], [class*=" nad-vl180to300"]', belowThresholdCssDeclaration: { display: 'block' }, aboveThresholdCssDeclaration: { display: 'none' }, - threshold: 20000, + threshold: 80000, thresholdStatus: THRESHOLD_STATUS.BELOW, }, + { + cssSelector: '.nad-disconnected .nad-edge-path', + belowThresholdCssDeclaration: { 'stroke-dasharray': '10, 10' }, + aboveThresholdCssDeclaration: { 'stroke-dasharray': '0.5%, 0.5%' }, + threshold: 2500, + thresholdStatus: THRESHOLD_STATUS.ABOVE, + }, + { + cssSelector: '.nad-branch-edges .nad-edge-path, .nad-3wt-edges .nad-edge-path', + belowThresholdCssDeclaration: { 'stroke-width': '3' }, + aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' }, + threshold: 1000, + thresholdStatus: THRESHOLD_STATUS.ABOVE, + }, + { + cssSelector: '.nad-branch-edges .nad-winding, .nad-3wt-nodes .nad-winding', + belowThresholdCssDeclaration: { 'stroke-width': '3' }, + aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' }, + threshold: 1000, + thresholdStatus: THRESHOLD_STATUS.ABOVE, + }, + { + cssSelector: '.nad-vl-nodes circle.nad-unknown-busnode', + belowThresholdCssDeclaration: { 'stroke-width': '3' }, + aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' }, + threshold: 1000, + thresholdStatus: THRESHOLD_STATUS.ABOVE, + }, ]; function NetworkAreaDiagramContent(props) { diff --git a/src/components/dialogs/connectivity/branch-connectivity-form.tsx b/src/components/dialogs/connectivity/branch-connectivity-form.tsx new file mode 100644 index 0000000000..3ea08dd98a --- /dev/null +++ b/src/components/dialogs/connectivity/branch-connectivity-form.tsx @@ -0,0 +1,78 @@ +/** + * 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 { Grid } from '@mui/material'; +import { gridItem, GridSection } from '../dialogUtils'; +import { ConnectivityForm } from './connectivity-form'; +import { CONNECTIVITY, CONNECTIVITY_1, CONNECTIVITY_2 } from 'components/utils/field-constants'; +import React, { FunctionComponent } from 'react'; +import useVoltageLevelsListInfos from '../../../hooks/use-voltage-levels-list-infos'; +import { CurrentTreeNode } from '../../../redux/reducer'; +import { UUID } from 'crypto'; + +interface BranchConnectivityFormProps { + currentNode: CurrentTreeNode; + studyUuid: UUID; + isModification?: boolean; + previousValues?: any; +} + +const BranchConnectivityForm: FunctionComponent = ({ + currentNode, + studyUuid, + isModification = false, + previousValues, +}) => { + const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNode.id); + const id1 = `${CONNECTIVITY}.${CONNECTIVITY_1}`; + const id2 = `${CONNECTIVITY}.${CONNECTIVITY_2}`; + + const connectivity1Field = ( + + ); + + const connectivity2Field = ( + + ); + + return ( + <> + + + {gridItem(connectivity1Field, 12)} + + + + {gridItem(connectivity2Field, 12)} + + + ); +}; + +export default BranchConnectivityForm; diff --git a/src/components/dialogs/connectivity/connectivity-form-utils.js b/src/components/dialogs/connectivity/connectivity-form-utils.js index 87731cf4b1..a88e98832a 100644 --- a/src/components/dialogs/connectivity/connectivity-form-utils.js +++ b/src/components/dialogs/connectivity/connectivity-form-utils.js @@ -12,6 +12,8 @@ import { CONNECTION_NAME, CONNECTION_POSITION, CONNECTIVITY, + CONNECTIVITY_1, + CONNECTIVITY_2, ID, NAME, VOLTAGE_LEVEL, @@ -44,6 +46,13 @@ export const getConnectivityPropertiesValidationSchema = (isEquipmentModificatio }; }; +export const getCon1andCon2WithPositionValidationSchema = (isEquipmentModification = false, id = CONNECTIVITY) => ({ + [id]: yup.object().shape({ + ...getConnectivityWithPositionValidationSchema(isEquipmentModification, CONNECTIVITY_1), + ...getConnectivityWithPositionValidationSchema(isEquipmentModification, CONNECTIVITY_2), + }), +}); + export const getConnectivityWithPositionValidationSchema = (isEquipmentModification = false, id = CONNECTIVITY) => ({ [id]: yup.object().shape({ [CONNECTION_DIRECTION]: yup.string().nullable(), @@ -74,6 +83,13 @@ export const getConnectivityPropertiesEmptyFormData = (isEquipmentModification = }; }; +export const getCont1Cont2WithPositionEmptyFormData = (isEquipmentModification = false, id = CONNECTIVITY) => ({ + [id]: { + ...getConnectivityWithPositionEmptyFormData(isEquipmentModification, CONNECTIVITY_1), + ...getConnectivityWithPositionEmptyFormData(isEquipmentModification, CONNECTIVITY_2), + }, +}); + export const getConnectivityWithPositionEmptyFormData = (isEquipmentModification = false, id = CONNECTIVITY) => ({ [id]: { ...getConnectivityPropertiesEmptyFormData(isEquipmentModification), @@ -164,3 +180,13 @@ export const getConnectivityFormData = ( }, }; }; + +export const createConnectivityData = (equipmentToModify, index) => ({ + busbarSectionId: equipmentToModify?.[`busOrBusbarSectionId${index}`]?.value ?? null, + connectionDirection: equipmentToModify?.[`connectionDirection${index}`]?.value ?? null, + connectionName: equipmentToModify?.[`connectionName${index}`]?.value ?? '', + connectionPosition: equipmentToModify?.[`connectionPosition${index}`]?.value ?? null, + voltageLevelId: equipmentToModify?.[`voltageLevelId${index}`]?.value ?? null, + terminalConnected: equipmentToModify?.[`terminal${index}Connected`]?.value ?? null, + isEquipmentModification: true, +}); diff --git a/src/components/dialogs/contingency-list-selector.jsx b/src/components/dialogs/contingency-list-selector.jsx index 95782c8070..df3095f139 100644 --- a/src/components/dialogs/contingency-list-selector.jsx +++ b/src/components/dialogs/contingency-list-selector.jsx @@ -27,6 +27,7 @@ import { DirectoryItemSelector } from '@gridsuite/commons-ui'; import { isNodeBuilt } from 'components/graph/util/model-functions'; import DeleteIcon from '@mui/icons-material/Delete.js'; import IconButton from '@mui/material/IconButton'; +import { toggleElementFromList } from 'components/utils/utils'; import { DialogActions } from '@mui/material'; function makeButton(onClick, message, disabled) { @@ -155,21 +156,22 @@ const ContingencyListSelector = (props) => { }; const handleSecondaryAction = useCallback( - (item) => ( - { - e.stopPropagation(); - removeFromFavorites([item]); - }} - size={'small'} - > - - - ), + (item, isItemHovered) => + isItemHovered && ( + { + e.stopPropagation(); + removeFromFavorites([item]); + }} + size={'small'} + > + + + ), [removeFromFavorites] ); @@ -189,7 +191,11 @@ const ContingencyListSelector = (props) => { selectedItems={checkedContingencyList} onSelectionChange={setCheckedContingencyList} secondaryAction={handleSecondaryAction} - enableSecondaryActionOnHover + onItemClick={(contingencyList) => + setCheckedContingencyList((oldCheckedElements) => [ + ...toggleElementFromList(contingencyList, oldCheckedElements, (element) => element.id), + ]) + } /> void; - currentNode: { id: string }; - studyUuid: UUID; } -const ImportModificationDialog: FunctionComponent = ({ - open, - onClose, - currentNode, - studyUuid, -}) => { +const ImportModificationDialog: FunctionComponent = ({ open, onClose }) => { const intl = useIntl(); const { snackError } = useSnackMessage(); + const studyUuid = useSelector((state: AppState) => state.studyUuid); + const currentNode = useSelector((state: AppState) => state.currentTreeNode); const processSelectedElements = (selectedElements: TreeViewFinderNodeProps[]) => { const copyInfos = { - copyType: CopyType.INSERT, + copyType: NetworkModificationCopyType.INSERT, }; const modificationUuidList = selectedElements.map((e) => e.id); // import selected modifications if (modificationUuidList.length > 0) { - copyOrMoveModifications(studyUuid, currentNode.id, modificationUuidList, copyInfos).catch((errmsg) => { + copyOrMoveModifications(studyUuid, currentNode?.id, modificationUuidList, copyInfos).catch((errmsg) => { snackError({ messageTxt: errmsg, headerId: 'errDuplicateModificationMsg', diff --git a/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx b/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx index e06e95344f..d45d3b0a86 100644 --- a/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx +++ b/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx @@ -8,10 +8,12 @@ import { Grid, Tab, Tabs } from '@mui/material'; import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { LineCreationDialogTab } from './creation/line-creation-dialog'; import { getTabIndicatorStyle, getTabStyle } from '../../../utils/tab-utils'; +import { LineCreationDialogTab } from './creation/line-creation-dialog'; +import { LineModificationDialogTab } from './modification/line-modification-dialog'; -const LineDialogTabs = ({ tabIndex, tabIndexesWithError, setTabIndex }) => { +const LineDialogTabs = ({ tabIndex, tabIndexesWithError, setTabIndex, isModification = false }) => { + const LineDialogTab = isModification ? LineModificationDialogTab : LineCreationDialogTab; return ( { sx: getTabIndicatorStyle(tabIndexesWithError, tabIndex), }} > + {isModification && ( + } + sx={getTabStyle(tabIndexesWithError, LineDialogTab.CONNECTIVITY_TAB)} + /> + )} } - sx={getTabStyle(tabIndexesWithError, LineCreationDialogTab.CHARACTERISTICS_TAB)} + sx={getTabStyle(tabIndexesWithError, LineDialogTab.CHARACTERISTICS_TAB)} /> } - sx={getTabStyle(tabIndexesWithError, LineCreationDialogTab.LIMITS_TAB)} + sx={getTabStyle(tabIndexesWithError, LineDialogTab.LIMITS_TAB)} /> diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx index 000e8496d0..5fbe37a0f8 100644 --- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx +++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx @@ -7,11 +7,10 @@ import React from 'react'; import { EQUIPMENT_NAME } from 'components/utils/field-constants'; -import { Box, Grid } from '@mui/material'; +import { Box, Grid, TextField } from '@mui/material'; import { filledTextField, gridItem } from 'components/dialogs/dialogUtils'; import LineDialogTabs from '../line-dialog-tabs'; import { TextInput } from '@gridsuite/commons-ui'; -import { TextField } from '@mui/material'; const LineModificationDialogHeader = ({ lineToModify, tabIndexesWithError, tabIndex, setTabIndex, equipmentId }) => { const lineIdField = ( @@ -55,6 +54,7 @@ const LineModificationDialogHeader = ({ lineToModify, tabIndexesWithError, tabIn tabIndex={tabIndex} tabIndexesWithError={tabIndexesWithError} setTabIndex={setTabIndex} + isModification={true} /> diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx index 7e16e34e15..4d3bc43853 100644 --- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx +++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx @@ -5,15 +5,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LineCreationDialogTab } from './line-modification-dialog'; +import { LineModificationDialogTab } from './line-modification-dialog'; import { Box } from '@mui/material'; import LimitsPane from '../../../limits/limits-pane'; import LineCharacteristicsPane from '../characteristics-pane/line-characteristics-pane'; +import React from 'react'; +import BranchConnectivityForm from '../../../connectivity/branch-connectivity-form.tsx'; const LineModificationDialogTabs = ({ studyUuid, currentNode, lineToModify, tabIndex }) => { return ( <> -