Browse Source

#215 improve performances (#231)

* #215 improve performances
- manage scenario expandable/default_expanded

* mypy

* mypy

* back to expanded

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 year ago
parent
commit
38d78c1278
3 changed files with 75 additions and 76 deletions
  1. 9 7
      gui/src/ScenarioViewer.tsx
  2. 64 67
      src/taipy/gui_core/GuiCoreLib.py
  3. 2 2
      src/taipy/gui_core/viselements.json

+ 9 - 7
gui/src/ScenarioViewer.tsx

@@ -53,7 +53,6 @@ interface ScenarioViewerProps {
     id?: string;
     expandable?: boolean;
     expanded?: boolean;
-    defaultExpanded?: boolean;
     updateVarName?: string;
     defaultScenario?: string;
     scenario?: ScenarioFull | Array<ScenarioFull>;
@@ -229,6 +228,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
     const {
         id = "",
         expandable = true,
+        expanded = false,
         showConfig = false,
         showCycle = false,
         showDelete = true,
@@ -271,7 +271,6 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
     }, [props.scenario, props.defaultScenario]);
 
     const active = useDynamicProperty(props.active, props.defaultActive, true);
-    const expanded = useDynamicProperty(props.expanded, props.defaultExpanded, false);
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
 
     const [deleteDialog, setDeleteDialogOpen] = useState(false);
@@ -302,8 +301,11 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
     });
 
     // userExpanded
-    const [userExpanded, setUserExpanded] = useState(false);
-    const onExpand = useCallback((e: SyntheticEvent, exp: boolean) => setUserExpanded(exp), []);
+    const [userExpanded, setUserExpanded] = useState(isScenario && expanded);
+    const onExpand = useCallback(
+        (e: SyntheticEvent, expand: boolean) => expandable && setUserExpanded(expand),
+        [expandable]
+    );
 
     // submits
     const submitPipeline = useCallback(
@@ -463,8 +465,8 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                 }))
             );
         setLabel(scLabel);
-        isScenario || setUserExpanded(false);
-    }, [scTags, scProperties, scLabel, isScenario, showTags, showProperties]);
+        setUserExpanded(expanded && isScenario);
+    }, [scTags, scProperties, scLabel, isScenario, showTags, showProperties, expanded]);
 
     // Refresh on broadcast
     useEffect(() => {
@@ -478,7 +480,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
         <>
             <Box sx={MainBoxSx} id={id} onClick={onFocus} className={className}>
                 <Accordion
-                    defaultExpanded={expandable && expanded}
+                    defaultExpanded={expanded}
                     expanded={userExpanded}
                     onChange={onExpand}
                     disabled={!isScenario}

+ 64 - 67
src/taipy/gui_core/GuiCoreLib.py

@@ -10,6 +10,7 @@
 # specific language governing permissions and limitations under the License.
 
 import typing as t
+from collections import defaultdict
 from datetime import datetime
 from enum import Enum
 from threading import Lock
@@ -20,15 +21,7 @@ from taipy.config import Config
 from taipy.core import Cycle, DataNode, Pipeline, Scenario, create_scenario
 from taipy.core import delete as core_delete
 from taipy.core import get as core_get
-from taipy.core import (
-    get_cycles_scenarios,
-    get_data_nodes,
-    get_scenarios,
-    is_deletable,
-    is_promotable,
-    is_submittable,
-    set_primary,
-)
+from taipy.core import get_cycles_scenarios, get_data_nodes, is_deletable, is_promotable, is_submittable, set_primary
 from taipy.core import submit as core_submit
 from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event
@@ -146,8 +139,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def __init__(self, gui: Gui) -> None:
         self.gui = gui
-        self.scenarios_base_level: t.Optional[t.List[t.Union[Cycle, Scenario]]] = None
-        self.data_nodes_base_level: t.Optional[t.List[t.Union[Cycle, Scenario, Pipeline, DataNode]]] = None
+        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
         # register to taipy core notification
         reg_id, reg_queue = Notifier.register()
@@ -157,13 +150,14 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def process_event(self, event: Event):
         if event.entity_type == EventEntityType.SCENARIO:
-            self.scenarios_base_level = None
+            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},
             )
-            self.data_nodes_base_level = None
         elif event.entity_type == EventEntityType.PIPELINE and event.entity_id:  # TODO import EventOperation
             pipeline = core_get(event.entity_id) if event.operation.value != 3 else None
             if pipeline:
@@ -171,26 +165,39 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     self.gui.broadcast(
                         _GuiCoreContext._CORE_CHANGED_NAME, {"scenario": [x for x in pipeline.parent_ids]}
                     )
+        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},
+            )
 
-    @staticmethod
-    def scenario_adapter(data):
+    def scenario_adapter(self, data):
         if hasattr(data, "id") and core_get(data.id) is not None:
             if isinstance(data, Cycle):
-                return (data.id, data.get_simple_label(), get_scenarios(data), _EntityType.CYCLE.value, False)
+                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.scenarios_base_level is None:
-                self.scenarios_base_level = []
-                for cycle, scenarios in get_cycles_scenarios().items():
-                    if cycle is None:
-                        self.scenarios_base_level.extend(scenarios)
-                    else:
-                        self.scenarios_base_level.append(cycle)
-            return self.scenarios_base_level
+            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")
@@ -325,48 +332,37 @@ class _GuiCoreContext(CoreEventConsumerBase):
 
     def get_datanodes_tree(self):
         with self.lock:
-            if self.data_nodes_base_level is None:
-                self.data_nodes_base_level = _GuiCoreContext.__get_data_nodes()
-                for cycle, scenarios in get_cycles_scenarios().items():
-                    if cycle is None:
-                        self.data_nodes_base_level.extend(scenarios)
-                    else:
-                        self.data_nodes_base_level.append(cycle)
-            return self.data_nodes_base_level
-
-    @staticmethod
-    def __get_data_nodes(id: t.Optional[str] = None):
-        def from_parent(dn: DataNode):
-            if id is None and dn.owner_id is None:
-                return True
-            return False if id is None or dn.owner_id is None else dn.owner_id == id
+            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)
+        return self.get_scenarios()
 
-        return [x for x in get_data_nodes() if from_parent(x)]
-
-    @staticmethod
-    def data_node_adapter(data):
+    def data_node_adapter(self, data):
         if hasattr(data, "id") and core_get(data.id) is not None:
-            if isinstance(data, Cycle):
-                return (
-                    data.id,
-                    data.get_simple_label(),
-                    _GuiCoreContext.__get_data_nodes(data.id) + get_scenarios(data),
-                    _EntityType.CYCLE.value,
-                    False,
-                )
-            elif isinstance(data, Scenario):
-                return (
-                    data.id,
-                    data.get_simple_label(),
-                    _GuiCoreContext.__get_data_nodes(data.id) + [core_get(p) for p in data._pipelines],
-                    _EntityType.SCENARIO.value,
-                    data.is_primary,
-                )
-            elif isinstance(data, Pipeline):
-                if dn := _GuiCoreContext.__get_data_nodes(data.id):
-                    return (data.id, data.get_simple_label(), dn, _EntityType.PIPELINE.value, False)
-            elif isinstance(data, DataNode):
+            if isinstance(data, DataNode):
                 return (data.id, data.get_simple_label(), None, _EntityType.DATANODE.value, False)
+            else:
+                with self.lock:
+                    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.pipelines.values()),
+                            _EntityType.SCENARIO.value,
+                            data.is_primary,
+                        )
+                    elif isinstance(data, Pipeline):
+                        if datanodes := self.data_nodes_by_owner.get(data.id):
+                            return (data.id, data.get_simple_label(), datanodes, _EntityType.PIPELINE.value, False)
         return None
 
 
@@ -410,7 +406,7 @@ class _GuiCore(ElementLibrary):
                 "scenario": ElementProperty(_GuiCoreScenarioAdapter),
                 "active": ElementProperty(PropertyType.dynamic_boolean, True),
                 "expandable": ElementProperty(PropertyType.boolean, True),
-                "expanded": ElementProperty(PropertyType.dynamic_boolean, False),
+                "expanded": ElementProperty(PropertyType.boolean, False),
                 "show_submit": ElementProperty(PropertyType.boolean, True),
                 "show_delete": ElementProperty(PropertyType.boolean, True),
                 "show_config": ElementProperty(PropertyType.boolean, False),
@@ -473,9 +469,10 @@ class _GuiCore(ElementLibrary):
         return ["lib/taipy-gui-core.js"]
 
     def on_init(self, gui: Gui) -> t.Optional[t.Tuple[str, t.Any]]:
-        gui._add_adapter_for_type(_GuiCore.__SCENARIO_ADAPTER, _GuiCoreContext.scenario_adapter)
-        gui._add_adapter_for_type(_GuiCore.__DATANODE_ADAPTER, _GuiCoreContext.data_node_adapter)
-        return _GuiCore.__CTX_VAR_NAME, _GuiCoreContext(gui)
+        ctx = _GuiCoreContext(gui)
+        gui._add_adapter_for_type(_GuiCore.__SCENARIO_ADAPTER, ctx.scenario_adapter)
+        gui._add_adapter_for_type(_GuiCore.__DATANODE_ADAPTER, ctx.data_node_adapter)
+        return _GuiCore.__CTX_VAR_NAME, ctx
 
     def on_user_init(self, state: State):
         for var in [

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

@@ -86,9 +86,9 @@
           },
           {
             "name": "expanded",
-            "type": "dynamic(bool)",
+            "type": "bool",
             "default_value": "True",
-            "doc": "If True, the scenario visualizer is expanded, and its content is displayed.<br/>If False, the scenario visualizer is collapsed and only its name a <i>submit</i> button are visible."
+            "doc": "If True, when a valid scenario is selected, the scenario visualizer is expanded, and its content is displayed.<br/>If False, the scenario visualizer is collapsed and only its name a <i>submit</i> button are visible."
           },
           {
             "name": "show_submit",