Skip to content

Commit 64a9b52

Browse files
FredLL-AvaigaFred Lefévère-Laoide
andauthored
auth and not auth related or otherwise (#2815)
* auth and not auth related - in selectors, no selection should return None - Scenario dialog should forget previous data - Properties Editor should not allow empty keys and be auth aware - job selectors selection on data change * recommended by copilot * be a bit more easy on looking for data accessor * protect when removing from list * dynamic list property camel case on frontend * fab comment * more coverage * Fab's comments --------- Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
1 parent f494ab2 commit 64a9b52

File tree

9 files changed

+330
-35
lines changed

9 files changed

+330
-35
lines changed

frontend/taipy/src/CoreSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ const CoreSelector = (props: CoreSelectorProps) => {
423423
dispatch(
424424
createSendUpdateAction(
425425
updateVarName,
426-
multiple ? [] : "",
426+
multiple ? [] : null,
427427
module,
428428
onChange,
429429
propagate,

frontend/taipy/src/JobSelector.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,25 @@ const JobSelector = (props: JobSelectorProps) => {
763763
setJobRows(filteredJobRows);
764764
const jobIds = filteredJobRows.map((j) => j[JobProps.id]);
765765
setChecked((ids) => ids.filter((id) => jobIds.includes(id)));
766-
}, [filters, props.jobs]);
766+
setSelected((ids) => {
767+
const newSelected = ids.filter((id) => jobIds.includes(id));
768+
if (ids.length !== newSelected.length) {
769+
const jobsVar = getUpdateVar(props.updateVars, "jobs");
770+
dispatch(
771+
createSendUpdateAction(
772+
props.updateVarName,
773+
newSelected,
774+
module,
775+
props.onChange,
776+
propagate,
777+
jobsVar,
778+
),
779+
);
780+
return newSelected;
781+
}
782+
return ids;
783+
});
784+
}, [filters, props.jobs, dispatch, module, props.onChange, props.updateVarName, props.updateVars, propagate]);
767785

768786
useEffect(() => {
769787
if (props.value) {

frontend/taipy/src/PropertiesEditor.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
9595
const { id: propId = "" } = dataset || e?.currentTarget.dataset || {};
9696
const property = propId ? properties.find((p) => p.id === propId) : newProp;
9797
if (property) {
98+
if (!property.key) {
99+
return;
100+
}
98101
const oldId = property.id;
99102
const payload: PropertiesEditPayload = {
100103
id: entityId,
@@ -104,13 +107,15 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
104107
if (oldId && oldId != property.key) {
105108
payload.deleted_properties = [{ key: oldId }];
106109
}
107-
dispatch(createSendActionNameAction(id, module, props.onEdit, payload));
110+
if (payload.properties?.length || payload.deleted_properties?.length) {
111+
dispatch(createSendActionNameAction(id, module, props.onEdit, payload));
112+
}
108113
}
109114
setNewProp((np) => ({ ...np, key: "", value: "" }));
110115
setFocusName("");
111116
}
112117
},
113-
[isDefined, props.onEdit, entityId, properties, newProp, id, dispatch, module, setFocusName, updateVars]
118+
[isDefined, props.onEdit, entityId, properties, newProp, id, dispatch, module, setFocusName, updateVars],
114119
);
115120
const cancelProperty = useCallback(
116121
(e?: MouseEvent<HTMLElement>, dataset?: DOMStringMap) => {
@@ -120,12 +125,12 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
120125
const property = entProperties.find(([key]) => key === propId);
121126
property &&
122127
setProperties((ps) =>
123-
ps.map((p) => (p.id === property[0] ? { ...p, key: property[0], value: property[1] } : p))
128+
ps.map((p) => (p.id === property[0] ? { ...p, key: property[0], value: property[1] } : p)),
124129
);
125130
setFocusName("");
126131
}
127132
},
128-
[isDefined, entProperties, setFocusName]
133+
[isDefined, entProperties, setFocusName],
129134
);
130135

131136
const onKeyDown = useCallback(
@@ -142,7 +147,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
142147
}
143148
}
144149
},
145-
[editProperty, cancelProperty]
150+
[editProperty, cancelProperty],
146151
);
147152

148153
const deleteProperty = useCallback(
@@ -156,11 +161,11 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
156161
createSendActionNameAction(id, module, props.onEdit, {
157162
id: entityId,
158163
deleted_properties: [property],
159-
})
164+
}),
160165
);
161166
setFocusName("");
162167
},
163-
[props.onEdit, entityId, id, dispatch, module, properties, setFocusName]
168+
[props.onEdit, entityId, id, dispatch, module, properties, setFocusName],
164169
);
165170

166171
useEffect(() => {
@@ -170,7 +175,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
170175
id: k,
171176
key: k,
172177
value: v,
173-
}))
178+
})),
174179
);
175180
}, [show, entProperties]);
176181

@@ -232,8 +237,9 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
232237
data-id={property.id}
233238
onClick={editProperty}
234239
size="small"
240+
disabled={!property.key || !isDefined}
235241
>
236-
<CheckCircle color="primary" />
242+
<CheckCircle color={disableColor("primary", !property.key || !isDefined)} />
237243
</IconButton>
238244
</Tooltip>
239245
<Tooltip title="Cancel">
@@ -295,7 +301,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
295301
onClick={onFocus}
296302
sx={hoverSx}
297303
>
298-
{active && focusName == "new-property" ? (
304+
{active && !notEditableReason && focusName == "new-property" ? (
299305
<>
300306
<Grid size={4}>
301307
<TextField
@@ -318,14 +324,21 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
318324
variant="outlined"
319325
sx={FieldNoMaxWidth}
320326
disabled={!isDefined}
321-
slotProps={{ htmlInput: { onKeyDown, "data-enter": true }}}
327+
slotProps={{ htmlInput: { onKeyDown, "data-enter": true } }}
322328
/>
323329
</Grid>
324330
<Grid size={2} container alignContent="center" alignItems="center" justifyContent="center">
325331
<Tooltip title="Apply">
326-
<IconButton sx={IconPaddingSx} onClick={editProperty} size="small">
327-
<CheckCircle color="primary" />
328-
</IconButton>
332+
<span>
333+
<IconButton
334+
sx={IconPaddingSx}
335+
onClick={editProperty}
336+
size="small"
337+
disabled={!newProp.key || !isDefined}
338+
>
339+
<CheckCircle color={disableColor("primary", !newProp.key || !isDefined)} />
340+
</IconButton>
341+
</span>
329342
</Tooltip>
330343
<Tooltip title="Cancel">
331344
<IconButton sx={IconPaddingSx} onClick={cancelProperty} size="small">

frontend/taipy/src/ScenarioSelector.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
195195

196196
useEffect(() => {
197197
form.setValues(
198-
scenario
198+
actionEdit && scenario
199199
? {
200200
id: scenario[ScFProps.id],
201201
config: scenario[ScFProps.config_id],
@@ -206,10 +206,10 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
206206
: { ...emptyScenario, config: configs?.length === 1 ? configs[0][0] : "" },
207207
);
208208
setProperties(
209-
scenario ? scenario[ScFProps.properties].map(([k, v], i) => ({ id: i + "", key: k, value: v })) : [],
209+
actionEdit && scenario ? scenario[ScFProps.properties].map(([k, v], i) => ({ id: i + "", key: k, value: v })) : [],
210210
);
211211
// eslint-disable-next-line react-hooks/exhaustive-deps
212-
}, [scenario, configs]);
212+
}, [actionEdit, scenario, configs]);
213213

214214
const onDeleteScenario = useCallback(() => {
215215
submit(actionEdit, true, { id: scenario && scenario[ScFProps.id] });

taipy/gui/_renderers/builder.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,9 @@ def __set_dynamic_string_list(self, name: str, default_value: t.Any):
338338
if isinstance(value, list):
339339
self.__set_json_attribute(_to_camel_case(f"default_{name}"), value)
340340
if hash := self.__hashes.get(name):
341-
self.__update_vars.append(f"{name}={hash}")
342-
self.__set_react_attribute(name, hash)
341+
var_name = _to_camel_case(name)
342+
self.__update_vars.append(f"{var_name}={hash}")
343+
self.__set_react_attribute(var_name, hash)
343344
return self
344345

345346
def __set_list_attribute(

taipy/gui/data/data_accessor.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ def _get_instance(self, value: _TaipyData) -> _DataAccessor:
149149
access = self.__access_4_type.get(type(converted_value))
150150
if access is not None:
151151
return access
152+
for cl in self.__access_4_type.keys():
153+
if isinstance(value, cl):
154+
access = self.__access_4_type[cl]
155+
self.__access_4_type[type(value)] = access
156+
return access
157+
152158
_warn(f"Can't find Data Accessor for type {str(type(value))}.")
153159
return self.__invalid_data_accessor
154160
return access

taipy/gui/gui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ def __send_var_list_update( # noqa C901
11821182
for k, v in values.items():
11831183
if isinstance(v, (_TaipyData, _TaipyContentHtml)) and v.get_name() in modified_vars:
11841184
modified_vars.remove(v.get_name())
1185-
elif isinstance(v, _DoNotUpdate):
1185+
elif isinstance(v, _DoNotUpdate) and k in modified_vars:
11861186
modified_vars.remove(k)
11871187
custom_page_filtered_types = _Hooks()._get_resource_handler_data_layer_supported_types()
11881188
in_custom_page_context = _Hooks()._is_in_custom_page_context()

taipy/gui_core/_adapters.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ def get(self):
111111
_get_reason(is_editable(scenario), "Scenario not editable"),
112112
]
113113
except Exception as e:
114-
_warn(f"Access to scenario ({data.id if hasattr(data, 'id') else 'No_id'}) failed", e)
114+
if not _is_invalidCredentials_exception(e):
115+
_warn(
116+
f"Access to scenario '{data.id}'" if hasattr(data, "id") else "Access to anonymous scenario", e
117+
)
115118

116119
return None
117120

@@ -170,7 +173,10 @@ def get(self):
170173
],
171174
]
172175
except Exception as e:
173-
_warn(f"Access to scenario ({data.id if hasattr(data, 'id') else 'No_id'}) failed", e)
176+
if not _is_invalidCredentials_exception(e):
177+
_warn(
178+
f"Access to scenario '{data.id}'" if hasattr(data, "id") else "Access to anonymous scenario", e
179+
)
174180

175181
return None
176182

@@ -213,13 +219,7 @@ def __get_data(self, dn: DataNode):
213219
json.dumps(value)
214220
except Exception as e:
215221
error = f"Unsupported data: {e}."
216-
return (
217-
value,
218-
val_type,
219-
None,
220-
error,
221-
isinstance(dn, JSONDataNode)
222-
)
222+
return (value, val_type, None, error, isinstance(dn, JSONDataNode))
223223
except Exception as e:
224224
return (None, None, None, f"read data_node: {e}")
225225
return (None, None, None, f"Data unavailable for {dn.get_simple_label()}")
@@ -260,7 +260,11 @@ def get(self):
260260
else "",
261261
]
262262
except Exception as e:
263-
_warn(f"Access to data node ({data.id if hasattr(data, 'id') else 'No_id'}) failed", e)
263+
if not _is_invalidCredentials_exception(e):
264+
_warn(
265+
f"Access to data node '{data.id}'" if hasattr(data, "id") else "Access to anonymous data node",
266+
e,
267+
)
264268

265269
return None
266270

@@ -269,6 +273,10 @@ def get_hash():
269273
return _TaipyBase._HOLDER_PREFIX + "Dn"
270274

271275

276+
def _is_invalidCredentials_exception(e: Exception):
277+
return type(e).__name__ == "InvalidCredentials"
278+
279+
272280
_operators: t.Dict[str, t.Callable] = {
273281
"==": eq,
274282
"!=": ne,
@@ -568,9 +576,7 @@ class _GuiCoreDatanodeProperties(_GuiCoreProperties):
568576
_GuiCorePropDesc(DataNodeFilter("Last edit date", datetime, "last_edit_date"), for_sort=True),
569577
_GuiCorePropDesc(DataNodeFilter("Expiration date", datetime, "expiration_date"), extended=True, for_sort=True),
570578
_GuiCorePropDesc(DataNodeFilter("Expired", bool, "is_expired"), extended=True),
571-
_GuiCorePropDesc(
572-
DataNodeFilter("Rank", int, "_get_rank()", [ParamType.ScenarioConfigId]), for_sort=True
573-
),
579+
_GuiCorePropDesc(DataNodeFilter("Rank", int, "_get_rank()", [ParamType.ScenarioConfigId]), for_sort=True),
574580
]
575581
__DN_VALIDITY = None
576582

0 commit comments

Comments
 (0)