Przeglądaj źródła

rename properties and refactor (#296)

* refactor
rename properties

* refactor
rename properties

* doc

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 rok temu
rodzic
commit
760d816648

+ 136 - 106
gui/src/JobSelector.tsx

@@ -62,10 +62,10 @@ interface JobSelectorProps {
     className?: string;
     dynamicClassName?: string;
     height: string;
-    showJobId?: boolean;
-    showEntityLabel?: boolean;
-    showEntityId?: boolean;
-    showSubmitId?: boolean;
+    showId?: boolean;
+    showSubmittedLabel?: boolean;
+    showSubmittedId?: boolean;
+    showSubmissionId?: boolean;
     showDate?: boolean;
     showCancel?: boolean;
     showDelete?: boolean;
@@ -84,9 +84,9 @@ enum JobProps {
     id,
     name,
     _,
-    entity_id,
-    entity_name,
-    submit_id,
+    submitted_id,
+    submitted_label,
+    submission_id,
     creation_date,
     status,
 }
@@ -150,7 +150,12 @@ interface FilterProps {
     columns: JobSelectorColumns[];
 }
 const Filter = ({ open, anchorEl, handleFilterClose, handleApplyFilter, columns }: FilterProps) => {
-    const form = useFormik<{filters: FilterData[], newData: "" | number, newOperator: "is" | "isnot", newValue: string}>({
+    const form = useFormik<{
+        filters: FilterData[];
+        newData: "" | number;
+        newOperator: "is" | "isnot";
+        newValue: string;
+    }>({
         initialValues: {
             filters: [],
             newData: "",
@@ -284,10 +289,10 @@ const Filter = ({ open, anchorEl, handleFilterClose, handleApplyFilter, columns
                                                 (item.showPrimaryLabel || item.showSecondayLabel)
                                         )
                                         .map((item) => (
-                                                <MenuItem key={item.id} value={item.columnIndex}>
-                                                    {item.primaryLabel}
-                                                </MenuItem>
-                                            ))}
+                                            <MenuItem key={item.id} value={item.columnIndex}>
+                                                {item.primaryLabel}
+                                            </MenuItem>
+                                        ))}
                                 </Select>
                             </FormControl>
                         </Grid>
@@ -315,11 +320,16 @@ const Filter = ({ open, anchorEl, handleFilterClose, handleApplyFilter, columns
                         </Grid>
                     </Grid>
                     <Grid item xs={12} container justifyContent="space-between" mt={2}>
-                        <Button variant="outlined" color="inherit" onClick={removeAllFilter} disabled={!form.values.filters.length}>
+                        <Button
+                            variant="outlined"
+                            color="inherit"
+                            onClick={removeAllFilter}
+                            disabled={!form.values.filters.length}
+                        >
                             Remove all filters
                         </Button>
                         <Button variant="contained" type="submit">
-                            Apply {form.values.filters.length} filter{form.values.filters.length > 1 ? "s": ""}
+                            Apply {form.values.filters.length} filter{form.values.filters.length > 1 ? "s" : ""}
                         </Button>
                     </Grid>
                 </Grid>
@@ -334,33 +344,31 @@ interface JobSelectedTableHeadProps {
     handleSelectAllClick?: (event: React.ChangeEvent<HTMLInputElement>) => void;
     columns: JobSelectorColumns[];
 }
-function JobSelectedTableHead({ jobs, selected, handleSelectAllClick, columns }: JobSelectedTableHeadProps) {
-    return (
-        <TableHead>
-            <TableRow>
-                <TableCell padding="checkbox">
-                    <Checkbox
-                        color="primary"
-                        checked={!!jobs && jobs == selected}
-                        indeterminate={!!jobs && !!selected && jobs != selected}
-                        onChange={handleSelectAllClick}
-                    />
-                </TableCell>
-                {columns
-                    .filter((i) => i.showPrimaryLabel || i.showSecondayLabel)
-                    .map((item) => (
-                        <TableCell key={item.id}>
-                            {item.secondaryLabel ? (
-                                <ListItemText primary={item.primaryLabel} secondary={item.secondaryLabel} />
-                            ) : (
-                                item.primaryLabel
-                            )}
-                        </TableCell>
-                    ))}
-            </TableRow>
-        </TableHead>
-    );
-}
+const JobSelectedTableHead = ({ jobs, selected, handleSelectAllClick, columns }: JobSelectedTableHeadProps) => (
+    <TableHead>
+        <TableRow>
+            <TableCell padding="checkbox">
+                <Checkbox
+                    color="primary"
+                    checked={!!jobs && jobs == selected}
+                    indeterminate={!!jobs && !!selected && jobs != selected}
+                    onChange={handleSelectAllClick}
+                />
+            </TableCell>
+            {columns
+                .filter((c) => c.showPrimaryLabel || c.showSecondayLabel)
+                .map((col) => (
+                    <TableCell key={col.id}>
+                        {col.secondaryLabel ? (
+                            <ListItemText primary={col.primaryLabel} secondary={col.secondaryLabel} />
+                        ) : (
+                            col.primaryLabel
+                        )}
+                    </TableCell>
+                ))}
+        </TableRow>
+    </TableHead>
+);
 
 interface JobSelectedTableRowProps {
     row: Job;
@@ -370,10 +378,10 @@ interface JobSelectedTableRowProps {
     handleCheckboxClick: (event: React.MouseEvent<HTMLElement>) => void;
     handleCancelJobs: (event: React.MouseEvent<HTMLElement>) => void;
     handleDeleteJobs: (event: React.MouseEvent<HTMLElement>) => void;
-    showJobId?: boolean;
-    showEntityLabel?: boolean;
-    showEntityId?: boolean;
-    showSubmitId?: boolean;
+    showId?: boolean;
+    showSubmittedLabel?: boolean;
+    showSubmittedId?: boolean;
+    showSubmissionId?: boolean;
     showDate?: boolean;
     showCancel?: boolean;
     showDelete?: boolean;
@@ -386,10 +394,10 @@ const JobSelectedTableRow = ({
     handleCheckboxClick,
     handleCancelJobs,
     handleDeleteJobs,
-    showJobId,
-    showEntityLabel,
-    showEntityId,
-    showSubmitId,
+    showId,
+    showSubmittedLabel,
+    showSubmittedId,
+    showSubmissionId,
     showDate,
     showCancel,
     showDelete,
@@ -398,21 +406,29 @@ const JobSelectedTableRow = ({
     const [id, jobName, _, entityId, entityName, submitId, creationDate, status] = row;
 
     return (
-        <TableRow hover role="checkbox" tabIndex={-1} key={id} data-id={id} onClick={handleSelection} selected={isSelected}>
+        <TableRow
+            hover
+            role="checkbox"
+            tabIndex={-1}
+            key={id}
+            data-id={id}
+            onClick={handleSelection}
+            selected={isSelected}
+        >
             <TableCell padding="checkbox">
                 <Checkbox color="primary" checked={isItemSelected} data-id={id} onClick={handleCheckboxClick} />
             </TableCell>
-            {showJobId ? (
+            {showId ? (
                 <TableCell component="th" scope="row" padding="none">
                     <ListItemText primary={jobName} secondary={id} />
                 </TableCell>
             ) : null}
-            {showSubmitId ? <TableCell>{submitId}</TableCell> : null}
-            {showEntityLabel || showEntityId ? (
+            {showSubmissionId ? <TableCell>{submitId}</TableCell> : null}
+            {showSubmittedLabel || showSubmittedId ? (
                 <TableCell>
-                    {!showEntityLabel && showEntityId ? (
+                    {!showSubmittedLabel && showSubmittedId ? (
                         entityId
-                    ) : !showEntityId && showEntityLabel ? (
+                    ) : !showSubmittedId && showSubmittedLabel ? (
                         entityName
                     ) : (
                         <ListItemText primary={entityName} secondary={entityId} />
@@ -451,10 +467,10 @@ const JobSelectedTableRow = ({
 const JobSelector = (props: JobSelectorProps) => {
     const {
         id = "",
-        showJobId = true,
-        showEntityLabel = true,
-        showEntityId = true,
-        showSubmitId = true,
+        showId = true,
+        showSubmittedLabel = true,
+        showSubmittedId = true,
+        showSubmissionId = true,
         showDate = true,
         showCancel = true,
         showDelete = true,
@@ -491,25 +507,25 @@ const JobSelector = (props: JobSelectorProps) => {
             {
                 id: "jobId",
                 primaryLabel: "Job",
-                showPrimaryLabel: showJobId,
+                showPrimaryLabel: showId,
                 secondaryLabel: "ID",
-                showSecondayLabel: showJobId,
+                showSecondayLabel: showId,
                 columnIndex: JobProps.id,
             },
             {
                 id: "submitID",
-                primaryLabel: "Submit ID",
-                columnIndex: JobProps.submit_id,
-                showPrimaryLabel: showSubmitId,
-                showSecondayLabel: showSubmitId,
+                primaryLabel: "Submission ID",
+                columnIndex: JobProps.submission_id,
+                showPrimaryLabel: showSubmissionId,
+                showSecondayLabel: showSubmissionId,
             },
             {
                 id: "submitEntity",
                 primaryLabel: "Submitted entity",
                 secondaryLabel: "ID",
-                columnIndex: JobProps.entity_id,
-                showPrimaryLabel: showEntityLabel,
-                showSecondayLabel: showEntityId,
+                columnIndex: JobProps.submitted_id,
+                showPrimaryLabel: showSubmittedLabel,
+                showSecondayLabel: showSubmittedId,
             },
             {
                 id: "createdDt",
@@ -533,7 +549,7 @@ const JobSelector = (props: JobSelectorProps) => {
                 showSecondayLabel: showDelete,
             },
         ],
-        [showDate, showEntityId, showEntityLabel, showJobId, showSubmitId, showCancel, showDelete]
+        [showDate, showSubmittedId, showSubmittedLabel, showId, showSubmissionId, showCancel, showDelete]
     );
 
     const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
@@ -552,31 +568,27 @@ const JobSelector = (props: JobSelectorProps) => {
         });
     }, []);
 
-    const handleSelection = useCallback((event: React.MouseEvent<HTMLElement>) => {
-        const { id = "" } = event.currentTarget?.dataset || {};
-        setSelected((oldSelected) => {
-            const newSelected = [...oldSelected];
-            const selectedIndex = newSelected.indexOf(id);
-
-            if (selectedIndex === -1) {
-                newSelected.push(id);
-            } else {
-                newSelected.splice(selectedIndex, 1);
-            }
-            const jobsVar = getUpdateVar(props.updateVars, "jobs");
-            dispatch(
-                createSendUpdateAction(
-                    props.updateVarName,
-                    newSelected,
-                    module,
-                    props.onChange,
-                    propagate,
-                    jobsVar
-                )
-            );
-            return newSelected;
-        });
-    }, [dispatch, module, props.onChange, props.updateVars, props.updateVarName, propagate]);
+    const handleSelection = useCallback(
+        (event: React.MouseEvent<HTMLElement>) => {
+            const { id = "" } = event.currentTarget?.dataset || {};
+            setSelected((oldSelected) => {
+                const newSelected = [...oldSelected];
+                const selectedIndex = newSelected.indexOf(id);
+
+                if (selectedIndex === -1) {
+                    newSelected.push(id);
+                } else {
+                    newSelected.splice(selectedIndex, 1);
+                }
+                const jobsVar = getUpdateVar(props.updateVars, "jobs");
+                dispatch(
+                    createSendUpdateAction(props.updateVarName, newSelected, module, props.onChange, propagate, jobsVar)
+                );
+                return newSelected;
+            });
+        },
+        [dispatch, module, props.onChange, props.updateVars, props.updateVarName, propagate]
+    );
 
     const handleCheckAllClick = useCallback(
         (event: React.ChangeEvent<HTMLInputElement>) =>
@@ -589,7 +601,12 @@ const JobSelector = (props: JobSelectorProps) => {
             event.stopPropagation();
             const { id = "", multiple = false } = event.currentTarget?.dataset || {};
             try {
-                dispatch(createSendActionNameAction(props.id, module, props.onJobAction, {id: multiple === false ? [id] : JSON.parse(id), action: "cancel"}));
+                dispatch(
+                    createSendActionNameAction(props.id, module, props.onJobAction, {
+                        id: multiple === false ? [id] : JSON.parse(id),
+                        action: "cancel",
+                    })
+                );
             } catch (e) {
                 console.warn("Error parsing ids for cancel.", e);
             }
@@ -602,7 +619,12 @@ const JobSelector = (props: JobSelectorProps) => {
             event.stopPropagation();
             const { id = "", multiple = false } = event.currentTarget?.dataset || {};
             try {
-                dispatch(createSendActionNameAction(props.id, module, props.onJobAction, {id: multiple === false ? [id] : JSON.parse(id), action: "delete"}));
+                dispatch(
+                    createSendActionNameAction(props.id, module, props.onJobAction, {
+                        id: multiple === false ? [id] : JSON.parse(id),
+                        action: "delete",
+                    })
+                );
             } catch (e) {
                 console.warn("Error parsing ids for delete.", e);
             }
@@ -616,7 +638,11 @@ const JobSelector = (props: JobSelectorProps) => {
             !jobRows.some(
                 (job) =>
                     checked.includes(job[JobProps.id]) &&
-                    !(job[JobProps.status] === JobStatus.SUBMITTED || job[JobProps.status] === JobStatus.BLOCKED || job[JobProps.status] === JobStatus.PENDING)
+                    !(
+                        job[JobProps.status] === JobStatus.SUBMITTED ||
+                        job[JobProps.status] === JobStatus.BLOCKED ||
+                        job[JobProps.status] === JobStatus.PENDING
+                    )
             ),
         [jobRows, checked]
     );
@@ -658,9 +684,13 @@ const JobSelector = (props: JobSelectorProps) => {
                                 if (filter.data === JobProps.status) {
                                     rowColumnValue = JobStatus[job[JobProps.status]].toLowerCase();
                                 } else if (filter.data === JobProps.id) {
-                                    rowColumnValue = `${job[JobProps.id].toLowerCase()}${job[JobProps.name].toLowerCase()}`;
-                                } else if (filter.data === JobProps.entity_id) {
-                                    rowColumnValue = `${job[JobProps.entity_id].toLowerCase()}${job[JobProps.entity_name].toLowerCase()}`;
+                                    rowColumnValue = `${job[JobProps.id].toLowerCase()}${job[
+                                        JobProps.name
+                                    ].toLowerCase()}`;
+                                } else if (filter.data === JobProps.submitted_id) {
+                                    rowColumnValue = `${job[JobProps.submitted_id].toLowerCase()}${job[
+                                        JobProps.submitted_label
+                                    ].toLowerCase()}`;
                                 } else if (filter.data === JobProps.creation_date) {
                                     rowColumnValue = new Date(job[JobProps.creation_date]).toLocaleString();
                                 } else if (filter.data < JobLength) {
@@ -679,11 +709,11 @@ const JobSelector = (props: JobSelectorProps) => {
 
     useEffect(() => {
         if (props.value) {
-            setSelected(Array.isArray(props.value) ? props.value: [props.value]);
+            setSelected(Array.isArray(props.value) ? props.value : [props.value]);
         } else if (props.defaultValue) {
             try {
                 const val = JSON.parse(props.defaultValue);
-                setSelected(Array.isArray(val) ? val: [val]);
+                setSelected(Array.isArray(val) ? val : [val]);
             } catch (e) {
                 console.warn("Error parsing jobs default value", e);
             }
@@ -785,10 +815,10 @@ const JobSelector = (props: JobSelectorProps) => {
                                     key={row[JobProps.id]}
                                     handleDeleteJobs={handleDeleteJobs}
                                     handleCancelJobs={handleCancelJobs}
-                                    showSubmitId={showSubmitId}
-                                    showJobId={showJobId}
-                                    showEntityLabel={showEntityLabel}
-                                    showEntityId={showEntityId}
+                                    showSubmissionId={showSubmissionId}
+                                    showId={showId}
+                                    showSubmittedLabel={showSubmittedLabel}
+                                    showSubmittedId={showSubmittedId}
                                     showDate={showDate}
                                     showCancel={showCancel}
                                     showDelete={showDelete}

+ 6 - 711
src/taipy/gui_core/GuiCoreLib.py

@@ -9,720 +9,15 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
-import json
 import typing as t
-from collections import defaultdict
 from datetime import datetime
-from enum import Enum
-from numbers import Number
-from threading import Lock
 
-try:
-    import zoneinfo
-except ImportError:
-    from backports import zoneinfo  # type: ignore[no-redef]
-
-import pandas as pd
-from dateutil import parser
-
-from taipy.config import Config
-from taipy.core import Cycle, DataNode, Job, Scenario, Sequence, cancel_job, create_scenario
-from taipy.core import delete as core_delete
-from taipy.core import delete_job
-from taipy.core import get as core_get
-from taipy.core import (
-    get_cycles_scenarios,
-    get_data_nodes,
-    get_jobs,
-    is_deletable,
-    is_promotable,
-    is_submittable,
-    set_primary,
-)
-from taipy.core import submit as core_submit
-from taipy.core.data._abstract_tabular import _AbstractTabularDataNode
-from taipy.core.notification import CoreEventConsumerBase, EventEntityType
-from taipy.core.notification.event import Event
-from taipy.core.notification.notifier import Notifier
 from taipy.gui import Gui, State
-from taipy.gui._warnings import _warn
 from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
-from taipy.gui.gui import _DoNotUpdate
-from taipy.gui.utils import _TaipyBase
 
 from ..version import _get_version
-
-
-# prevent gui from trying to push scenario instances to the front-end
-class _GCDoNotUpdate(_DoNotUpdate):
-    def __repr__(self):
-        return self.get_label() if hasattr(self, "get_label") else super().__repr__()
-
-
-Scenario.__bases__ += (_GCDoNotUpdate,)
-Sequence.__bases__ += (_GCDoNotUpdate,)
-DataNode.__bases__ += (_GCDoNotUpdate,)
-Job.__bases__ += (_GCDoNotUpdate,)
-
-
-class _EntityType(Enum):
-    CYCLE = 0
-    SCENARIO = 1
-    SEQUENCE = 2
-    DATANODE = 3
-
-
-class _GuiCoreScenarioAdapter(_TaipyBase):
-    __INNER_PROPS = ["name"]
-
-    def get(self):
-        data = super().get()
-        if isinstance(data, Scenario):
-            scenario = core_get(data.id)
-            if scenario:
-                return [
-                    scenario.id,
-                    scenario.is_primary,
-                    scenario.config_id,
-                    scenario.cycle.get_simple_label() if scenario.cycle else "",
-                    scenario.get_simple_label(),
-                    list(scenario.tags) if scenario.tags else [],
-                    [(k, v) for k, v in scenario.properties.items() if k not in _GuiCoreScenarioAdapter.__INNER_PROPS]
-                    if scenario.properties
-                    else [],
-                    [(p.id, p.get_simple_label(), is_submittable(p)) for p in scenario.sequences.values()]
-                    if scenario.sequences
-                    else [],
-                    list(scenario.properties.get("authorized_tags", [])) if scenario.properties else [],
-                    is_deletable(scenario),  # deletable
-                    is_promotable(scenario),
-                    is_submittable(scenario),
-                ]
-        return None
-
-    @staticmethod
-    def get_hash():
-        return _TaipyBase._HOLDER_PREFIX + "Sc"
-
-
-class _GuiCoreScenarioDagAdapter(_TaipyBase):
-    @staticmethod
-    def get_entity_type(node: t.Any):
-        return DataNode.__name__ if isinstance(node.entity, DataNode) else node.type
-
-    def get(self):
-        data = super().get()
-        if isinstance(data, Scenario):
-            scenario = core_get(data.id)
-            if scenario:
-                dag = data._get_dag()
-                nodes = dict()
-                for id, node in dag.nodes.items():
-                    entityType = _GuiCoreScenarioDagAdapter.get_entity_type(node)
-                    cat = nodes.get(entityType)
-                    if cat is None:
-                        cat = dict()
-                        nodes[entityType] = cat
-                    cat[id] = {
-                        "name": node.entity.get_simple_label(),
-                        "type": node.entity.storage_type() if hasattr(node.entity, "storage_type") else None,
-                    }
-                return [
-                    data.id,
-                    nodes,
-                    [
-                        (
-                            _GuiCoreScenarioDagAdapter.get_entity_type(e.src),
-                            e.src.entity.id,
-                            _GuiCoreScenarioDagAdapter.get_entity_type(e.dest),
-                            e.dest.entity.id,
-                        )
-                        for e in dag.edges
-                    ],
-                ]
-        return None
-
-    @staticmethod
-    def get_hash():
-        return _TaipyBase._HOLDER_PREFIX + "ScG"
-
-
-class _GuiCoreDatanodeAdapter(_TaipyBase):
-    __INNER_PROPS = ["name"]
-
-    def get(self):
-        data = super().get()
-        if isinstance(data, DataNode):
-            datanode = core_get(data.id)
-            if datanode:
-                owner = core_get(datanode.owner_id) if datanode.owner_id else None
-                return [
-                    datanode.id,
-                    datanode.storage_type() if hasattr(datanode, "storage_type") else "",
-                    datanode.config_id,
-                    f"{datanode.last_edit_date}" if datanode.last_edit_date else "",
-                    f"{datanode.expiration_date}" if datanode.last_edit_date else "",
-                    datanode.get_simple_label(),
-                    datanode.owner_id or "",
-                    owner.get_simple_label() if owner else "GLOBAL",
-                    _EntityType.CYCLE.value
-                    if isinstance(owner, Cycle)
-                    else _EntityType.SCENARIO.value
-                    if isinstance(owner, Scenario)
-                    else -1,
-                    [
-                        (k, f"{v}")
-                        for k, v in datanode._get_user_properties().items()
-                        if k not in _GuiCoreDatanodeAdapter.__INNER_PROPS
-                    ],
-                ]
-        return None
-
-    @staticmethod
-    def get_hash():
-        return _TaipyBase._HOLDER_PREFIX + "Dn"
-
-
-class _GuiCoreContext(CoreEventConsumerBase):
-    __PROP_ENTITY_ID = "id"
-    __PROP_ENTITY_COMMENT = "comment"
-    __PROP_CONFIG_ID = "config"
-    __PROP_DATE = "date"
-    __PROP_ENTITY_NAME = "name"
-    __PROP_SCENARIO_PRIMARY = "primary"
-    __PROP_SCENARIO_TAGS = "tags"
-    __ENTITY_PROPS = (__PROP_CONFIG_ID, __PROP_DATE, __PROP_ENTITY_NAME)
-    __ACTION = "action"
-    _CORE_CHANGED_NAME = "core_changed"
-    _SCENARIO_SELECTOR_ERROR_VAR = "gui_core_sc_error"
-    _SCENARIO_SELECTOR_ID_VAR = "gui_core_sc_id"
-    _SCENARIO_VIZ_ERROR_VAR = "gui_core_sv_error"
-    _JOB_SELECTOR_ERROR_VAR = "gui_core_js_error"
-    _DATANODE_VIZ_ERROR_VAR = "gui_core_dv_error"
-    _DATANODE_VIZ_OWNER_ID_VAR = "gui_core_dv_owner_id"
-    _DATANODE_VIZ_HISTORY_ID_VAR = "gui_core_dv_history_id"
-    _DATANODE_VIZ_DATA_ID_VAR = "gui_core_dv_data_id"
-    _DATANODE_VIZ_DATA_CHART_ID_VAR = "gui_core_dv_data_chart_id"
-    _DATANODE_VIZ_DATA_NODE_PROP = "data_node"
-
-    def __init__(self, gui: Gui) -> None:
-        self.gui = gui
-        self.scenario_by_cycle: t.Optional[t.Dict[t.Optional[Cycle], t.List[Scenario]]] = None
-        self.data_nodes_by_owner: t.Optional[t.Dict[t.Optional[str], DataNode]] = None
-        self.scenario_configs: t.Optional[t.List[t.Tuple[str, str]]] = None
-        self.jobs_list: t.Optional[t.List[Job]] = None
-        # register to taipy core notification
-        reg_id, reg_queue = Notifier.register()
-        self.lock = Lock()
-        super().__init__(reg_id, reg_queue)
-        self.start()
-
-    def process_event(self, event: Event):
-        if event.entity_type == EventEntityType.SCENARIO:
-            with self.lock:
-                self.scenario_by_cycle = None
-                self.data_nodes_by_owner = None
-            scenario = core_get(event.entity_id) if event.operation.value != 3 else None
-            self.gui.broadcast(
-                _GuiCoreContext._CORE_CHANGED_NAME,
-                {"scenario": event.entity_id if scenario else True},
-            )
-        elif event.entity_type == EventEntityType.SEQUENCE and event.entity_id:  # TODO import EventOperation
-            sequence = core_get(event.entity_id) if event.operation.value != 3 else None
-            if sequence:
-                if hasattr(sequence, "parent_ids") and sequence.parent_ids:
-                    self.gui.broadcast(
-                        _GuiCoreContext._CORE_CHANGED_NAME, {"scenario": [x for x in sequence.parent_ids]}
-                    )
-        elif event.entity_type == EventEntityType.JOB:
-            with self.lock:
-                self.jobs_list = None
-            self.gui.broadcast(_GuiCoreContext._CORE_CHANGED_NAME, {"jobs": True})
-        elif event.entity_type == EventEntityType.DATA_NODE:
-            with self.lock:
-                self.data_nodes_by_owner = None
-            self.gui.broadcast(
-                _GuiCoreContext._CORE_CHANGED_NAME,
-                {"datanode": event.entity_id if event.operation.value != 3 else True},
-            )
-
-    def scenario_adapter(self, data):
-        if hasattr(data, "id") and core_get(data.id) is not None:
-            if self.scenario_by_cycle and isinstance(data, Cycle):
-                return (
-                    data.id,
-                    data.get_simple_label(),
-                    self.scenario_by_cycle.get(data),
-                    _EntityType.CYCLE.value,
-                    False,
-                )
-            elif isinstance(data, Scenario):
-                return (data.id, data.get_simple_label(), None, _EntityType.SCENARIO.value, data.is_primary)
-        return None
-
-    def get_scenarios(self):
-        cycles_scenarios = []
-        with self.lock:
-            if self.scenario_by_cycle is None:
-                self.scenario_by_cycle = get_cycles_scenarios()
-            for cycle, scenarios in self.scenario_by_cycle.items():
-                if cycle is None:
-                    cycles_scenarios.extend(scenarios)
-                else:
-                    cycles_scenarios.append(cycle)
-        return cycles_scenarios
-
-    def select_scenario(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) == 0:
-            return
-        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ID_VAR, args[0])
-
-    def get_scenario_by_id(self, id: str) -> t.Optional[Scenario]:
-        if not id:
-            return None
-        try:
-            return core_get(id)
-        except Exception:
-            return None
-
-    def get_scenario_configs(self):
-        with self.lock:
-            if self.scenario_configs is None:
-                configs = Config.scenarios
-                if isinstance(configs, dict):
-                    self.scenario_configs = [(id, f"{c.id}") for id, c in configs.items() if id != "default"]
-            return self.scenario_configs
-
-    def crud_scenario(self, state: State, id: str, user_action: str, payload: t.Dict[str, str]):  # noqa: C901
-        args = payload.get("args")
-        if (
-            args is None
-            or not isinstance(args, list)
-            or len(args) < 3
-            or not isinstance(args[0], bool)
-            or not isinstance(args[1], bool)
-            or not isinstance(args[2], dict)
-        ):
-            return
-        update = args[0]
-        delete = args[1]
-        data = args[2]
-        scenario = None
-
-        name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
-        if update:
-            scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-            if delete:
-                try:
-                    core_delete(scenario_id)
-                except Exception as e:
-                    state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error deleting Scenario. {e}")
-            else:
-                scenario = core_get(scenario_id)
-        else:
-            config_id = data.get(_GuiCoreContext.__PROP_CONFIG_ID)
-            scenario_config = Config.scenarios.get(config_id)
-            if scenario_config is None:
-                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid configuration id ({config_id})")
-                return
-            date_str = data.get(_GuiCoreContext.__PROP_DATE)
-            try:
-                date = parser.parse(date_str) if isinstance(date_str, str) else None
-            except Exception as e:
-                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid date ({date_str}).{e}")
-                return
-            try:
-                gui: Gui = state._gui
-                on_creation = args[3] if len(args) > 3 and isinstance(args[3], str) else None
-                on_creation_function = gui._get_user_function(on_creation) if on_creation else None
-                if callable(on_creation_function):
-                    try:
-                        res = gui._call_function_with_state(
-                            on_creation_function,
-                            [
-                                id,
-                                on_creation,
-                                {
-                                    "config": scenario_config,
-                                    "date": date,
-                                    "label": name,
-                                    "properties": {
-                                        v.get("key"): v.get("value") for v in data.get("properties", dict())
-                                    },
-                                },
-                            ],
-                        )
-                        if isinstance(res, Scenario):
-                            # everything's fine
-                            state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
-                            return
-                        if res:
-                            # do not create
-                            state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"{res}")
-                            return
-                    except Exception as e:  # pragma: no cover
-                        if not gui._call_on_exception(on_creation, e):
-                            _warn(f"on_creation(): Exception raised in '{on_creation}()':\n{e}")
-                else:
-                    _warn(f"on_creation(): '{on_creation}' is not a function.")
-                scenario = create_scenario(scenario_config, date, name)
-            except Exception as e:
-                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
-        if scenario:
-            with scenario as sc:
-                sc.properties[_GuiCoreContext.__PROP_ENTITY_NAME] = name
-                if props := data.get("properties"):
-                    try:
-                        new_keys = [prop.get("key") for prop in props]
-                        for key in t.cast(dict, sc.properties).keys():
-                            if key and key not in _GuiCoreContext.__ENTITY_PROPS and key not in new_keys:
-                                t.cast(dict, sc.properties).pop(key, None)
-                        for prop in props:
-                            key = prop.get("key")
-                            if key and key not in _GuiCoreContext.__ENTITY_PROPS:
-                                sc._properties[key] = prop.get("value")
-                        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
-                    except Exception as e:
-                        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
-
-    def edit_entity(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
-            return
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        entity: t.Union[Scenario, Sequence] = core_get(entity_id)
-        if entity:
-            try:
-                if isinstance(entity, Scenario):
-                    primary = data.get(_GuiCoreContext.__PROP_SCENARIO_PRIMARY)
-                    if primary is True:
-                        set_primary(entity)
-                self.__edit_properties(entity, data)
-                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, "")
-            except Exception as e:
-                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, f"Error updating Scenario. {e}")
-
-    def submit_entity(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
-            return
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        entity = core_get(entity_id)
-        if entity:
-            try:
-                core_submit(entity)
-                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, "")
-            except Exception as e:
-                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, f"Error submitting entity. {e}")
-
-    def __do_datanodes_tree(self):
-        if self.data_nodes_by_owner is None:
-            self.data_nodes_by_owner = defaultdict(list)
-            for dn in get_data_nodes():
-                self.data_nodes_by_owner[dn.owner_id].append(dn)
-
-    def get_datanodes_tree(self):
-        with self.lock:
-            self.__do_datanodes_tree()
-        return self.data_nodes_by_owner.get(None, []) + self.get_scenarios()
-
-    def data_node_adapter(self, data):
-        if data and hasattr(data, "id") and core_get(data.id) is not None:
-            if isinstance(data, DataNode):
-                return (data.id, data.get_simple_label(), None, _EntityType.DATANODE.value, False)
-            else:
-                with self.lock:
-                    self.__do_datanodes_tree()
-                    if self.data_nodes_by_owner:
-                        if isinstance(data, Cycle):
-                            return (
-                                data.id,
-                                data.get_simple_label(),
-                                self.data_nodes_by_owner[data.id] + self.scenario_by_cycle.get(data, []),
-                                _EntityType.CYCLE.value,
-                                False,
-                            )
-                        elif isinstance(data, Scenario):
-                            return (
-                                data.id,
-                                data.get_simple_label(),
-                                self.data_nodes_by_owner[data.id] + list(data.sequences.values()),
-                                _EntityType.SCENARIO.value,
-                                data.is_primary,
-                            )
-                        elif isinstance(data, Sequence):
-                            if datanodes := self.data_nodes_by_owner.get(data.id):
-                                return (data.id, data.get_simple_label(), datanodes, _EntityType.SEQUENCE.value, False)
-        return None
-
-    def get_jobs_list(self):
-        with self.lock:
-            if self.jobs_list is None:
-                self.jobs_list = get_jobs()
-            return self.jobs_list
-
-    def job_adapter(self, data):
-        if hasattr(data, "id") and core_get(data.id) is not None:
-            if isinstance(data, Job):
-                # entity = core_get(data.owner_id)
-                return (
-                    data.id,
-                    data.get_simple_label(),
-                    [],
-                    "",
-                    "",
-                    data.submit_id,
-                    data.creation_date,
-                    data.status.value,
-                )
-
-    def act_on_jobs(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
-            return
-        data = args[0]
-        job_ids = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        job_action = data.get(_GuiCoreContext.__ACTION)
-        if job_action and isinstance(job_ids, list):
-            errs = []
-            if job_action == "delete":
-                for job_id in job_ids:
-                    try:
-                        delete_job(core_get(job_id))
-                    except Exception as e:
-                        errs.append(f"Error deleting job. {e}")
-            elif job_action == "cancel":
-                for job_id in job_ids:
-                    try:
-                        cancel_job(job_id)
-                    except Exception as e:
-                        errs.append(f"Error canceling job. {e}")
-            state.assign(_GuiCoreContext._JOB_SELECTOR_ERROR_VAR, "<br/>".join(errs) if errs else "")
-
-    def edit_data_node(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
-            return
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        entity: DataNode = core_get(entity_id)
-        if isinstance(entity, DataNode):
-            try:
-                self.__edit_properties(entity, data)
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
-            except Exception as e:
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode. {e}")
-
-    def __edit_properties(self, entity: t.Union[Scenario, Sequence, DataNode], data: t.Dict[str, str]):
-        with entity as ent:
-            if isinstance(ent, Scenario):
-                tags = data.get(_GuiCoreContext.__PROP_SCENARIO_TAGS)
-                if isinstance(tags, (list, tuple)):
-                    ent.tags = {t for t in tags}
-            name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
-            if isinstance(name, str):
-                ent.properties[_GuiCoreContext.__PROP_ENTITY_NAME] = name
-            props = data.get("properties")
-            if isinstance(props, (list, tuple)):
-                for prop in props:
-                    key = prop.get("key")
-                    if key and key not in _GuiCoreContext.__ENTITY_PROPS:
-                        ent.properties[key] = prop.get("value")
-            deleted_props = data.get("deleted_properties")
-            if isinstance(deleted_props, (list, tuple)):
-                for prop in deleted_props:
-                    key = prop.get("key")
-                    if key and key not in _GuiCoreContext.__ENTITY_PROPS:
-                        ent.properties.pop(key, None)
-
-    def get_scenarios_for_owner(self, owner_id: str):
-        cycles_scenarios: t.List[t.Union[Scenario, Cycle]] = []
-        with self.lock:
-            if self.scenario_by_cycle is None:
-                self.scenario_by_cycle = get_cycles_scenarios()
-            if owner_id:
-                if owner_id == "GLOBAL":
-                    for cycle, scenarios in self.scenario_by_cycle.items():
-                        if cycle is None:
-                            cycles_scenarios.extend(scenarios)
-                        else:
-                            cycles_scenarios.append(cycle)
-                else:
-                    entity = core_get(owner_id)
-                    if entity and (scenarios := self.scenario_by_cycle.get(entity)):
-                        cycles_scenarios.extend(scenarios)
-                    elif isinstance(entity, Scenario):
-                        cycles_scenarios.append(entity)
-        return cycles_scenarios
-
-    def get_data_node_history(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-        ):
-            res = []
-            for e in dn.edits:
-                job: Job = core_get(e.get("job_id")) if "job_id" in e else None
-                res.append(
-                    (
-                        e.get("timestamp"),
-                        job.id if job else e.get("writer_identifier", ""),
-                        f"Execution of task {job.task.get_simple_label()}."
-                        if job and job.task
-                        else e.get("comment", ""),
-                    )
-                )
-            return list(reversed(sorted(res, key=lambda r: r[0])))
-        return _DoNotUpdate()
-
-    def get_data_node_data(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-        ):
-            if dn.is_ready_for_reading:
-                if isinstance(dn, _AbstractTabularDataNode):
-                    return (None, None, True, None)
-                try:
-                    value = dn.read()
-                    if isinstance(value, (pd.DataFrame, pd.Series)):
-                        return (None, None, True, None)
-                    return (
-                        value,
-                        "date"
-                        if "date" in type(value).__name__
-                        else type(value).__name__
-                        if isinstance(value, Number)
-                        else None,
-                        None,
-                        None,
-                    )
-                except Exception as e:
-                    return (None, None, None, f"read data_node: {e}")
-            return (None, None, None, f"Data unavailable for {dn.get_simple_label()}")
-        return _DoNotUpdate()
-
-    def update_data(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
-            return
-        data = args[0]
-        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
-        entity: DataNode = core_get(entity_id)
-        if isinstance(entity, DataNode):
-            try:
-                entity.write(
-                    parser.parse(data.get("value"))
-                    if data.get("type") == "date"
-                    else int(data.get("value"))
-                    if data.get("type") == "int"
-                    else float(data.get("value"))
-                    if data.get("type") == "float"
-                    else data.get("value"),
-                    comment=data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT),
-                )
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
-            except Exception as e:
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode value. {e}")
-            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, entity_id)  # this will update the data value
-
-    def tabular_data_edit(self, state: State, var_name: str, action: str, payload: dict):
-        user_data = payload.get("user_data", {})
-        dn_id = user_data.get("dn_id")
-        datanode = core_get(dn_id) if dn_id else None
-        if isinstance(datanode, DataNode):
-            try:
-                idx = payload.get("index")
-                col = payload.get("col")
-                tz = payload.get("tz")
-                val = (
-                    parser.parse(str(payload.get("value"))).astimezone(zoneinfo.ZoneInfo(tz)).replace(tzinfo=None)
-                    if tz is not None
-                    else payload.get("value")
-                )
-                # user_value = payload.get("user_value")
-                data = self.__read_tabular_data(datanode)
-                data.at[idx, col] = val
-                datanode.write(data, comment=user_data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT))
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
-            except Exception as e:
-                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode tabular value. {e}")
-        setattr(state, _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, dn_id)
-
-    def __read_tabular_data(self, datanode: DataNode):
-        return datanode.read()
-
-    def get_data_node_tabular_data(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                return self.__read_tabular_data(dn)
-            except Exception:
-                return None
-        return None
-
-    def get_data_node_tabular_columns(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                return self.gui._tbl_cols(
-                    True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=self.__read_tabular_data(dn)
-                )
-            except Exception:
-                return None
-        return None
-
-    def get_data_node_chart_config(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-            and dn.is_ready_for_reading
-        ):
-            try:
-                return self.gui._chart_conf(
-                    True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=self.__read_tabular_data(dn)
-                )
-            except Exception:
-                return None
-        return None
-
-    def select_id(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) == 0 and isinstance(args[0], dict):
-            return
-        data = args[0]
-        if owner_id := data.get("owner_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR, owner_id)
-        elif history_id := data.get("history_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR, history_id)
-        elif data_id := data.get("data_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, data_id)
-        elif chart_id := data.get("chart_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR, chart_id)
+from ._adapters import _GuiCoreDatanodeAdapter, _GuiCoreScenarioAdapter, _GuiCoreScenarioDagAdapter
+from ._context import _GuiCoreContext
 
 
 class _GuiCore(ElementLibrary):
@@ -891,10 +186,10 @@ class _GuiCore(ElementLibrary):
                 "id": ElementProperty(PropertyType.string),
                 "class_name": ElementProperty(PropertyType.dynamic_string),
                 "value": ElementProperty(PropertyType.lov_value),
-                "show_job_id": ElementProperty(PropertyType.boolean, True),
-                "show_entity_label": ElementProperty(PropertyType.boolean, True),
-                "show_entity_id": ElementProperty(PropertyType.boolean, False),
-                "show_submit_id": ElementProperty(PropertyType.boolean, False),
+                "show_id": ElementProperty(PropertyType.boolean, True),
+                "show_submitted_label": ElementProperty(PropertyType.boolean, True),
+                "show_submitted_id": ElementProperty(PropertyType.boolean, False),
+                "show_submission_id": ElementProperty(PropertyType.boolean, False),
                 "show_date": ElementProperty(PropertyType.boolean, True),
                 "show_cancel": ElementProperty(PropertyType.boolean, True),
                 "show_delete": ElementProperty(PropertyType.boolean, True),

+ 149 - 0
src/taipy/gui_core/_adapters.py

@@ -0,0 +1,149 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import typing as t
+from enum import Enum
+
+from taipy.core import Cycle, DataNode, Job, Scenario, Sequence
+from taipy.core import get as core_get
+from taipy.core import is_deletable, is_promotable, is_submittable
+from taipy.gui.gui import _DoNotUpdate
+from taipy.gui.utils import _TaipyBase
+
+
+# prevent gui from trying to push scenario instances to the front-end
+class _GCDoNotUpdate(_DoNotUpdate):
+    def __repr__(self):
+        return self.get_label() if hasattr(self, "get_label") else super().__repr__()
+
+
+Scenario.__bases__ += (_GCDoNotUpdate,)
+Sequence.__bases__ += (_GCDoNotUpdate,)
+DataNode.__bases__ += (_GCDoNotUpdate,)
+Job.__bases__ += (_GCDoNotUpdate,)
+
+
+class _EntityType(Enum):
+    CYCLE = 0
+    SCENARIO = 1
+    SEQUENCE = 2
+    DATANODE = 3
+
+
+class _GuiCoreScenarioAdapter(_TaipyBase):
+    __INNER_PROPS = ["name"]
+
+    def get(self):
+        data = super().get()
+        if isinstance(data, Scenario):
+            scenario = core_get(data.id)
+            if scenario:
+                return [
+                    scenario.id,
+                    scenario.is_primary,
+                    scenario.config_id,
+                    scenario.cycle.get_simple_label() if scenario.cycle else "",
+                    scenario.get_simple_label(),
+                    list(scenario.tags) if scenario.tags else [],
+                    [(k, v) for k, v in scenario.properties.items() if k not in _GuiCoreScenarioAdapter.__INNER_PROPS]
+                    if scenario.properties
+                    else [],
+                    [(p.id, p.get_simple_label(), is_submittable(p)) for p in scenario.sequences.values()]
+                    if scenario.sequences
+                    else [],
+                    list(scenario.properties.get("authorized_tags", [])) if scenario.properties else [],
+                    is_deletable(scenario),  # deletable
+                    is_promotable(scenario),
+                    is_submittable(scenario),
+                ]
+        return None
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "Sc"
+
+
+class _GuiCoreScenarioDagAdapter(_TaipyBase):
+    @staticmethod
+    def get_entity_type(node: t.Any):
+        return DataNode.__name__ if isinstance(node.entity, DataNode) else node.type
+
+    def get(self):
+        data = super().get()
+        if isinstance(data, Scenario):
+            scenario = core_get(data.id)
+            if scenario:
+                dag = data._get_dag()
+                nodes = dict()
+                for id, node in dag.nodes.items():
+                    entityType = _GuiCoreScenarioDagAdapter.get_entity_type(node)
+                    cat = nodes.get(entityType)
+                    if cat is None:
+                        cat = dict()
+                        nodes[entityType] = cat
+                    cat[id] = {
+                        "name": node.entity.get_simple_label(),
+                        "type": node.entity.storage_type() if hasattr(node.entity, "storage_type") else None,
+                    }
+                return [
+                    data.id,
+                    nodes,
+                    [
+                        (
+                            _GuiCoreScenarioDagAdapter.get_entity_type(e.src),
+                            e.src.entity.id,
+                            _GuiCoreScenarioDagAdapter.get_entity_type(e.dest),
+                            e.dest.entity.id,
+                        )
+                        for e in dag.edges
+                    ],
+                ]
+        return None
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "ScG"
+
+
+class _GuiCoreDatanodeAdapter(_TaipyBase):
+    __INNER_PROPS = ["name"]
+
+    def get(self):
+        data = super().get()
+        if isinstance(data, DataNode):
+            datanode = core_get(data.id)
+            if datanode:
+                owner = core_get(datanode.owner_id) if datanode.owner_id else None
+                return [
+                    datanode.id,
+                    datanode.storage_type() if hasattr(datanode, "storage_type") else "",
+                    datanode.config_id,
+                    f"{datanode.last_edit_date}" if datanode.last_edit_date else "",
+                    f"{datanode.expiration_date}" if datanode.last_edit_date else "",
+                    datanode.get_simple_label(),
+                    datanode.owner_id or "",
+                    owner.get_simple_label() if owner else "GLOBAL",
+                    _EntityType.CYCLE.value
+                    if isinstance(owner, Cycle)
+                    else _EntityType.SCENARIO.value
+                    if isinstance(owner, Scenario)
+                    else -1,
+                    [
+                        (k, f"{v}")
+                        for k, v in datanode._get_user_properties().items()
+                        if k not in _GuiCoreDatanodeAdapter.__INNER_PROPS
+                    ],
+                ]
+        return None
+
+    @staticmethod
+    def get_hash():
+        return _TaipyBase._HOLDER_PREFIX + "Dn"

+ 583 - 0
src/taipy/gui_core/_context.py

@@ -0,0 +1,583 @@
+# Copyright 2023 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import json
+import typing as t
+from collections import defaultdict
+from numbers import Number
+from threading import Lock
+
+try:
+    import zoneinfo
+except ImportError:
+    from backports import zoneinfo  # type: ignore[no-redef]
+
+import pandas as pd
+from dateutil import parser
+
+from taipy.config import Config
+from taipy.core import Cycle, DataNode, Job, Scenario, Sequence, cancel_job, create_scenario
+from taipy.core import delete as core_delete
+from taipy.core import delete_job
+from taipy.core import get as core_get
+from taipy.core import get_cycles_scenarios, get_data_nodes, get_jobs, set_primary
+from taipy.core import submit as core_submit
+from taipy.core.data._abstract_tabular import _AbstractTabularDataNode
+from taipy.core.notification import CoreEventConsumerBase, EventEntityType
+from taipy.core.notification.event import Event
+from taipy.core.notification.notifier import Notifier
+from taipy.gui import Gui, State
+from taipy.gui._warnings import _warn
+from taipy.gui.gui import _DoNotUpdate
+
+from ._adapters import _EntityType
+
+
+class _GuiCoreContext(CoreEventConsumerBase):
+    __PROP_ENTITY_ID = "id"
+    __PROP_ENTITY_COMMENT = "comment"
+    __PROP_CONFIG_ID = "config"
+    __PROP_DATE = "date"
+    __PROP_ENTITY_NAME = "name"
+    __PROP_SCENARIO_PRIMARY = "primary"
+    __PROP_SCENARIO_TAGS = "tags"
+    __ENTITY_PROPS = (__PROP_CONFIG_ID, __PROP_DATE, __PROP_ENTITY_NAME)
+    __ACTION = "action"
+    _CORE_CHANGED_NAME = "core_changed"
+    _SCENARIO_SELECTOR_ERROR_VAR = "gui_core_sc_error"
+    _SCENARIO_SELECTOR_ID_VAR = "gui_core_sc_id"
+    _SCENARIO_VIZ_ERROR_VAR = "gui_core_sv_error"
+    _JOB_SELECTOR_ERROR_VAR = "gui_core_js_error"
+    _DATANODE_VIZ_ERROR_VAR = "gui_core_dv_error"
+    _DATANODE_VIZ_OWNER_ID_VAR = "gui_core_dv_owner_id"
+    _DATANODE_VIZ_HISTORY_ID_VAR = "gui_core_dv_history_id"
+    _DATANODE_VIZ_DATA_ID_VAR = "gui_core_dv_data_id"
+    _DATANODE_VIZ_DATA_CHART_ID_VAR = "gui_core_dv_data_chart_id"
+    _DATANODE_VIZ_DATA_NODE_PROP = "data_node"
+
+    def __init__(self, gui: Gui) -> None:
+        self.gui = gui
+        self.scenario_by_cycle: t.Optional[t.Dict[t.Optional[Cycle], t.List[Scenario]]] = None
+        self.data_nodes_by_owner: t.Optional[t.Dict[t.Optional[str], DataNode]] = None
+        self.scenario_configs: t.Optional[t.List[t.Tuple[str, str]]] = None
+        self.jobs_list: t.Optional[t.List[Job]] = None
+        # register to taipy core notification
+        reg_id, reg_queue = Notifier.register()
+        self.lock = Lock()
+        super().__init__(reg_id, reg_queue)
+        self.start()
+
+    def process_event(self, event: Event):
+        if event.entity_type == EventEntityType.SCENARIO:
+            with self.lock:
+                self.scenario_by_cycle = None
+                self.data_nodes_by_owner = None
+            scenario = core_get(event.entity_id) if event.operation.value != 3 else None
+            self.gui.broadcast(
+                _GuiCoreContext._CORE_CHANGED_NAME,
+                {"scenario": event.entity_id if scenario else True},
+            )
+        elif event.entity_type == EventEntityType.SEQUENCE and event.entity_id:  # TODO import EventOperation
+            sequence = core_get(event.entity_id) if event.operation.value != 3 else None
+            if sequence:
+                if hasattr(sequence, "parent_ids") and sequence.parent_ids:
+                    self.gui.broadcast(
+                        _GuiCoreContext._CORE_CHANGED_NAME, {"scenario": [x for x in sequence.parent_ids]}
+                    )
+        elif event.entity_type == EventEntityType.JOB:
+            with self.lock:
+                self.jobs_list = None
+            self.gui.broadcast(_GuiCoreContext._CORE_CHANGED_NAME, {"jobs": True})
+        elif event.entity_type == EventEntityType.DATA_NODE:
+            with self.lock:
+                self.data_nodes_by_owner = None
+            self.gui.broadcast(
+                _GuiCoreContext._CORE_CHANGED_NAME,
+                {"datanode": event.entity_id if event.operation.value != 3 else True},
+            )
+
+    def scenario_adapter(self, data):
+        if hasattr(data, "id") and core_get(data.id) is not None:
+            if self.scenario_by_cycle and isinstance(data, Cycle):
+                return (
+                    data.id,
+                    data.get_simple_label(),
+                    self.scenario_by_cycle.get(data),
+                    _EntityType.CYCLE.value,
+                    False,
+                )
+            elif isinstance(data, Scenario):
+                return (data.id, data.get_simple_label(), None, _EntityType.SCENARIO.value, data.is_primary)
+        return None
+
+    def get_scenarios(self):
+        cycles_scenarios = []
+        with self.lock:
+            if self.scenario_by_cycle is None:
+                self.scenario_by_cycle = get_cycles_scenarios()
+            for cycle, scenarios in self.scenario_by_cycle.items():
+                if cycle is None:
+                    cycles_scenarios.extend(scenarios)
+                else:
+                    cycles_scenarios.append(cycle)
+        return cycles_scenarios
+
+    def select_scenario(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) == 0:
+            return
+        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ID_VAR, args[0])
+
+    def get_scenario_by_id(self, id: str) -> t.Optional[Scenario]:
+        if not id:
+            return None
+        try:
+            return core_get(id)
+        except Exception:
+            return None
+
+    def get_scenario_configs(self):
+        with self.lock:
+            if self.scenario_configs is None:
+                configs = Config.scenarios
+                if isinstance(configs, dict):
+                    self.scenario_configs = [(id, f"{c.id}") for id, c in configs.items() if id != "default"]
+            return self.scenario_configs
+
+    def crud_scenario(self, state: State, id: str, user_action: str, payload: t.Dict[str, str]):  # noqa: C901
+        args = payload.get("args")
+        if (
+            args is None
+            or not isinstance(args, list)
+            or len(args) < 3
+            or not isinstance(args[0], bool)
+            or not isinstance(args[1], bool)
+            or not isinstance(args[2], dict)
+        ):
+            return
+        update = args[0]
+        delete = args[1]
+        data = args[2]
+        scenario = None
+
+        name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
+        if update:
+            scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+            if delete:
+                try:
+                    core_delete(scenario_id)
+                except Exception as e:
+                    state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error deleting Scenario. {e}")
+            else:
+                scenario = core_get(scenario_id)
+        else:
+            config_id = data.get(_GuiCoreContext.__PROP_CONFIG_ID)
+            scenario_config = Config.scenarios.get(config_id)
+            if scenario_config is None:
+                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid configuration id ({config_id})")
+                return
+            date_str = data.get(_GuiCoreContext.__PROP_DATE)
+            try:
+                date = parser.parse(date_str) if isinstance(date_str, str) else None
+            except Exception as e:
+                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid date ({date_str}).{e}")
+                return
+            try:
+                gui: Gui = state._gui
+                on_creation = args[3] if len(args) > 3 and isinstance(args[3], str) else None
+                on_creation_function = gui._get_user_function(on_creation) if on_creation else None
+                if callable(on_creation_function):
+                    try:
+                        res = gui._call_function_with_state(
+                            on_creation_function,
+                            [
+                                id,
+                                on_creation,
+                                {
+                                    "config": scenario_config,
+                                    "date": date,
+                                    "label": name,
+                                    "properties": {
+                                        v.get("key"): v.get("value") for v in data.get("properties", dict())
+                                    },
+                                },
+                            ],
+                        )
+                        if isinstance(res, Scenario):
+                            # everything's fine
+                            state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
+                            return
+                        if res:
+                            # do not create
+                            state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"{res}")
+                            return
+                    except Exception as e:  # pragma: no cover
+                        if not gui._call_on_exception(on_creation, e):
+                            _warn(f"on_creation(): Exception raised in '{on_creation}()':\n{e}")
+                else:
+                    _warn(f"on_creation(): '{on_creation}' is not a function.")
+                scenario = create_scenario(scenario_config, date, name)
+            except Exception as e:
+                state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
+        if scenario:
+            with scenario as sc:
+                sc.properties[_GuiCoreContext.__PROP_ENTITY_NAME] = name
+                if props := data.get("properties"):
+                    try:
+                        new_keys = [prop.get("key") for prop in props]
+                        for key in t.cast(dict, sc.properties).keys():
+                            if key and key not in _GuiCoreContext.__ENTITY_PROPS and key not in new_keys:
+                                t.cast(dict, sc.properties).pop(key, None)
+                        for prop in props:
+                            key = prop.get("key")
+                            if key and key not in _GuiCoreContext.__ENTITY_PROPS:
+                                sc._properties[key] = prop.get("value")
+                        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
+                    except Exception as e:
+                        state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
+
+    def edit_entity(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity: t.Union[Scenario, Sequence] = core_get(entity_id)
+        if entity:
+            try:
+                if isinstance(entity, Scenario):
+                    primary = data.get(_GuiCoreContext.__PROP_SCENARIO_PRIMARY)
+                    if primary is True:
+                        set_primary(entity)
+                self.__edit_properties(entity, data)
+                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, "")
+            except Exception as e:
+                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, f"Error updating Scenario. {e}")
+
+    def submit_entity(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity = core_get(entity_id)
+        if entity:
+            try:
+                core_submit(entity)
+                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, "")
+            except Exception as e:
+                state.assign(_GuiCoreContext._SCENARIO_VIZ_ERROR_VAR, f"Error submitting entity. {e}")
+
+    def __do_datanodes_tree(self):
+        if self.data_nodes_by_owner is None:
+            self.data_nodes_by_owner = defaultdict(list)
+            for dn in get_data_nodes():
+                self.data_nodes_by_owner[dn.owner_id].append(dn)
+
+    def get_datanodes_tree(self):
+        with self.lock:
+            self.__do_datanodes_tree()
+        return self.data_nodes_by_owner.get(None, []) + self.get_scenarios()
+
+    def data_node_adapter(self, data):
+        if data and hasattr(data, "id") and core_get(data.id) is not None:
+            if isinstance(data, DataNode):
+                return (data.id, data.get_simple_label(), None, _EntityType.DATANODE.value, False)
+            else:
+                with self.lock:
+                    self.__do_datanodes_tree()
+                    if self.data_nodes_by_owner:
+                        if isinstance(data, Cycle):
+                            return (
+                                data.id,
+                                data.get_simple_label(),
+                                self.data_nodes_by_owner[data.id] + self.scenario_by_cycle.get(data, []),
+                                _EntityType.CYCLE.value,
+                                False,
+                            )
+                        elif isinstance(data, Scenario):
+                            return (
+                                data.id,
+                                data.get_simple_label(),
+                                self.data_nodes_by_owner[data.id] + list(data.sequences.values()),
+                                _EntityType.SCENARIO.value,
+                                data.is_primary,
+                            )
+                        elif isinstance(data, Sequence):
+                            if datanodes := self.data_nodes_by_owner.get(data.id):
+                                return (data.id, data.get_simple_label(), datanodes, _EntityType.SEQUENCE.value, False)
+        return None
+
+    def get_jobs_list(self):
+        with self.lock:
+            if self.jobs_list is None:
+                self.jobs_list = get_jobs()
+            return self.jobs_list
+
+    def job_adapter(self, data):
+        if hasattr(data, "id") and core_get(data.id) is not None:
+            if isinstance(data, Job):
+                entity = core_get(data.owner_id)
+                return (
+                    data.id,
+                    data.get_simple_label(),
+                    [],
+                    entity.get_simple_label() if entity else "",
+                    entity.id if entity else "",
+                    data.submit_id,
+                    data.creation_date,
+                    data.status.value,
+                )
+
+    def act_on_jobs(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        job_ids = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        job_action = data.get(_GuiCoreContext.__ACTION)
+        if job_action and isinstance(job_ids, list):
+            errs = []
+            if job_action == "delete":
+                for job_id in job_ids:
+                    try:
+                        delete_job(core_get(job_id))
+                    except Exception as e:
+                        errs.append(f"Error deleting job. {e}")
+            elif job_action == "cancel":
+                for job_id in job_ids:
+                    try:
+                        cancel_job(job_id)
+                    except Exception as e:
+                        errs.append(f"Error canceling job. {e}")
+            state.assign(_GuiCoreContext._JOB_SELECTOR_ERROR_VAR, "<br/>".join(errs) if errs else "")
+
+    def edit_data_node(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity: DataNode = core_get(entity_id)
+        if isinstance(entity, DataNode):
+            try:
+                self.__edit_properties(entity, data)
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
+            except Exception as e:
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode. {e}")
+
+    def __edit_properties(self, entity: t.Union[Scenario, Sequence, DataNode], data: t.Dict[str, str]):
+        with entity as ent:
+            if isinstance(ent, Scenario):
+                tags = data.get(_GuiCoreContext.__PROP_SCENARIO_TAGS)
+                if isinstance(tags, (list, tuple)):
+                    ent.tags = {t for t in tags}
+            name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
+            if isinstance(name, str):
+                ent.properties[_GuiCoreContext.__PROP_ENTITY_NAME] = name
+            props = data.get("properties")
+            if isinstance(props, (list, tuple)):
+                for prop in props:
+                    key = prop.get("key")
+                    if key and key not in _GuiCoreContext.__ENTITY_PROPS:
+                        ent.properties[key] = prop.get("value")
+            deleted_props = data.get("deleted_properties")
+            if isinstance(deleted_props, (list, tuple)):
+                for prop in deleted_props:
+                    key = prop.get("key")
+                    if key and key not in _GuiCoreContext.__ENTITY_PROPS:
+                        ent.properties.pop(key, None)
+
+    def get_scenarios_for_owner(self, owner_id: str):
+        cycles_scenarios: t.List[t.Union[Scenario, Cycle]] = []
+        with self.lock:
+            if self.scenario_by_cycle is None:
+                self.scenario_by_cycle = get_cycles_scenarios()
+            if owner_id:
+                if owner_id == "GLOBAL":
+                    for cycle, scenarios in self.scenario_by_cycle.items():
+                        if cycle is None:
+                            cycles_scenarios.extend(scenarios)
+                        else:
+                            cycles_scenarios.append(cycle)
+                else:
+                    entity = core_get(owner_id)
+                    if entity and (scenarios := self.scenario_by_cycle.get(entity)):
+                        cycles_scenarios.extend(scenarios)
+                    elif isinstance(entity, Scenario):
+                        cycles_scenarios.append(entity)
+        return cycles_scenarios
+
+    def get_data_node_history(self, datanode: DataNode, id: str):
+        if (
+            id
+            and isinstance(datanode, DataNode)
+            and id == datanode.id
+            and (dn := core_get(id))
+            and isinstance(dn, DataNode)
+        ):
+            res = []
+            for e in dn.edits:
+                job: Job = core_get(e.get("job_id")) if "job_id" in e else None
+                res.append(
+                    (
+                        e.get("timestamp"),
+                        job.id if job else e.get("writer_identifier", ""),
+                        f"Execution of task {job.task.get_simple_label()}."
+                        if job and job.task
+                        else e.get("comment", ""),
+                    )
+                )
+            return list(reversed(sorted(res, key=lambda r: r[0])))
+        return _DoNotUpdate()
+
+    def get_data_node_data(self, datanode: DataNode, id: str):
+        if (
+            id
+            and isinstance(datanode, DataNode)
+            and id == datanode.id
+            and (dn := core_get(id))
+            and isinstance(dn, DataNode)
+        ):
+            if dn.is_ready_for_reading:
+                if isinstance(dn, _AbstractTabularDataNode):
+                    return (None, None, True, None)
+                try:
+                    value = dn.read()
+                    if isinstance(value, (pd.DataFrame, pd.Series)):
+                        return (None, None, True, None)
+                    return (
+                        value,
+                        "date"
+                        if "date" in type(value).__name__
+                        else type(value).__name__
+                        if isinstance(value, Number)
+                        else None,
+                        None,
+                        None,
+                    )
+                except Exception as e:
+                    return (None, None, None, f"read data_node: {e}")
+            return (None, None, None, f"Data unavailable for {dn.get_simple_label()}")
+        return _DoNotUpdate()
+
+    def update_data(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        entity_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        entity: DataNode = core_get(entity_id)
+        if isinstance(entity, DataNode):
+            try:
+                entity.write(
+                    parser.parse(data.get("value"))
+                    if data.get("type") == "date"
+                    else int(data.get("value"))
+                    if data.get("type") == "int"
+                    else float(data.get("value"))
+                    if data.get("type") == "float"
+                    else data.get("value"),
+                    comment=data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT),
+                )
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
+            except Exception as e:
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode value. {e}")
+            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, entity_id)  # this will update the data value
+
+    def tabular_data_edit(self, state: State, var_name: str, action: str, payload: dict):
+        user_data = payload.get("user_data", {})
+        dn_id = user_data.get("dn_id")
+        datanode = core_get(dn_id) if dn_id else None
+        if isinstance(datanode, DataNode):
+            try:
+                idx = payload.get("index")
+                col = payload.get("col")
+                tz = payload.get("tz")
+                val = (
+                    parser.parse(str(payload.get("value"))).astimezone(zoneinfo.ZoneInfo(tz)).replace(tzinfo=None)
+                    if tz is not None
+                    else payload.get("value")
+                )
+                # user_value = payload.get("user_value")
+                data = self.__read_tabular_data(datanode)
+                data.at[idx, col] = val
+                datanode.write(data, comment=user_data.get(_GuiCoreContext.__PROP_ENTITY_COMMENT))
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, "")
+            except Exception as e:
+                state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode tabular value. {e}")
+        setattr(state, _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, dn_id)
+
+    def __read_tabular_data(self, datanode: DataNode):
+        return datanode.read()
+
+    def get_data_node_tabular_data(self, datanode: DataNode, id: str):
+        if (
+            id
+            and isinstance(datanode, DataNode)
+            and id == datanode.id
+            and (dn := core_get(id))
+            and isinstance(dn, DataNode)
+            and dn.is_ready_for_reading
+        ):
+            try:
+                return self.__read_tabular_data(dn)
+            except Exception:
+                return None
+        return None
+
+    def get_data_node_tabular_columns(self, datanode: DataNode, id: str):
+        if (
+            id
+            and isinstance(datanode, DataNode)
+            and id == datanode.id
+            and (dn := core_get(id))
+            and isinstance(dn, DataNode)
+            and dn.is_ready_for_reading
+        ):
+            try:
+                return self.gui._tbl_cols(
+                    True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=self.__read_tabular_data(dn)
+                )
+            except Exception:
+                return None
+        return None
+
+    def get_data_node_chart_config(self, datanode: DataNode, id: str):
+        if (
+            id
+            and isinstance(datanode, DataNode)
+            and id == datanode.id
+            and (dn := core_get(id))
+            and isinstance(dn, DataNode)
+            and dn.is_ready_for_reading
+        ):
+            try:
+                return self.gui._chart_conf(
+                    True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=self.__read_tabular_data(dn)
+                )
+            except Exception:
+                return None
+        return None
+
+    def select_id(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) == 0 and isinstance(args[0], dict):
+            return
+        data = args[0]
+        if owner_id := data.get("owner_id"):
+            state.assign(_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR, owner_id)
+        elif history_id := data.get("history_id"):
+            state.assign(_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR, history_id)
+        elif data_id := data.get("data_id"):
+            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, data_id)
+        elif chart_id := data.get("chart_id"):
+            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR, chart_id)

+ 4 - 4
src/taipy/gui_core/viselements.json

@@ -265,25 +265,25 @@
                         "doc": "Bound to the selected <code>Job^</code>(s), or None if there is none."
                     },
                     {
-                        "name": "show_job_id",
+                        "name": "show_id",
                         "type": "bool",
                         "default_value": "True",
                         "doc": "If False, the <code>Job^</code> id is not shown in the selector."
                     },
                     {
-                        "name": "show_entity_label",
+                        "name": "show_submitted_label",
                         "type": "bool",
                         "default_value": "True",
                         "doc": "If False, the <code>Scenario^</code> or <code>Sequence^</code> label is not shown in the selector."
                     },
                     {
-                        "name": "show_entity_id",
+                        "name": "show_submitted_id",
                         "type": "bool",
                         "default_value": "False",
                         "doc": "If True, the <code>Scenario^</code> or <code>Sequence^</code> id is shown in the selector."
                     },
                     {
-                        "name": "show_submit_id",
+                        "name": "show_submission_id",
                         "type": "bool",
                         "default_value": "False",
                         "doc": "If True, the submission id is shown in the selector."