diff --git a/src/components/dialogs/parameters/dynamicsimulation/curve/dialog/curve-selector-utils.ts b/src/components/dialogs/parameters/dynamicsimulation/curve/dialog/curve-selector-utils.ts index e645cbccf2..c0036275f4 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/curve/dialog/curve-selector-utils.ts +++ b/src/components/dialogs/parameters/dynamicsimulation/curve/dialog/curve-selector-utils.ts @@ -313,6 +313,9 @@ export const buildExpertRules = ( }; }; +/** @deprecated The behavior of this local builder function differs from the backend one used by others (analysis results & spreadsheet). + * A new endpoint is now available to evaluate a global-filter on a network. To migrate to it. + */ export const buildExpertFilter = ( equipmentType: EQUIPMENT_TYPES, voltageLevelIds: string[] | undefined, diff --git a/src/components/results/common/global-filter/use-global-filter-results.ts b/src/components/results/common/global-filter/use-global-filter-results.ts new file mode 100644 index 0000000000..9fdf0a5a96 --- /dev/null +++ b/src/components/results/common/global-filter/use-global-filter-results.ts @@ -0,0 +1,46 @@ +/* + * Copyright © 2025, 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 type { NonEmptyTuple } from 'type-fest'; +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import type { GlobalFilter, GlobalFilters } from './global-filter-types'; +import { evaluateGlobalFilter } from '../../../../services/study/filter'; +import type { AppState } from '../../../../redux/reducer'; +import useGlobalFilters, { isGlobalFilterParameter } from './use-global-filters'; +import type { FilterEquipmentType } from '../../../../types/filter-lib/filter'; + +/* Because of ESLint react-hooks/rules-of-hooks, nullable value must be managed inside the hook, because + * React hooks can't be called conditionally and/or different order. */ +function useGlobalFiltersResults( + globalFilters: GlobalFilters | undefined, + equipmentTypes: NonEmptyTuple +) { + const { snackError } = useSnackMessage(); + const studyUuid = useSelector((state: AppState) => state.studyUuid); + const currentNodeUuid = useSelector((state: AppState) => state.currentTreeNode?.id); + const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid); + const [filteredIds, setFilteredIds] = useState(); + useEffect(() => { + if (studyUuid && currentRootNetworkUuid && currentNodeUuid && isGlobalFilterParameter(globalFilters)) { + evaluateGlobalFilter(studyUuid, currentNodeUuid, currentRootNetworkUuid, equipmentTypes, globalFilters) + .then(setFilteredIds) + .catch((error) => { + console.error('Error while fetching GlobalFilter results', error); + snackError({ headerId: 'FilterEvaluationError', messageTxt: `${error}` }); + }); + } + }, [currentNodeUuid, currentRootNetworkUuid, equipmentTypes, globalFilters, snackError, studyUuid]); + return filteredIds; +} + +export function useGlobalFilterResults(filters: GlobalFilter[], equipmentTypes: NonEmptyTuple) { + const { globalFilters, handleGlobalFilterChange } = useGlobalFilters(); + useEffect(() => handleGlobalFilterChange(filters), [filters, handleGlobalFilterChange]); + return useGlobalFiltersResults(globalFilters, equipmentTypes); +} diff --git a/src/components/results/common/global-filter/use-global-filters.ts b/src/components/results/common/global-filter/use-global-filters.ts index a773b61d81..5be8423191 100644 --- a/src/components/results/common/global-filter/use-global-filters.ts +++ b/src/components/results/common/global-filter/use-global-filters.ts @@ -9,88 +9,66 @@ import { useCallback, useState } from 'react'; import { GlobalFilter, GlobalFilters } from './global-filter-types'; import { FilterType } from '../utils'; -interface UseGlobalFiltersParams { - onFilterChange?: (filters: GlobalFilters) => void; +export function isGlobalFilterParameter(globalFilters: GlobalFilters | undefined): globalFilters is GlobalFilters { + return ( + globalFilters !== undefined && + ((globalFilters.countryCode && globalFilters.countryCode.length > 0) || + (globalFilters.nominalV && globalFilters.nominalV.length > 0) || + (globalFilters.genericFilter && globalFilters.genericFilter.length > 0) || + !!globalFilters.substationProperty) + ); } -export default function useGlobalFilters({ onFilterChange }: UseGlobalFiltersParams) { +export default function useGlobalFilters() { const [globalFilters, setGlobalFilters] = useState(); - const handleGlobalFilterChange = useCallback( - (value: GlobalFilter[]) => { - let newGlobalFilter: GlobalFilters = {}; - - const nominalVs = new Set( - value - .filter((filter: GlobalFilter) => filter.filterType === FilterType.VOLTAGE_LEVEL) - .map((filter: GlobalFilter) => filter.label) - ); + // see + const handleGlobalFilterChange = useCallback((value: GlobalFilter[]) => { + let newGlobalFilter: GlobalFilters = {}; - const genericFilters: Set = new Set( - value - .filter((filter: GlobalFilter): boolean => filter.filterType === FilterType.GENERIC_FILTER) - .map((filter: GlobalFilter) => filter.uuid ?? '') - .filter((uuid: string): boolean => uuid !== '') - ); + const nominalVs = new Set( + value + .filter((filter: GlobalFilter) => filter.filterType === FilterType.VOLTAGE_LEVEL) + .map((filter: GlobalFilter) => filter.label) + ); - const countryCodes = new Set( - value - .filter((filter: GlobalFilter) => filter.filterType === FilterType.COUNTRY) - .map((filter: GlobalFilter) => filter.label) - ); + const genericFilters: Set = new Set( + value + .filter((filter: GlobalFilter): boolean => filter.filterType === FilterType.GENERIC_FILTER) + .map((filter: GlobalFilter) => filter.uuid ?? '') + .filter((uuid: string): boolean => uuid !== '') + ); - const substationProperties: Map = new Map(); + const countryCodes = new Set( value - .filter((filter: GlobalFilter) => filter.filterType === FilterType.SUBSTATION_PROPERTY) - .forEach((filter: GlobalFilter) => { - if (filter.filterSubtype) { - const subtypeSubstationProperties = substationProperties.get(filter.filterSubtype); - if (subtypeSubstationProperties) { - subtypeSubstationProperties.push(filter.label); - } else { - substationProperties.set(filter.filterSubtype, [filter.label]); - } + .filter((filter: GlobalFilter) => filter.filterType === FilterType.COUNTRY) + .map((filter: GlobalFilter) => filter.label) + ); + + const substationProperties: Map = new Map(); + value + .filter((filter: GlobalFilter) => filter.filterType === FilterType.SUBSTATION_PROPERTY) + .forEach((filter: GlobalFilter) => { + if (filter.filterSubtype) { + const subtypeSubstationProperties = substationProperties.get(filter.filterSubtype); + if (subtypeSubstationProperties) { + subtypeSubstationProperties.push(filter.label); + } else { + substationProperties.set(filter.filterSubtype, [filter.label]); } - }); - - newGlobalFilter.nominalV = [...nominalVs]; - newGlobalFilter.countryCode = [...countryCodes]; - newGlobalFilter.genericFilter = [...genericFilters]; + } + }); - if (substationProperties.size > 0) { - newGlobalFilter.substationProperty = Object.fromEntries(substationProperties); - } - - setGlobalFilters(newGlobalFilter); - onFilterChange?.(newGlobalFilter); - }, - [onFilterChange] - ); - - const getGlobalFilterParameter = useCallback((globalFilters: GlobalFilters | undefined) => { - let shouldSentParameter = false; - - if (globalFilters) { - if ( - (globalFilters.countryCode && globalFilters.countryCode.length > 0) || - (globalFilters.nominalV && globalFilters.nominalV.length > 0) || - (globalFilters.genericFilter && globalFilters.genericFilter.length > 0) || - globalFilters.substationProperty - ) { - shouldSentParameter = true; - } - } + newGlobalFilter.nominalV = [...nominalVs]; + newGlobalFilter.countryCode = [...countryCodes]; + newGlobalFilter.genericFilter = [...genericFilters]; - if (!shouldSentParameter) { - return undefined; + if (substationProperties.size > 0) { + newGlobalFilter.substationProperty = Object.fromEntries(substationProperties); } - return globalFilters; + setGlobalFilters(newGlobalFilter); }, []); - return { - globalFilters, - handleGlobalFilterChange, - getGlobalFilterParameter, - }; + return { globalFilters, handleGlobalFilterChange }; } diff --git a/src/components/results/loadflow/load-flow-result-tab.tsx b/src/components/results/loadflow/load-flow-result-tab.tsx index 764f202af1..adce0bbedf 100644 --- a/src/components/results/loadflow/load-flow-result-tab.tsx +++ b/src/components/results/loadflow/load-flow-result-tab.tsx @@ -45,7 +45,7 @@ import { import { EQUIPMENT_TYPES } from '../../utils/equipment-types'; import type { UUID } from 'node:crypto'; import GlobalFilterSelector from '../common/global-filter/global-filter-selector'; -import useGlobalFilters from '../common/global-filter/use-global-filters'; +import useGlobalFilters, { isGlobalFilterParameter } from '../common/global-filter/use-global-filters'; import { useGlobalFilterOptions } from '../common/global-filter/use-global-filter-options'; import { ICellRendererParams } from 'ag-grid-community'; import { Button, Tooltip } from '@mui/material'; @@ -88,7 +88,7 @@ export const LoadFlowResultTab: FunctionComponent = ({ const { filters } = useFilterSelector(AgGridFilterType.Loadflow, mappingTabs(tabIndex)); const { countriesFilter, voltageLevelsFilter, propertiesFilter } = useGlobalFilterOptions(); - const { globalFilters, handleGlobalFilterChange, getGlobalFilterParameter } = useGlobalFilters({}); + const { globalFilters, handleGlobalFilterChange } = useGlobalFilters(); const { onLinkClick } = useLoadFlowResultColumnActions({ studyUuid, nodeUuid, @@ -129,16 +129,20 @@ export const LoadFlowResultTab: FunctionComponent = ({ colId: FROM_COLUMN_TO_FIELD_LIMIT_VIOLATION_RESULT[sort.colId], })), filters: mapFieldsToColumnsFilter(updatedFilters, mappingFields(tabIndex)), - ...(getGlobalFilterParameter(globalFilters) !== undefined && { - globalFilters: { - ...getGlobalFilterParameter(globalFilters), - limitViolationsTypes: - tabIndex === 0 ? [LimitTypes.CURRENT] : [LimitTypes.HIGH_VOLTAGE, LimitTypes.LOW_VOLTAGE], - }, - }), + ...(isGlobalFilterParameter(globalFilters) + ? { + globalFilters: { + ...globalFilters, + limitViolationsTypes: + tabIndex === 0 + ? [LimitTypes.CURRENT] + : [LimitTypes.HIGH_VOLTAGE, LimitTypes.LOW_VOLTAGE], + }, + } + : {}), }); }, - [tabIndex, filters, intl, sortConfig, getGlobalFilterParameter, globalFilters] + [tabIndex, filters, intl, sortConfig, globalFilters] ); const fetchloadflowResultWithParameters = useMemo(() => { diff --git a/src/components/results/securityanalysis/security-analysis-result-tab.tsx b/src/components/results/securityanalysis/security-analysis-result-tab.tsx index 2f57940f50..f87b560a52 100644 --- a/src/components/results/securityanalysis/security-analysis-result-tab.tsx +++ b/src/components/results/securityanalysis/security-analysis-result-tab.tsx @@ -44,7 +44,7 @@ import { securityAnalysisResultInvalidations } from '../../computing-status/use- import { useParameterState } from 'components/dialogs/parameters/use-parameters-state'; import { useNodeData } from 'components/use-node-data'; import GlobalFilterSelector from '../common/global-filter/global-filter-selector'; -import useGlobalFilters from '../common/global-filter/use-global-filters'; +import useGlobalFilters, { isGlobalFilterParameter } from '../common/global-filter/use-global-filters'; import { useGlobalFilterOptions } from '../common/global-filter/use-global-filter-options'; import { EQUIPMENT_TYPES } from '../../utils/equipment-types'; import { usePaginationSelector } from 'hooks/use-pagination-selector'; @@ -124,7 +124,7 @@ export const SecurityAnalysisResultTab: FunctionComponent state.computingStatus[ComputingType.SENSITIVITY_ANALYSIS] ); - const { globalFilters, handleGlobalFilterChange, getGlobalFilterParameter } = useGlobalFilters({}); + const { globalFilters, handleGlobalFilterChange } = useGlobalFilters(); const { countriesFilter, voltageLevelsFilter, propertiesFilter } = useGlobalFilterOptions(); const handleSensiNOrNkIndexChange = (event: SyntheticEvent, newNOrNKIndex: number) => { @@ -104,7 +104,7 @@ function SensitivityAnalysisResultTab({ csvHeaders={csvHeaders} nOrNkIndex={nOrNkIndex} sensiKind={sensiTab} - globalFilters={getGlobalFilterParameter(globalFilters)} + globalFilters={isGlobalFilterParameter(globalFilters) ? globalFilters : undefined} disabled={isCsvButtonDisabled} /> @@ -116,7 +116,7 @@ function SensitivityAnalysisResultTab({ currentRootNetworkUuid={currentRootNetworkUuid} setCsvHeaders={setCsvHeaders} setIsCsvButtonDisabled={setIsCsvButtonDisabled} - globalFilters={getGlobalFilterParameter(globalFilters)} + globalFilters={isGlobalFilterParameter(globalFilters) ? globalFilters : undefined} /> )} diff --git a/src/components/results/shortcircuit/shortcircuit-analysis-result-tab.tsx b/src/components/results/shortcircuit/shortcircuit-analysis-result-tab.tsx index 8618523ff1..a0f1f966df 100644 --- a/src/components/results/shortcircuit/shortcircuit-analysis-result-tab.tsx +++ b/src/components/results/shortcircuit/shortcircuit-analysis-result-tab.tsx @@ -29,7 +29,7 @@ import type { UUID } from 'node:crypto'; import { ColDef, GridReadyEvent, RowDataUpdatedEvent } from 'ag-grid-community'; import GlobalFilterSelector from '../common/global-filter/global-filter-selector'; import { EQUIPMENT_TYPES } from '../../utils/equipment-types'; -import useGlobalFilters from '../common/global-filter/use-global-filters'; +import useGlobalFilters, { isGlobalFilterParameter } from '../common/global-filter/use-global-filters'; import { useGlobalFilterOptions } from '../common/global-filter/use-global-filter-options'; interface ShortCircuitAnalysisResultTabProps { @@ -93,7 +93,7 @@ export const ShortCircuitAnalysisResultTab: FunctionComponent ) : ( diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-spreadsheet-gs-filter.ts b/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-spreadsheet-gs-filter.ts index 7dcd649fe4..2e9c790c45 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-spreadsheet-gs-filter.ts +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-spreadsheet-gs-filter.ts @@ -5,158 +5,46 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { type RefObject, useCallback, useEffect, useState } from 'react'; -import { type FilterChangedEvent, type IRowNode } from 'ag-grid-community'; +import { type RefObject, useCallback, useEffect, useMemo } from 'react'; +import type { FilterChangedEvent, GridOptions } from 'ag-grid-community'; import type { UUID } from 'node:crypto'; import { useSelector } from 'react-redux'; import { type AppState } from '../../../../../redux/reducer'; -import { evaluateFilters, evaluateJsonFilter } from '../../../../../services/study/filter'; -import { buildExpertFilter } from '../../../../dialogs/parameters/dynamicsimulation/curve/dialog/curve-selector-utils'; import { SpreadsheetEquipmentType } from '../../../types/spreadsheet.type'; -import type { GlobalFilter } from '../../../../results/common/global-filter/global-filter-types'; import { type AgGridReact } from 'ag-grid-react'; import { ROW_INDEX_COLUMN_ID } from '../../../constants'; -import type { EQUIPMENT_TYPES } from '../../../../utils/equipment-types'; -import { useSnackMessage } from '@gridsuite/commons-ui'; - -async function buildAndEvaluateFilter( - equipmentType: EQUIPMENT_TYPES, - countries: string[], - nominalVoltages: number[], - substationProperties: Record, - idsByEqType: Record, - studyUuid: UUID, - currentNodeId: UUID, - currentRootNetworkUuid: UUID -) { - const computedFilter = buildExpertFilter( - equipmentType, - undefined, - countries, - nominalVoltages, - substationProperties, - idsByEqType - ); - if (computedFilter.rules.rules && computedFilter.rules.rules.length > 0) { - return await evaluateJsonFilter(studyUuid, currentNodeId, currentRootNetworkUuid, computedFilter); - } else { - return [] as Awaited>; - } -} +import { useGlobalFilterResults } from '../../../../results/common/global-filter/use-global-filter-results'; +import { FilterEquipmentType } from '../../../../../types/filter-lib/filter'; export const refreshSpreadsheetAfterFilterChanged = (event: FilterChangedEvent) => { event.api.refreshCells({ columns: [ROW_INDEX_COLUMN_ID], force: true }); }; -export const useSpreadsheetGlobalFilter = ( - gridRef: RefObject, +type ObjWithId = { id: T }; +export function useSpreadsheetGlobalFilter( + gridRef: RefObject>, tabUuid: UUID, equipmentType: SpreadsheetEquipmentType -) => { - const { snackError } = useSnackMessage(); - const [filterIds, setFilterIds] = useState([]); +) { const globalFilterSpreadsheetState = useSelector((state: AppState) => state.globalFilterSpreadsheetState[tabUuid]); - - const studyUuid = useSelector((state: AppState) => state.studyUuid); - const currentNode = useSelector((state: AppState) => state.currentTreeNode); - const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid); - - const applyGlobalFilter = useCallback( - async (globalFilters: GlobalFilter[]) => { - if (studyUuid && currentNode && currentRootNetworkUuid) { - const countries = globalFilters - ?.filter((filter) => filter.filterType === 'country') - .map((filter) => filter.label); - const nominalVoltages = globalFilters - ?.filter((filter) => filter.filterType === 'voltageLevel') - .map((filter) => Number(filter.label)); - const substationProperties = globalFilters - ?.filter((filter) => filter.filterType === 'substationProperty') - .reduce>((acc, item) => { - if (item.filterSubtype) { - if (!acc[item.filterSubtype]) { - acc[item.filterSubtype] = []; - } - acc[item.filterSubtype].push(item.label); - } - return acc; - }, {}); - const genericFilters = globalFilters?.filter((filter) => filter.filterType === 'genericFilter'); - - let idsByEqType: Record = {}; - let firstInitByEqType: Record = {}; - - if (genericFilters?.length > 0) { - // We currently pre evaluate generic filters because expert filters can't be referenced by other expert filters as of now - const filtersUuids = genericFilters - .flatMap((filter) => filter.uuid) - .filter((uuid): uuid is UUID => uuid !== undefined); - const response = await evaluateFilters( - studyUuid, - currentNode.id, - currentRootNetworkUuid, - filtersUuids - ); - response.forEach((filterEq) => { - if (filterEq.identifiableAttributes.length > 0) { - const eqType = filterEq.identifiableAttributes[0].type; - if (!idsByEqType[eqType]) { - idsByEqType[eqType] = []; - firstInitByEqType[eqType] = true; - } - const equipIds = filterEq.identifiableAttributes.map((identifiable) => identifiable.id); - if (idsByEqType[eqType].length === 0 && firstInitByEqType[eqType]) { - idsByEqType[eqType] = equipIds; - firstInitByEqType[eqType] = false; - } else if (idsByEqType[eqType].length > 0 && equipIds.length > 0) { - // intersection here because it is a AND - idsByEqType[eqType] = idsByEqType[eqType].filter((id) => equipIds.includes(id)); - } - } - }); - } - Promise.all( - (equipmentType === SpreadsheetEquipmentType.BRANCH - ? [SpreadsheetEquipmentType.LINE, SpreadsheetEquipmentType.TWO_WINDINGS_TRANSFORMER] - : [equipmentType] - ).map((eType) => - buildAndEvaluateFilter( - eType as unknown as EQUIPMENT_TYPES, - countries, - nominalVoltages, - substationProperties, - idsByEqType, - studyUuid, - currentNode.id, - currentRootNetworkUuid - ) - ) - ).then( - (values) => { - // we don't do a set because as equipment types are different/don't overlap, there isn't common id between types - setFilterIds(values.flatMap((ias) => ias.map((ia) => ia.id))); - }, - (reason) => { - console.error('Error while evaluating the filter(s) in the spreadsheet:', reason); - snackError({ headerId: 'FilterEvaluationError', messageTxt: reason.message }); - } - ); - } - }, - [currentNode, currentRootNetworkUuid, equipmentType, snackError, studyUuid] + const equipmentTypes = useMemo( + () => + equipmentType === SpreadsheetEquipmentType.BRANCH + ? ([FilterEquipmentType.LINE, FilterEquipmentType.TWO_WINDINGS_TRANSFORMER] as const) + : ([equipmentType as unknown as FilterEquipmentType] as const), + [equipmentType] ); - + const filterIds = useGlobalFilterResults(globalFilterSpreadsheetState, equipmentTypes); useEffect(() => { - applyGlobalFilter(globalFilterSpreadsheetState); gridRef.current?.api?.onFilterChanged(); - }, [applyGlobalFilter, tabUuid, globalFilterSpreadsheetState, gridRef]); - - const doesFormulaFilteringPass = useCallback((node: IRowNode) => filterIds.includes(node.data.id), [filterIds]); - - const isExternalFilterPresent = useCallback( - () => globalFilterSpreadsheetState?.length > 0, + }, [filterIds, gridRef]); + const doesFormulaFilteringPass = useCallback['doesExternalFilterPass']>>( + (node) => node.data?.id !== undefined && (filterIds?.includes(node.data?.id) ?? true), + [filterIds] + ); + const isExternalFilterPresent = useCallback['isExternalFilterPresent']>>( + () => globalFilterSpreadsheetState.length > 0, [globalFilterSpreadsheetState] ); - return { doesFormulaFilteringPass, isExternalFilterPresent }; -}; +} diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/global-filter/spreadsheet-global-filter.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/global-filter/spreadsheet-global-filter.tsx index bd6090a692..4c1c2cecf1 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/global-filter/spreadsheet-global-filter.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/global-filter/spreadsheet-global-filter.tsx @@ -56,23 +56,28 @@ export default function SpreadsheetGlobalFilter({ tableDefinition }: Readonly( () => [ - ...(tableDefinition.type === SpreadsheetEquipmentType.SUBSTATION ? [] : voltageLevelsFilter), + ...(tableDefinition.type === SpreadsheetEquipmentType.SUBSTATION || + tableDefinition.type === SpreadsheetEquipmentType.HVDC_LINE + ? [] + : voltageLevelsFilter), ...countriesFilter, ...propertiesFilter, ], [countriesFilter, propertiesFilter, tableDefinition.type, voltageLevelsFilter] ); - const filterTypes = useMemo( - () => [ + const filterTypes = useMemo(() => { + let fTypes = [ ...(tableDefinition.type === SpreadsheetEquipmentType.BRANCH ? [EQUIPMENT_TYPES.LINE, EQUIPMENT_TYPES.TWO_WINDINGS_TRANSFORMER] : [tableDefinition.type as unknown as EQUIPMENT_TYPES]), EQUIPMENT_TYPES.SUBSTATION, - EQUIPMENT_TYPES.VOLTAGE_LEVEL, - ], - [tableDefinition.type] - ); + ]; + if (tableDefinition.type !== SpreadsheetEquipmentType.SUBSTATION) { + fTypes.push(EQUIPMENT_TYPES.VOLTAGE_LEVEL); + } + return fTypes; + }, [tableDefinition.type]); useEffect(() => { if (globalFilterSpreadsheetState) { diff --git a/src/components/voltage-init-result-tab.tsx b/src/components/voltage-init-result-tab.tsx index 2fa3490fc8..cefe609537 100644 --- a/src/components/voltage-init-result-tab.tsx +++ b/src/components/voltage-init-result-tab.tsx @@ -15,7 +15,7 @@ import { AppState } from '../redux/reducer'; import { VoltageInitResult } from './voltage-init-result'; import { useMemo } from 'react'; import { fetchVoltageInitResult } from '../services/study/voltage-init'; -import useGlobalFilters from './results/common/global-filter/use-global-filters'; +import useGlobalFilters, { isGlobalFilterParameter } from './results/common/global-filter/use-global-filters'; import { useGlobalFilterOptions } from './results/common/global-filter/use-global-filter-options'; export type VoltageInitResultTabProps = { @@ -33,24 +33,19 @@ export function VoltageInitResultTab({ (state: AppState) => state.computingStatus[ComputingType.VOLTAGE_INITIALIZATION] ); const { countriesFilter, voltageLevelsFilter, propertiesFilter } = useGlobalFilterOptions(); - const { globalFilters, handleGlobalFilterChange, getGlobalFilterParameter } = useGlobalFilters({}); + const { globalFilters, handleGlobalFilterChange } = useGlobalFilters(); const globalFilterOptions = useMemo( () => [...voltageLevelsFilter, ...countriesFilter, ...propertiesFilter], [voltageLevelsFilter, countriesFilter, propertiesFilter] ); const fetchVoltageInitResultWithGlobalFilters = useMemo( - () => (studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => { - return fetchVoltageInitResult(studyUuid, nodeUuid, currentRootNetworkUuid, { + () => (studyUuid: UUID, nodeUuid: UUID, currentRootNetworkUuid: UUID) => + fetchVoltageInitResult(studyUuid, nodeUuid, currentRootNetworkUuid, { filters: null, - ...(getGlobalFilterParameter(globalFilters) !== undefined && { - globalFilters: { - ...getGlobalFilterParameter(globalFilters), - }, - }), - }); - }, - [getGlobalFilterParameter, globalFilters] + ...(isGlobalFilterParameter(globalFilters) ? { globalFilters: { ...globalFilters } } : {}), + }), + [globalFilters] ); const { result: voltageInitResult } = useNodeData({ diff --git a/src/services/study/filter.ts b/src/services/study/filter.ts index 35c5d19e78..fcde805041 100644 --- a/src/services/study/filter.ts +++ b/src/services/study/filter.ts @@ -5,12 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { getRequestParamFromList } from '../utils'; +import type { NonEmptyTuple } from 'type-fest'; import { backendFetchJson } from '@gridsuite/commons-ui'; import type { UUID } from 'node:crypto'; +import { getRequestParamFromList } from '../utils'; import { getStudyUrlWithNodeUuidAndRootNetworkUuid } from './index'; import { RuleGroupTypeExport } from '../../components/dialogs/filter/expert/expert-filter.type'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; +import type { GlobalFilters } from '../../components/results/common/global-filter/global-filter-types'; +import type { FilterEquipmentType } from '../../types/filter-lib/filter'; export interface ExpertFilter { id?: UUID; @@ -38,6 +41,35 @@ export interface IdentifiableAttributes { distributionKey: number; } +/** + * Evaluate a {@link GlobalFilter} on a network + * @param studyUuid the {@link UUID} of the study to work on + * @param currentNodeUuid the current node to get the variant + * @param currentRootNetworkUuid the root network to work on to get the variant + * @param equipmentTypes The types of equipment to filter + * @param filters the filters description + * @return The equipment IDs that pass the filters + */ +export async function evaluateGlobalFilter( + studyUuid: UUID, + currentNodeUuid: UUID, + currentRootNetworkUuid: UUID, + equipmentTypes: NonEmptyTuple, + filters: GlobalFilters +): Promise { + return backendFetchJson( + `${getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, currentNodeUuid, currentRootNetworkUuid)}/global-filter/evaluate?${new URLSearchParams( + { equipmentTypes: equipmentTypes.join(',') } + )}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(filters), + } + ); +} + +/** @deprecated migrate to {@link #evaluateGlobalFilter} */ export async function evaluateJsonFilter( studyUuid: UUID, currentNodeUuid: UUID, @@ -59,6 +91,7 @@ export async function evaluateJsonFilter( }); } +/** @deprecated migrate to {@link #evaluateGlobalFilter} */ export async function evaluateFilters( studyUuid: UUID, currentNodeUuid: UUID, diff --git a/src/types/filter-lib/filter.ts b/src/types/filter-lib/filter.ts new file mode 100644 index 0000000000..b605e301f5 --- /dev/null +++ b/src/types/filter-lib/filter.ts @@ -0,0 +1,26 @@ +/* + * Copyright © 2025, 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 enum FilterEquipmentType { + BATTERY = 'BATTERY', + BRANCH = 'BRANCH', + BUS = 'BUS', + BUSBAR_SECTION = 'BUSBAR_SECTION', + DANGLING_LINE = 'DANGLING_LINE', + GENERATOR = 'GENERATOR', + HVDC_LINE = 'HVDC_LINE', + LCC_CONVERTER_STATION = 'LCC_CONVERTER_STATION', + LINE = 'LINE', + LOAD = 'LOAD', + SHUNT_COMPENSATOR = 'SHUNT_COMPENSATOR', + STATIC_VAR_COMPENSATOR = 'STATIC_VAR_COMPENSATOR', + SUBSTATION = 'SUBSTATION', + THREE_WINDINGS_TRANSFORMER = 'THREE_WINDINGS_TRANSFORMER', + TWO_WINDINGS_TRANSFORMER = 'TWO_WINDINGS_TRANSFORMER', + VOLTAGE_LEVEL = 'VOLTAGE_LEVEL', + VSC_CONVERTER_STATION = 'VSC_CONVERTER_STATION', +}