ソースを参照

backport on_create (#232) (#254)

* #232 backport on_create

* #232 backport on_creation before|after

* #232 one on_creation is enough

* name => label

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 年間 前
コミット
02100933d8

+ 1 - 1
gui/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "taipy-gui-core",
   "name": "taipy-gui-core",
-  "version": "2.3.1",
+  "version": "2.4.0",
   "private": true,
   "private": true,
   "devDependencies": {
   "devDependencies": {
     "@types/react": "^18.0.15",
     "@types/react": "^18.0.15",

+ 5 - 4
gui/src/ScenarioSelector.tsx

@@ -96,6 +96,7 @@ interface ScenarioSelectorProps {
     scenarios?: Cycles | Scenarios;
     scenarios?: Cycles | Scenarios;
     onScenarioCrud: string;
     onScenarioCrud: string;
     onChange?: string;
     onChange?: string;
+    onCreation?: string;
     coreChanged?: Record<string, unknown>;
     coreChanged?: Record<string, unknown>;
     updateVars: string;
     updateVars: string;
     configs?: Array<[string, string]>;
     configs?: Array<[string, string]>;
@@ -305,7 +306,7 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
             values.properties = [...properties];
             values.properties = [...properties];
             setProperties([]);
             setProperties([]);
             submit(actionEdit, false, values);
             submit(actionEdit, false, values);
-            form.resetForm();
+            form.resetForm({ values: { ...emptyScenario, config: configs?.length === 1 ? configs[0][0] : "" } });
             close();
             close();
         },
         },
     });
     });
@@ -320,7 +321,7 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
                       date: scenario[ScFProps.creation_date],
                       date: scenario[ScFProps.creation_date],
                       properties: [],
                       properties: [],
                   }
                   }
-                : emptyScenario
+                : { ...emptyScenario, config: configs?.length === 1 ? configs[0][0] : "" }
         );
         );
         setProperties(
         setProperties(
             scenario ? scenario[ScFProps.properties].map(([k, v], i) => ({ id: i + "", key: k, value: v })) : []
             scenario ? scenario[ScFProps.properties].map(([k, v], i) => ({ id: i + "", key: k, value: v })) : []
@@ -594,13 +595,13 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
 
 
     const onSubmit = useCallback(
     const onSubmit = useCallback(
         (...values: unknown[]) => {
         (...values: unknown[]) => {
-            dispatch(createSendActionNameAction(id, module, props.onScenarioCrud, ...values));
+            dispatch(createSendActionNameAction(id, module, props.onScenarioCrud, ...values, props.onCreation));
             if (values.length > 1 && values[1]) {
             if (values.length > 1 && values[1]) {
                 // delete requested => unselect current node
                 // delete requested => unselect current node
                 unselect();
                 unselect();
             }
             }
         },
         },
-        [id, props.onScenarioCrud, dispatch, module, unselect]
+        [id, props.onScenarioCrud, dispatch, module, props.onCreation, unselect]
     );
     );
 
 
     // Refresh on broadcast
     // Refresh on broadcast

+ 41 - 3
src/taipy/gui_core/GuiCoreLib.py

@@ -34,6 +34,7 @@ from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event
 from taipy.core.notification.event import Event
 from taipy.core.notification.notifier import Notifier
 from taipy.core.notification.notifier import Notifier
 from taipy.gui import Gui, State
 from taipy.gui import Gui, State
+from taipy.gui._warnings import _warn
 from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
 from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
 from taipy.gui.gui import _DoNotUpdate
 from taipy.gui.gui import _DoNotUpdate
 from taipy.gui.utils import _TaipyBase
 from taipy.gui.utils import _TaipyBase
@@ -75,8 +76,12 @@ class _GuiCoreScenarioAdapter(_TaipyBase):
                     scenario.cycle.get_simple_label() if scenario.cycle else "",
                     scenario.cycle.get_simple_label() if scenario.cycle else "",
                     scenario.get_simple_label(),
                     scenario.get_simple_label(),
                     list(scenario.tags) if scenario.tags else [],
                     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.pipelines.values()] if scenario.pipelines 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.pipelines.values()]
+                    if scenario.pipelines
+                    else [],
                     list(scenario.properties.get("authorized_tags", [])) if scenario.properties else [],
                     list(scenario.properties.get("authorized_tags", [])) if scenario.properties else [],
                     is_deletable(scenario),  # deletable
                     is_deletable(scenario),  # deletable
                     is_promotable(scenario),
                     is_promotable(scenario),
@@ -230,6 +235,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         delete = args[1]
         delete = args[1]
         data = args[2]
         data = args[2]
         scenario = None
         scenario = None
+
         name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
         name = data.get(_GuiCoreContext.__PROP_ENTITY_NAME)
         if update:
         if update:
             scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
             scenario_id = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
@@ -253,6 +259,37 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid date ({date_str}).{e}")
                 state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Invalid date ({date_str}).{e}")
                 return
                 return
             try:
             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", [])},
+                                },
+                            ],
+                        )
+                        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)
                 scenario = create_scenario(scenario_config, date, name)
             except Exception as e:
             except Exception as e:
                 state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
                 state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
@@ -268,7 +305,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         for prop in props:
                         for prop in props:
                             key = prop.get("key")
                             key = prop.get("key")
                             if key and key not in _GuiCoreContext.__SCENARIO_PROPS:
                             if key and key not in _GuiCoreContext.__SCENARIO_PROPS:
-                                sc._properties[key] = prop.get("value")
+                                sc.properties[key] = prop.get("value")
                         state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
                         state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, "")
                     except Exception as e:
                     except Exception as e:
                         state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
                         state.assign(_GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR, f"Error creating Scenario. {e}")
@@ -389,6 +426,7 @@ class _GuiCore(ElementLibrary):
                 "on_change": ElementProperty(PropertyType.function),
                 "on_change": ElementProperty(PropertyType.function),
                 "height": ElementProperty(PropertyType.string, "50vh"),
                 "height": ElementProperty(PropertyType.string, "50vh"),
                 "class_name": ElementProperty(PropertyType.dynamic_string),
                 "class_name": ElementProperty(PropertyType.dynamic_string),
+                "on_creation": ElementProperty(PropertyType.function),
             },
             },
             inner_properties={
             inner_properties={
                 "scenarios": ElementProperty(PropertyType.lov, f"{{{__CTX_VAR_NAME}.get_scenarios()}}"),
                 "scenarios": ElementProperty(PropertyType.lov, f"{{{__CTX_VAR_NAME}.get_scenarios()}}"),

+ 6 - 0
src/taipy/gui_core/viselements.json

@@ -55,6 +55,12 @@
             "type": "str",
             "type": "str",
             "default_value": "50vh",
             "default_value": "50vh",
             "doc": "The maximum height, in CSS units, of the tree."
             "doc": "The maximum height, in CSS units, of the tree."
+          },
+          {
+            "name": "on_creation",
+            "type": "Callback",
+            "doc": "The name of the function that is triggered when a scenario will be or has been created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the element.</li>\n<li>action (str): the name of the action that provoked the change.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config: the selected configuration.</li>\n<li>date: the selected date.</li>\n<li>label: the user entered label.</li>\n<li>properties: a key/value dictionnary.</li>\n</ul>\n</li>\n<li>return: the callback can return a scenario, an error message (the scenario will not be created) or None (the scenario will be created with the user parameters).</li>\n</ul>",
+            "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]]
           }
           }
         ]
         ]
       }
       }