Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/app-wrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ const lightTheme = createTheme({
},
highlightColor: '#1976D214',
},
agGridBackground: {
color: 'white',
},
networkModificationPanel: {
backgroundColor: 'white',
border: 'solid 1px #babfc7',
Expand Down Expand Up @@ -335,6 +338,9 @@ const darkTheme = createTheme({
},
highlightColor: '#90CAF929',
},
agGridBackground: {
color: '#383838',
},
networkModificationPanel: {
backgroundColor: '#252525',
border: 'solid 1px #68686e',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
*/

import { yupResolver } from '@hookform/resolvers/yup';
import { CustomFormProvider, ModificationType, snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui';
import {
CustomFormProvider,
FieldConstants,
ModificationType,
snackWithFallback,
useSnackMessage,
} from '@gridsuite/commons-ui';
import { v4 as uuid4 } from 'uuid';
import { useForm } from 'react-hook-form';
import { useCallback, useEffect, useMemo } from 'react';
import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form.js';
import { FORM_LOADING_DELAY } from 'components/network/constants.js';
import { TABULAR_PROPERTIES, MODIFICATIONS_TABLE, CSV_FILENAME, TYPE } from 'components/utils/field-constants.js';
import { CSV_FILENAME, MODIFICATIONS_TABLE, TABULAR_PROPERTIES, TYPE } from 'components/utils/field-constants.js';
import { ModificationDialog } from 'components/dialogs/commons/modificationDialog.js';
import { createTabularModification } from 'services/study/network-modifications.js';
import { FetchStatus } from 'services/utils.type';
Expand Down Expand Up @@ -111,6 +118,7 @@ export function TabularDialog({
}
}
modification = addPropertiesFromBack(modification, modif?.[TABULAR_PROPERTIES]);
modification[FieldConstants.AG_GRID_ROW_UUID] = uuid4();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'AG_GRID_ROW_UUID|Object\.keys\(row\)|convertCreationFieldFromFrontToBack|transformModificationsTable' \
  src/components/dialogs/network-modifications/tabular

Repository: gridsuite/gridstudy-app

Length of output: 10301


🏁 Script executed:

cat -n src/components/dialogs/network-modifications/tabular/tabular-creation-utils.ts | sed -n '188,210p'

Repository: gridsuite/gridstudy-app

Length of output: 583


🏁 Script executed:

cat -n src/components/dialogs/network-modifications/tabular/tabular-modification-utils.ts | sed -n '486,510p'

Repository: gridsuite/gridstudy-app

Length of output: 463


🏁 Script executed:

rg -n -A30 'transformRowToBackEndModification' src/components/dialogs/network-modifications/tabular/tabular-modification-utils.ts | head -60

Repository: gridsuite/gridstudy-app

Length of output: 1455


🏁 Script executed:

rg -n 'AG_GRID_ROW_UUID' src/components/dialogs/network-modifications/tabular/tabular-modification-utils.ts

Repository: gridsuite/gridstudy-app

Length of output: 49


🏁 Script executed:

rg -n 'AG_GRID_ROW_UUID' src/components/dialogs/network-modifications/tabular/tabular-creation-utils.ts

Repository: gridsuite/gridstudy-app

Length of output: 49


Remove AG_GRID_ROW_UUID before submitting to backend.

Lines 121 and 139–140 add FieldConstants.AG_GRID_ROW_UUID to row objects for UI state tracking. However, this field propagates to the API payload:

  • Creation submissions (lines 195–197) iterate all row keys without filtering, passing each through convertCreationFieldFromFrontToBack, which has no guard for the UUID field.
  • Modification submissions (line 166) delegate to transformation strategies that iterate all keys (line 444) without filtering.

Add an explicit check in convertCreationFieldFromFrontToBack or in the modification transformation strategy to exclude FieldConstants.AG_GRID_ROW_UUID before the backend payload is assembled.

Also applies to: 137–140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dialogs/network-modifications/tabular/tabular-dialog.tsx` at
line 121, The UI-only FieldConstants.AG_GRID_ROW_UUID is being added to row
objects for grid state but is leaking into API payloads; update the submission
path to strip this key before backend conversion by adding an explicit guard
that ignores FieldConstants.AG_GRID_ROW_UUID: either add a top-level filter in
convertCreationFieldFromFrontToBack to return undefined/skip when the field key
=== FieldConstants.AG_GRID_ROW_UUID, and/or add a pre-iteration filter in the
modification transformation strategy (the function that iterates row keys to
build modification payloads) so it never processes that key; ensure both
creation and modification flows (the code that calls
convertCreationFieldFromFrontToBack and the modification transformer) exclude
AG_GRID_ROW_UUID before assembling the final payload.

return modification;
});
reset({
Expand All @@ -126,7 +134,10 @@ export function TabularDialog({
const initTabularCreationData = useCallback(
(editData: TabularModificationEditDataType) => {
const equipmentType = getEquipmentTypeFromCreationType(editData?.modificationType);
const creations = convertCreations(editData?.modifications);
const creations = convertCreations(editData?.modifications).map((creation) => ({
...creation,
[FieldConstants.AG_GRID_ROW_UUID]: uuid4(),
}));
reset({
[TYPE]: equipmentType,
[MODIFICATIONS_TABLE]: creations,
Expand Down Expand Up @@ -241,6 +252,7 @@ export function TabularDialog({
titleId={dialogMode === TabularModificationType.CREATION ? 'TabularCreation' : 'TabularModification'}
open={open}
isDataFetching={dataFetching}
slotProps={{ paper: { sx: { height: '83vh' } } }}
{...dialogProps}
>
<TabularForm dataFetching={dataFetching} dialogMode={dialogMode} />
Expand Down
104 changes: 68 additions & 36 deletions src/components/dialogs/network-modifications/tabular/tabular-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,27 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useFormContext, useWatch } from 'react-hook-form';
import { type FieldValues, type UseFieldArrayReturn, useFormContext, useWatch } from 'react-hook-form';
import {
AutocompleteInput,
BooleanNullableCellRenderer,
CustomAGGrid,
CustomAgGridTable,
DefaultCellRenderer,
DirectoryItemSelector,
ElementType,
EquipmentType,
ErrorInput,
fetchStudyMetadata,
FieldErrorAlert,
FieldConstants,
getObjectId,
LANG_FRENCH,
type MuiStyles,
NumericEditor,
type TreeViewFinderNodeProps,
useSnackMessage,
useStateBoolean,
} from '@gridsuite/commons-ui';
import { v4 as uuid4 } from 'uuid';
import {
CSV_FILENAME,
EQUIPMENT_ID,
Expand Down Expand Up @@ -53,7 +53,7 @@
transformIfFrenchNumber,
} from './tabular-common';
import { ColDef } from 'ag-grid-community';
import { BOOLEAN } from '../../../network/constants';
import { ENUM, NUMBER } from '../../../network/constants';
import { TABULAR_CREATION_FIELDS } from './tabular-creation-utils';
import { TABULAR_MODIFICATION_FIELDS } from './tabular-modification-utils';
import { useFilterCsvGenerator } from './use-filter-csv-generator';
Expand All @@ -75,6 +75,7 @@
const { snackWarning } = useSnackMessage();
const [isFetching, setIsFetching] = useState<boolean>(dataFetching);
const { setValue, clearErrors, setError } = useFormContext();
const tableRef = useRef<UseFieldArrayReturn<FieldValues, string>>(null);
const propertiesDialogOpen = useStateBoolean(false);
const generateFromFilterOpen = useStateBoolean(false);
const prefilledModelDialogOpen = useStateBoolean(false);
Expand All @@ -91,9 +92,6 @@
const tabularProperties = useWatch({
name: TABULAR_PROPERTIES,
});
const watchTable = useWatch({
name: MODIFICATIONS_TABLE,
});
const watchFileName = useWatch({
name: CSV_FILENAME,
});
Expand Down Expand Up @@ -301,11 +299,15 @@
} else {
handleTabularModificationParsingError(results);
}
setValue(MODIFICATIONS_TABLE, results.data, { shouldDirty: true });
const rowsWithUuid = results.data.map((row) => ({
...row,
[FieldConstants.AG_GRID_ROW_UUID]: uuid4(),
}));
tableRef.current?.replace(rowsWithUuid);
Comment on lines +302 to +306
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Inline edits/additions bypass the only real validation path.

The grid is editable and can create new rows now, but the required/type checks still only run during CSV parsing. That means users can add blank rows or change a cell to an invalid value and still save because errors never refresh; conversely, fixing a CSV error inline can leave the form blocked. Please re-run row validation on grid mutations or validate the current table again in onSave.

Also applies to: 411-440, 444-453, 545-559

setValue(CSV_FILENAME, selectedFile?.name);
} else {
// If the file is undefined we don't update the values because it's outdated
setValue(MODIFICATIONS_TABLE, []);
tableRef.current?.replace([]);
setValue(CSV_FILENAME, undefined);
}
setIsFetching(false);
Expand Down Expand Up @@ -336,7 +338,7 @@

useEffect(() => {
if (selectedFileError) {
setValue(MODIFICATIONS_TABLE, []);
tableRef.current?.replace([]);
setValue(CSV_FILENAME, undefined);
clearErrors(MODIFICATIONS_TABLE);
setIsFetching(false);
Expand Down Expand Up @@ -373,7 +375,7 @@
const handleTypeChange = useCallback(() => {
setTypeChangedTrigger(!typeChangedTrigger);
clearErrors(MODIFICATIONS_TABLE);
setValue(MODIFICATIONS_TABLE, []);
tableRef.current?.replace([]);
setValue(CSV_FILENAME, undefined);
setValue(TABULAR_PROPERTIES, []);
resetFile();
Expand Down Expand Up @@ -406,26 +408,50 @@
const columnDefs = useMemo(() => {
return csvFields
.map((field) => {
const columnDef: ColDef = {};
const columnDef: ColDef = {
field: field.id,
headerName: intl.formatMessage({ id: field.id }) + (field.required ? ' (*)' : ''),
editable: true,
singleClickEdit: true,
};
if (field.id === EQUIPMENT_ID) {
columnDef.pinned = true;
columnDef.rowDrag = true;
}
switch (field.type) {
case NUMBER:
columnDef.cellEditor = NumericEditor;
break;
case ENUM:
columnDef.cellEditor = 'agSelectCellEditor';
columnDef.cellEditorParams = { values: [null, ...(field.options ?? [])] };
break;
default:
break;
}
columnDef.field = field.id;
columnDef.headerName = intl.formatMessage({ id: field.id }) + (field.required ? ' (*)' : '');
columnDef.cellRenderer = field.type === BOOLEAN ? BooleanNullableCellRenderer : DefaultCellRenderer;
return columnDef;
})
.concat(
selectedProperties.map((propertyName: string) => {
const columnDef: ColDef = {};
columnDef.field = PROPERTY_CSV_COLUMN_PREFIX + propertyName;
columnDef.headerName = propertyName;
columnDef.cellRenderer = DefaultCellRenderer;
return columnDef;
})
selectedProperties.map((propertyName: string) => ({
field: PROPERTY_CSV_COLUMN_PREFIX + propertyName,
headerName: propertyName,
editable: true,
singleClickEdit: true,
}))
);
}, [csvFields, selectedProperties, intl]);

const makeDefaultRowData = useCallback(() => {
const row: Record<string, any> = { [FieldConstants.AG_GRID_ROW_UUID]: uuid4() };
csvFields.forEach((field) => {
row[field.id] = null;
});
selectedProperties.forEach((propertyName) => {
row[PROPERTY_CSV_COLUMN_PREFIX + propertyName] = '';
});
return row;
}, [csvFields, selectedProperties]);

const onPropertiesChange = (formData: PropertiesFormType) => {
const newSelectedProperties =
formData[TABULAR_PROPERTIES]?.filter((property: TabularProperty) => property.selected)?.map(
Expand All @@ -434,7 +460,7 @@
if (newSelectedProperties.toString() !== selectedProperties.toString()) {
// new columns => reset table
clearErrors(MODIFICATIONS_TABLE);
setValue(MODIFICATIONS_TABLE, []);
tableRef.current?.replace([]);
setValue(CSV_FILENAME, undefined);
}
setValue(TABULAR_PROPERTIES, formData[TABULAR_PROPERTIES], { shouldDirty: true });
Expand Down Expand Up @@ -509,21 +535,27 @@
</Button>
</Grid>
)}
<Grid item>
<ErrorInput name={MODIFICATIONS_TABLE} InputField={FieldErrorAlert} />
{selectedFileError && <Alert severity="error">{selectedFileError}</Alert>}
</Grid>
{selectedFileError && (
<Grid item>
<Alert severity="error">{selectedFileError}</Alert>
</Grid>
)}
</Grid>
<Grid item xs={12} sx={dialogStyles.grid}>
<CustomAGGrid
rowData={watchTable}
loading={isFetching}
defaultColDef={defaultColDef}
<CustomAgGridTable
ref={tableRef}

Check failure on line 546 in src/components/dialogs/network-modifications/tabular/tabular-form.tsx

View workflow job for this annotation

GitHub Actions / build / build

Type '{ ref: RefObject<UseFieldArrayReturn<FieldValues, string> | null>; name: string; columnDefs: ColDef<any, any>[]; defaultColDef: { ...; }; ... 6 more ...; cssProps: { ...; }; }' is not assignable to type 'IntrinsicAttributes & Readonly<CustomAgGridTableProps>'.
name={MODIFICATIONS_TABLE}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
makeDefaultRowData={makeDefaultRowData}
loading={isFetching}
pagination
paginationPageSize={100}
suppressDragLeaveHidesColumns
rowSelection={{
mode: 'multiRow',
}}
overrideLocales={AGGRID_LOCALES}
csvProps={undefined}
cssProps={{ height: 535 }}
/>
</Grid>
<DefinePropertiesDialog
Expand Down
3 changes: 3 additions & 0 deletions src/module-mui.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ declare module '@mui/material/styles' {
};
highlightColor: string;
};
agGridBackground: {
color: string;
};
networkModificationPanel: {
backgroundColor: string;
border: string;
Expand Down
Loading