Skip to content

Commit e7ad108

Browse files
committed
fix(FR-2475): add fetchKey prop to ProjectSelect for data refetch on modal open
1 parent 9f3cb82 commit e7ad108

4 files changed

Lines changed: 82 additions & 1 deletion

File tree

e2e/utils/test-util.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,70 @@ function mergeWithUndefined<T extends object>(target: T, source: object): T {
5050
return result;
5151
}
5252

53+
/**
54+
* Remove duplicate keys within the same TOML section.
55+
* Keeps the last occurrence of each key per section.
56+
*/
57+
function deduplicateTomlKeys(toml: string): string {
58+
const lines = toml.split('\n');
59+
const result: string[] = [];
60+
const seenKeys = new Set<string>();
61+
let currentSection = '';
62+
63+
// First pass: track last occurrence of each key per section
64+
const lastOccurrence = new Map<string, number>();
65+
for (let i = 0; i < lines.length; i++) {
66+
const line = lines[i].trim();
67+
if (line.startsWith('[')) {
68+
currentSection = line;
69+
seenKeys.clear();
70+
} else if (line && !line.startsWith('#')) {
71+
const eqIndex = line.indexOf('=');
72+
if (eqIndex > 0) {
73+
const key = `${currentSection}::${line.substring(0, eqIndex).trim()}`;
74+
lastOccurrence.set(key, i);
75+
}
76+
}
77+
}
78+
79+
// Second pass: only keep the last occurrence of duplicate keys
80+
currentSection = '';
81+
const keyLineIndices = new Map<string, number[]>();
82+
for (let i = 0; i < lines.length; i++) {
83+
const line = lines[i].trim();
84+
if (line.startsWith('[')) {
85+
currentSection = line;
86+
} else if (line && !line.startsWith('#')) {
87+
const eqIndex = line.indexOf('=');
88+
if (eqIndex > 0) {
89+
const key = `${currentSection}::${line.substring(0, eqIndex).trim()}`;
90+
if (!keyLineIndices.has(key)) {
91+
keyLineIndices.set(key, []);
92+
}
93+
keyLineIndices.get(key)!.push(i);
94+
}
95+
}
96+
}
97+
98+
const skipLines = new Set<number>();
99+
for (const [, indices] of keyLineIndices) {
100+
if (indices.length > 1) {
101+
// Skip all but the last occurrence
102+
for (let j = 0; j < indices.length - 1; j++) {
103+
skipLines.add(indices[j]);
104+
}
105+
}
106+
}
107+
108+
for (let i = 0; i < lines.length; i++) {
109+
if (!skipLines.has(i)) {
110+
result.push(lines[i]);
111+
}
112+
}
113+
114+
return result.join('\n');
115+
}
116+
53117
// Theme configuration types based on theme.schema.json
54118
type ThemeLogoConfig = {
55119
src?: string;
@@ -751,7 +815,11 @@ export async function modifyConfigToml(
751815
if (!res.ok) throw new Error(`HTTP ${res.status}`);
752816
return res.text();
753817
});
754-
config = TOML.parse(configToml);
818+
// Pre-process TOML to remove duplicate keys before parsing.
819+
// Some server configurations may have duplicate keys (e.g., debug = true
820+
// appearing twice under [general]) which strict TOML parsers reject.
821+
const deduplicatedToml = deduplicateTomlKeys(configToml);
822+
config = TOML.parse(deduplicatedToml);
755823
break; // Success, exit retry loop
756824
} catch (error) {
757825
lastError = error;

react/src/components/ProjectSelect.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@ export interface ProjectSelectProps extends BAISelectProps {
2525
autoSelectDefault?: boolean;
2626
disableDefaultFilter?: boolean;
2727
lockedProjectTypes?: string[];
28+
fetchKey?: string;
2829
}
2930

3031
const ProjectSelect: React.FC<ProjectSelectProps> = ({
3132
onSelectProject,
3233
domain,
3334
disableDefaultFilter,
3435
lockedProjectTypes,
36+
fetchKey,
3537
...selectProps
3638
}) => {
3739
const { t } = useTranslation();
@@ -74,6 +76,7 @@ const ProjectSelect: React.FC<ProjectSelectProps> = ({
7476
},
7577
{
7678
fetchPolicy: 'store-and-network',
79+
fetchKey: fetchKey,
7780
},
7881
);
7982

react/src/components/UpdateUsersModal.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
useBAILogger,
1717
useErrorMessageResolver,
1818
useMutationWithPromise,
19+
useUpdatableState,
1920
} from 'backend.ai-ui';
2021
import _ from 'lodash';
2122
import { Suspense, useRef, useState } from 'react';
@@ -50,6 +51,7 @@ const UpdateUsersModal = ({
5051
'use memo';
5152
const formRef = useRef<FormInstance<UpdateUsersFormValues>>(null);
5253
const [isPending, setIsPending] = useState(false);
54+
const [fetchKey, updateFetchKey] = useUpdatableState('initial-fetch');
5355
const { token } = theme.useToken();
5456
const { t } = useTranslation();
5557
const { message } = App.useApp();
@@ -88,6 +90,12 @@ const UpdateUsersModal = ({
8890
okText={t('button.Update')}
8991
confirmLoading={isPending}
9092
{...modalProps}
93+
afterOpenChange={(open) => {
94+
if (open) {
95+
updateFetchKey();
96+
}
97+
modalProps.afterOpenChange?.(open);
98+
}}
9199
onOk={(e) => {
92100
formRef.current
93101
?.validateFields()
@@ -232,6 +240,7 @@ const UpdateUsersModal = ({
232240
domain={getFieldValue('domain_name')}
233241
disableDefaultFilter
234242
disabled={!getFieldValue('domain_name')}
243+
fetchKey={fetchKey}
235244
/>
236245
</Form.Item>
237246
)}

react/src/components/UserSettingModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ const UserSettingModal: React.FC<UserSettingModalProps> = ({
874874
domain={getFieldValue('domain_name')}
875875
disableDefaultFilter
876876
lockedProjectTypes={!user ? ['MODEL_STORE'] : undefined}
877+
fetchKey={fetchKey}
877878
/>
878879
</Form.Item>
879880
)}

0 commit comments

Comments
 (0)