Skip to content

Commit af23c4d

Browse files
authored
Merge branch 'main' into contingencyListByFilter
2 parents a5b7d63 + f8e9cef commit af23c4d

32 files changed

+310
-164
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gridsuite/commons-ui",
3-
"version": "0.116.4",
3+
"version": "0.120.0",
44
"description": "common react components for gridsuite applications",
55
"author": "gridsuite team",
66
"homepage": "https://github.com/gridsuite",

src/components/contingencyList/criteriaBased/CriteriaBasedForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function CriteriaBasedForm({ equipments, defaultValues, children }: Reado
5959
resetOnConfirmation={handleResetOnConfirmation}
6060
message="changeTypeMessage"
6161
validateButtonLabel="button.changeType"
62+
data-testid="EquipmentTypeSelector"
6263
/>
6364
</Box>
6465
<Box sx={unscrollableDialogStyles.scrollableContent}>

src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { SubmitButton } from '../../inputs/reactHookForm/utils/SubmitButton';
1414
import { CancelButton } from '../../inputs/reactHookForm/utils/CancelButton';
1515
import { CustomFormProvider } from '../../inputs/reactHookForm/provider/CustomFormProvider';
1616
import { PopupConfirmationDialog } from '../popupConfirmationDialog/PopupConfirmationDialog';
17+
import { GsLang } from '../../../utils';
1718

1819
export type CustomMuiDialogProps<T extends FieldValues = FieldValues> = DialogProps & {
1920
open: boolean;
@@ -28,7 +29,7 @@ export type CustomMuiDialogProps<T extends FieldValues = FieldValues> = DialogPr
2829
onCancel?: () => void;
2930
children: ReactNode;
3031
isDataFetching?: boolean;
31-
language?: string;
32+
language?: GsLang;
3233
confirmationMessageKey?: string;
3334
unscrollableFullHeight?: boolean;
3435
};

src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ export function DescriptionModificationDialog({
8989
label="descriptionProperty"
9090
minRows={3}
9191
rows={3}
92-
data-testid="DescriptionInputField"
9392
/>
9493
</Box>
9594
</CustomMuiDialog>

src/components/directoryItemSelector/DirectoryItemSelector.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ export function DirectoryItemSelector({
262262
}, [convertRoots, types, snackError]);
263263

264264
const fetchDirectoryChildren = useCallback(
265-
(nodeId: UUID): void => {
265+
(nodeId: UUID): Promise<void> => {
266266
const typeList = types.includes(ElementType.DIRECTORY) ? [] : types;
267-
fetchDirectoryContent(nodeId, typeList)
267+
return fetchDirectoryContent(nodeId, typeList)
268268
.then((children) => {
269269
const childrenMatchedTypes = children.filter((item: ElementAttributes) =>
270270
contentFilter().has(item.type)
@@ -279,7 +279,7 @@ export function DirectoryItemSelector({
279279
types,
280280
equipmentTypes
281281
).then((childrenWithMetadata: ElementAttributes[]) => {
282-
const filtredChildren = itemFilter
282+
const filteredChildren = itemFilter
283283
? childrenWithMetadata.filter((val: ElementAttributes) => {
284284
// Accept every directory
285285
if (val.type === ElementType.DIRECTORY) {
@@ -290,12 +290,12 @@ export function DirectoryItemSelector({
290290
})
291291
: childrenWithMetadata;
292292
// update directory content
293-
addToDirectory(nodeId, filtredChildren);
293+
addToDirectory(nodeId, filteredChildren);
294294
});
295-
} else {
296-
// update directory content
297-
addToDirectory(nodeId, childrenMatchedTypes);
298295
}
296+
// update directory content
297+
addToDirectory(nodeId, childrenMatchedTypes);
298+
return Promise.resolve();
299299
})
300300
.catch((error) => {
301301
console.warn(`Could not update subs (and content) of '${nodeId}' : ${error.message}`);
@@ -306,13 +306,11 @@ export function DirectoryItemSelector({
306306

307307
// Helper function to fetch children for a node if not already loaded
308308
const fetchNodeChildrenIfNeeded = useCallback(
309-
(nodeId: UUID, delay: number = 0) => {
310-
setTimeout(() => {
311-
const node = nodeMap.current[nodeId];
312-
if (node && (!node.children || node.children.length === 0) && node.type === ElementType.DIRECTORY) {
313-
fetchDirectoryChildren(nodeId);
314-
}
315-
}, delay);
309+
async (nodeId: UUID): Promise<void> => {
310+
const node = nodeMap.current[nodeId];
311+
if (node && (!node.children || node.children.length === 0) && node.type === ElementType.DIRECTORY) {
312+
await fetchDirectoryChildren(nodeId);
313+
}
316314
},
317315
[fetchDirectoryChildren]
318316
);
@@ -325,18 +323,18 @@ export function DirectoryItemSelector({
325323

326324
const expandedArray = await getExpansionPathsForSelected(selected, expanded);
327325
setAutoExpandedNodes(expandedArray);
328-
fetchChildrenForExpandedNodes(expandedArray, fetchNodeChildrenIfNeeded);
326+
await fetchChildrenForExpandedNodes(expandedArray, fetchNodeChildrenIfNeeded);
329327
return true;
330328
}, [selected, expanded, fetchNodeChildrenIfNeeded]);
331329

332330
// Handle expansion from provided expanded prop
333-
const handleProvidedExpansion = useCallback((): boolean => {
331+
const handleProvidedExpansion = useCallback(async (): Promise<boolean> => {
334332
if (!expanded || expanded.length === 0) {
335333
return false;
336334
}
337335

338336
setAutoExpandedNodes(expanded);
339-
fetchChildrenForExpandedNodes(expanded, fetchNodeChildrenIfNeeded);
337+
await fetchChildrenForExpandedNodes(expanded, fetchNodeChildrenIfNeeded);
340338

341339
return true;
342340
}, [expanded, fetchNodeChildrenIfNeeded]);
@@ -350,7 +348,7 @@ export function DirectoryItemSelector({
350348
}
351349

352350
setAutoExpandedNodes(expandPath);
353-
fetchChildrenForExpandedNodes(expandPath, fetchNodeChildrenIfNeeded);
351+
await fetchChildrenForExpandedNodes(expandPath, fetchNodeChildrenIfNeeded);
354352

355353
return true;
356354
}, [fetchNodeChildrenIfNeeded]);
@@ -359,11 +357,15 @@ export function DirectoryItemSelector({
359357
const initializeExpansion = useCallback(async () => {
360358
// Priority 1: Handle selected items
361359
const selectedSuccess = await handleSelectedExpansion();
362-
if (selectedSuccess) return;
360+
if (selectedSuccess) {
361+
return;
362+
}
363363

364364
// Priority 2: Handle provided expanded items
365-
const expandedSuccess = handleProvidedExpansion();
366-
if (expandedSuccess) return;
365+
const expandedSuccess = await handleProvidedExpansion();
366+
if (expandedSuccess) {
367+
return;
368+
}
367369

368370
// Priority 3: Fall back to last selected directory
369371
await handleLastSelectedExpansion();

src/components/directoryItemSelector/utils.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { UUID } from 'crypto';
9-
import { ElementAttributes, ElementType, LAST_SELECTED_DIRECTORY } from '../../utils';
9+
import { COMMON_APP_NAME, ElementAttributes, ElementType, LAST_SELECTED_DIRECTORY } from '../../utils';
1010
import { fetchDirectoryElementPath, updateConfigParameter } from '../../services';
1111

1212
/**
@@ -30,7 +30,7 @@ export async function clearLastSelectedDirectory(): Promise<void> {
3030
localStorage.removeItem(LAST_SELECTED_DIRECTORY);
3131

3232
try {
33-
await updateConfigParameter(LAST_SELECTED_DIRECTORY, 'null');
33+
await updateConfigParameter(COMMON_APP_NAME, LAST_SELECTED_DIRECTORY, 'null');
3434
} catch (error) {
3535
console.error('Failed to clear last selected directory:', error);
3636
}
@@ -42,7 +42,7 @@ export async function clearLastSelectedDirectory(): Promise<void> {
4242
*/
4343
export async function saveLastSelectedDirectory(directoryId: UUID): Promise<void> {
4444
try {
45-
await updateConfigParameter(LAST_SELECTED_DIRECTORY, directoryId);
45+
await updateConfigParameter(COMMON_APP_NAME, LAST_SELECTED_DIRECTORY, directoryId);
4646
} catch (error) {
4747
console.error('Failed to save last selected directory:', error);
4848
}
@@ -92,29 +92,40 @@ export async function initializeFromLastSelected(): Promise<UUID[] | null> {
9292
}
9393

9494
/**
95-
* Fetches expansion paths for multiple selected items
95+
* Fetches expansion paths for multiple selected items, collecting unique parent directories
96+
* (excluding the selected items themselves) and sorting them by their minimum depth across
97+
* all paths. This ensures parents appear before their descendants in the returned array,
98+
* which is crucial for sequential fetching to avoid loading children before parents are
99+
* fully populated in the node map.
96100
* @param selectedIds Array of selected item UUIDs
97101
* @param expanded Optional existing expanded nodes
98-
* @returns Promise resolving to combined expansion array
102+
* @returns Promise resolving to combined expansion array sorted by depth
99103
*/
100104
export async function getExpansionPathsForSelected(selectedIds: UUID[], expanded: UUID[] = []): Promise<UUID[]> {
101105
const expandedSet = new Set<UUID>(expanded);
106+
const idToMinIndex = new Map<UUID, number>();
102107

103-
const fetchPromises = selectedIds.map(async (selectedId) => {
104-
const path = await fetchDirectoryPathSafe(selectedId);
108+
const paths = await Promise.all(selectedIds.map(fetchDirectoryPathSafe));
105109

106-
if (path && path.length > 0) {
107-
// Add all parent directories to the expanded set (exclude the item itself)
110+
paths
111+
.filter((p): p is ElementAttributes[] => !!p && p.length > 0)
112+
.forEach((path) => {
108113
path.forEach((element, index) => {
109114
if (index < path.length - 1) {
110-
expandedSet.add(element.elementUuid);
115+
const id = element.elementUuid;
116+
expandedSet.add(id);
117+
if (!idToMinIndex.has(id) || index < idToMinIndex.get(id)!) {
118+
idToMinIndex.set(id, index);
119+
}
111120
}
112121
});
113-
}
114-
});
122+
});
123+
124+
const expandedArray = Array.from(expandedSet).sort(
125+
(a, b) => (idToMinIndex.get(a) ?? Infinity) - (idToMinIndex.get(b) ?? Infinity)
126+
);
115127

116-
await Promise.all(fetchPromises);
117-
return Array.from(expandedSet);
128+
return expandedArray;
118129
}
119130

120131
/**
@@ -142,17 +153,16 @@ export async function saveLastSelectedDirectoryFromNode(node: {
142153
}
143154

144155
/**
145-
* Fetches children for expanded nodes with staggered delay to avoid overwhelming the server
156+
* Fetches children for expanded nodes sequentially to ensure parent nodes are loaded before children
146157
* @param expandedNodes Array of node UUIDs to fetch children for
147158
* @param fetchChildrenCallback Function to fetch children for a single node
148-
* @param delayBetweenRequests Delay in milliseconds between requests (default: 100ms)
149159
*/
150-
export function fetchChildrenForExpandedNodes(
160+
export async function fetchChildrenForExpandedNodes(
151161
expandedNodes: UUID[],
152-
fetchChildrenCallback: (nodeId: UUID, delay: number) => void,
153-
delayBetweenRequests: number = 100
154-
): void {
155-
expandedNodes.forEach((nodeId, index) => {
156-
fetchChildrenCallback(nodeId, index * delayBetweenRequests);
157-
});
162+
fetchChildrenCallback: (nodeId: UUID) => Promise<void>
163+
): Promise<void> {
164+
await expandedNodes.reduce(async (promise, nodeId) => {
165+
await promise;
166+
return fetchChildrenCallback(nodeId);
167+
}, Promise.resolve());
158168
}

src/components/dnd-table/dnd-table.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ interface DndTableProps {
200200
uploadButtonMessageId?: string;
201201
handleResetButton?: () => void;
202202
resetButtonMessageId?: string;
203+
maxRows?: number;
203204
}
204205

205206
export function DndTable(props: Readonly<DndTableProps>) {
@@ -224,6 +225,7 @@ export function DndTable(props: Readonly<DndTableProps>) {
224225
uploadButtonMessageId = undefined,
225226
handleResetButton = undefined,
226227
resetButtonMessageId = undefined,
228+
maxRows = MAX_ROWS_NUMBER,
227229
} = props;
228230
const intl = useIntl();
229231

@@ -259,16 +261,16 @@ export function DndTable(props: Readonly<DndTableProps>) {
259261
}
260262

261263
const addNewRows = (numberOfRows: number) => {
262-
// checking if not exceeding 100 steps
263-
if (currentRows.length + numberOfRows > MAX_ROWS_NUMBER) {
264+
// checking if not exceeding the max allowed
265+
if (currentRows.length + numberOfRows > maxRows) {
264266
setError(arrayFormName, {
265267
type: 'custom',
266268
message: intl.formatMessage(
267269
{
268270
id: 'MaximumRowNumberError',
269271
},
270272
{
271-
value: MAX_ROWS_NUMBER,
273+
value: maxRows,
272274
}
273275
),
274276
});

src/components/filter/FilterCreationDialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { FilterType } from './constants/FilterConstants';
2424
import { MAX_CHAR_DESCRIPTION } from '../../utils/constants/uiConstants';
2525
import { EXPERT_FILTER_QUERY } from './expert/expertFilterConstants';
2626
import { FILTER_EQUIPMENTS_ATTRIBUTES } from './explicitNaming/ExplicitNamingFilterConstants';
27+
import { GsLang } from '../../utils';
2728

2829
const emptyFormData = {
2930
[FieldConstants.NAME]: '',
@@ -49,7 +50,7 @@ export interface FilterCreationDialogProps {
4950
open: boolean;
5051
onClose: () => void;
5152
activeDirectory?: UUID;
52-
language?: string;
53+
language?: GsLang;
5354
sourceFilterForExplicitNamingConversion?: {
5455
id: UUID;
5556
equipmentType: string;

src/components/filter/expert/ExpertFilterForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export function ExpertFilterForm() {
136136
message="changeTypeMessage"
137137
validateButtonLabel="button.changeType"
138138
sx={filterStyles.textField}
139+
data-testid="EquipmentTypeSelector"
139140
/>
140141
</Box>
141142
<Box sx={unscrollableDialogStyles.scrollableContent}>

0 commit comments

Comments
 (0)