Skip to content

Commit cccdf2c

Browse files
committed
feat: add dynamic algorithm params form and clustering explorer validation
1 parent 48f5770 commit cccdf2c

12 files changed

Lines changed: 285 additions & 15 deletions

File tree

DashAI/front/src/components/configurableObject/Inputs/SelectInput.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function SelectInput({
2222
description,
2323
options,
2424
optionNames = undefined,
25+
optionDescriptions = undefined,
2526
}) {
2627
const handleChange = (event) => {
2728
const inputValue = event.target.value;
@@ -58,12 +59,27 @@ function SelectInput({
5859
display: "block",
5960
},
6061
},
62+
secondary: {
63+
sx: {
64+
overflow: "hidden",
65+
textOverflow: "ellipsis",
66+
whiteSpace: "normal",
67+
maxWidth: "100%",
68+
display: "block",
69+
lineHeight: 1.35,
70+
},
71+
},
6172
}}
6273
primary={
6374
optionNames !== undefined && index < options.length
6475
? optionNames[index]
6576
: option
6677
}
78+
secondary={
79+
optionDescriptions !== undefined && index < options.length
80+
? optionDescriptions[index]
81+
: undefined
82+
}
6783
/>
6884
</MenuItem>
6985
))}
@@ -80,6 +96,7 @@ SelectInput.propTypes = {
8096
error: PropTypes.string,
8197
options: PropTypes.arrayOf(PropTypes.string).isRequired,
8298
optionNames: PropTypes.arrayOf(PropTypes.string),
99+
optionDescriptions: PropTypes.arrayOf(PropTypes.string),
83100
};
84101

85102
export default SelectInput;

DashAI/front/src/components/notebooks/ColumnSelector.jsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function ColumnSelector({
4040
allowedDtypes = [],
4141
allowedTypes = [],
4242
typesDtypeRestrictions = {},
43+
allowedColumnNames = null,
4344
onSelectionChange = () => {},
4445
onValidationChange = () => {},
4546
columnTypes = null,
@@ -156,6 +157,12 @@ function ColumnSelector({
156157
if (inputCardinality.exact === 0) return [];
157158
return rows
158159
.filter((row) => {
160+
if (
161+
allowedColumnNames !== null &&
162+
!allowedColumnNames.has(row.columnName)
163+
) {
164+
return false;
165+
}
159166
if (allowedTypes.length > 0 && !allowedTypes.includes(row.valueType)) {
160167
return false;
161168
}
@@ -171,7 +178,13 @@ function ColumnSelector({
171178
return true;
172179
})
173180
.map((row) => row.id);
174-
}, [rows, allowedDtypes, allowedTypes, typesDtypeRestrictions]);
181+
}, [
182+
rows,
183+
allowedDtypes,
184+
allowedTypes,
185+
typesDtypeRestrictions,
186+
allowedColumnNames,
187+
]);
175188

176189
// Check if row is selectable - using useCallback for stability
177190
const isRowSelectable = useCallback(

DashAI/front/src/components/notebooks/RightBar.jsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,31 @@ export default function RightBar({ notebook, onToggle }) {
180180
});
181181
}
182182

183+
// Restrict selectable columns to those used by the last converter (if required)
184+
if (
185+
explorer?.metadata?.restricts_to_converter_columns &&
186+
explorer?.metadata?.requires_converter_class
187+
) {
188+
const latestConverter = [...explorersAndConverters]
189+
.filter(
190+
(item) =>
191+
item.type === "converter" &&
192+
item.status === 3 &&
193+
item.converter === explorer.metadata.requires_converter_class,
194+
)
195+
.sort((a, b) => (b.id || 0) - (a.id || 0))[0];
196+
if (latestConverter) {
197+
const converterColNames = new Set(
198+
(latestConverter?.parameters?.scope?.columns || []).map(
199+
(c) => c.columnName,
200+
),
201+
);
202+
validColumns = validColumns.filter((col) =>
203+
converterColNames.has(col.columnName),
204+
);
205+
}
206+
}
207+
183208
// Check cardinality requirements
184209
if (inputCardinality.exact != null) {
185210
if (validColumns.length < inputCardinality.exact) {
@@ -218,6 +243,41 @@ export default function RightBar({ notebook, onToggle }) {
218243
})}`;
219244
}
220245

246+
// Check if a required converter class has been run and finished
247+
const requiresConverterClass = explorer?.metadata?.requires_converter_class;
248+
if (requiresConverterClass) {
249+
const finishedConverters = explorersAndConverters.filter(
250+
(item) =>
251+
item.type === "converter" &&
252+
item.status === 3 &&
253+
item.converter === requiresConverterClass,
254+
);
255+
if (finishedConverters.length === 0) {
256+
disabled = true;
257+
tooltip += `\n\n${t("datasets:error.requiresConverter", {
258+
converterClass: requiresConverterClass,
259+
})}`;
260+
} else {
261+
// Check if a specific algorithm is required (e.g. hdbscan, agglomerative)
262+
const requiresAlgorithm =
263+
explorer?.metadata?.requires_algorithm?.toLowerCase();
264+
if (requiresAlgorithm) {
265+
const latest = [...finishedConverters].sort(
266+
(a, b) => (b.id || 0) - (a.id || 0),
267+
)[0];
268+
const usedAlgorithm = (
269+
latest?.parameters?.params?.algorithm?.toLowerCase() ?? ""
270+
).replace(/clustering$/, "");
271+
if (usedAlgorithm !== requiresAlgorithm) {
272+
disabled = true;
273+
tooltip += `\n\n${t("datasets:error.requiresAlgorithm", {
274+
algorithm: requiresAlgorithm,
275+
})}`;
276+
}
277+
}
278+
}
279+
}
280+
221281
return { disabled, tooltip, validColumns };
222282
};
223283

@@ -273,7 +333,7 @@ export default function RightBar({ notebook, onToggle }) {
273333
notebook,
274334
};
275335
}),
276-
[explorers, datasetColumns, notebook?.id],
336+
[explorers, datasetColumns, notebook?.id, explorersAndConverters],
277337
);
278338

279339
const validatedConverters = useMemo(

DashAI/front/src/components/notebooks/explorerCreation/ScopeStepExplorer.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { useState } from "react";
1+
import { useState, useMemo } from "react";
22
import { Box, Typography } from "@mui/material";
33
import { useTheme } from "@mui/material/styles";
44
import FormSchemaButtonGroup from "../../shared/FormSchemaButtonGroup";
55
import ColumnSelector from "../ColumnSelector";
66
import { useTourContext } from "../../tour/TourProvider";
77
import { useTranslation } from "react-i18next";
8+
import { useExplorersAndConverters } from "../context/ExplorersAndConvertersContext";
89

910
export default function ScopeStepExplorer({
1011
notebook,
@@ -21,6 +22,25 @@ export default function ScopeStepExplorer({
2122
const typesDtypeRestrictions = tool?.metadata?.type_dtype_restrictions || {};
2223
const tourContext = useTourContext();
2324
const { t } = useTranslation(["datasets", "common"]);
25+
const { explorersAndConverters } = useExplorersAndConverters();
26+
27+
const allowedColumnNames = useMemo(() => {
28+
if (!tool?.metadata?.restricts_to_converter_columns) return null;
29+
const requiresClass = tool?.metadata?.requires_converter_class;
30+
if (!requiresClass) return null;
31+
const latest = [...explorersAndConverters]
32+
.filter(
33+
(item) =>
34+
item.type === "converter" &&
35+
item.status === 3 &&
36+
item.converter === requiresClass,
37+
)
38+
.sort((a, b) => (b.id || 0) - (a.id || 0))[0];
39+
if (!latest) return null;
40+
return new Set(
41+
(latest?.parameters?.scope?.columns || []).map((c) => c.columnName),
42+
);
43+
}, [explorersAndConverters, tool?.metadata]);
2444

2545
const handleSubmit = () => {
2646
nextStep();
@@ -53,6 +73,7 @@ export default function ScopeStepExplorer({
5373
allowedTypes={allowedTypes}
5474
allowedDtypes={allowedDtypes}
5575
typesDtypeRestrictions={typesDtypeRestrictions}
76+
allowedColumnNames={allowedColumnNames}
5677
onSelectionChange={(selected) => setScopeColumns(selected)}
5778
onValidationChange={(isValid) => setIsSelectionValid(isValid)}
5879
/>

DashAI/front/src/components/shared/FormSchemaField.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function FormSchemaField({ objName, paramJsonSchema, field, error }) {
4747
{...commonProps}
4848
options={paramJsonSchema.enum}
4949
optionNames={paramJsonSchema.enumNames}
50+
optionDescriptions={paramJsonSchema.optionDescriptions}
5051
/>
5152
);
5253
} else {

DashAI/front/src/components/shared/FormSchemaFieldWithCollapse.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ function FormSchemaFieldWithCollapse({
1919
description,
2020
errorMessage,
2121
children,
22+
defaultExpanded = false,
2223
}) {
23-
const [showSection, setShowSection] = React.useState(false);
24+
const [showSection, setShowSection] = React.useState(defaultExpanded);
2425
const { t } = useTranslation(["common"]);
2526

2627
const toggleButton = (
@@ -56,6 +57,7 @@ FormSchemaFieldWithCollapse.propTypes = {
5657
description: PropTypes.string,
5758
errorMessage: PropTypes.string,
5859
children: PropTypes.node,
60+
defaultExpanded: PropTypes.bool,
5961
};
6062

6163
export default FormSchemaFieldWithCollapse;

0 commit comments

Comments
 (0)