From afbaf734b5e3acc27a21d34b6d8e948119367f80 Mon Sep 17 00:00:00 2001 From: Fabien Arcellier Date: Mon, 7 Oct 2024 11:35:29 +0200 Subject: [PATCH 1/4] feat: prevent workflows tree to be sent during run mode * refact: use core_ui.export_component_tree before sending the component tree to user interface * refact: use ServeMode as typing for all argument that requires run or edit * fix: warning on continuous integration --- src/writer/app_runner.py | 19 +++++++++++++------ src/writer/core.py | 3 ++- src/writer/core_ui.py | 19 ++++++++++++++++++- tests/backend/test_core.py | 6 +++--- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/writer/app_runner.py b/src/writer/app_runner.py index 97ae8e36a..e7e7b0e3e 100644 --- a/src/writer/app_runner.py +++ b/src/writer/app_runner.py @@ -18,8 +18,8 @@ from pydantic import ValidationError from watchdog.observers.polling import PollingObserver -from writer import VERSION, audit_and_fix, wf_project -from writer.core import EventHandlerRegistry, MiddlewareRegistry, WriterSession, use_request_context +from writer import VERSION, audit_and_fix, core_ui, wf_project +from writer.core import Config, EventHandlerRegistry, MiddlewareRegistry, WriterSession, use_request_context from writer.core_ui import ingest_bmc_component_tree from writer.ss_types import ( AppProcessServerRequest, @@ -34,6 +34,7 @@ InitSessionRequest, InitSessionRequestPayload, InitSessionResponsePayload, + ServeMode, StateContentRequest, StateContentResponsePayload, StateEnquiryRequest, @@ -83,7 +84,7 @@ def __init__(self, client_conn: multiprocessing.connection.Connection, server_conn: multiprocessing.connection.Connection, app_path: str, - mode: str, + mode: ServeMode, run_code: str, bmc_components: Dict, is_app_process_server_ready: multiprocessing.synchronize.Event, @@ -147,11 +148,14 @@ def _handle_session_init(self, payload: InitSessionRequestPayload) -> InitSessio session.session_state.add_log_entry( "error", "Serialisation error", tb.format_exc()) + ui_component_tree = core_ui.export_component_tree( + session.session_component_tree, mode=writer.Config.mode) + res_payload = InitSessionResponsePayload( userState=user_state, sessionId=session.session_id, mail=session.session_state.mail, - components=session.session_component_tree.to_dict(), + components=ui_component_tree, userFunctions=self._get_user_functions(), featureFlags=writer.Config.feature_flags ) @@ -177,10 +181,13 @@ def _handle_event(self, session: WriterSession, event: WriterEvent) -> EventResp mail = session.session_state.mail + ui_component_tree = core_ui.export_component_tree( + session.session_component_tree, mode=Config.mode, only_update=True) + res_payload = EventResponsePayload( result=result, mutations=mutations, - components=session.session_component_tree.fetch_updates(), + components=ui_component_tree, mail=mail ) session.session_state.clear_mail() @@ -591,7 +598,7 @@ def __init__(self, app_path: str, mode: str): if mode not in ("edit", "run"): raise ValueError("Invalid mode.") - self.mode = mode + self.mode = cast(ServeMode, mode) self._set_logger() def hook_to_running_event_loop(self): diff --git a/src/writer/core.py b/src/writer/core.py index 90095d805..23a2f272e 100644 --- a/src/writer/core.py +++ b/src/writer/core.py @@ -52,6 +52,7 @@ InstancePath, InstancePathItem, Readable, + ServeMode, WriterEvent, WriterEventResult, WriterFileItem, @@ -129,7 +130,7 @@ def wrapper(*args, **kwargs): class Config: is_mail_enabled_for_log: bool = False - mode: str = "run" + mode: ServeMode = "run" logger: Optional[logging.Logger] = None feature_flags: list[str] = [] diff --git a/src/writer/core_ui.py b/src/writer/core_ui.py index 00d276e4d..9997b4000 100644 --- a/src/writer/core_ui.py +++ b/src/writer/core_ui.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, Field from typing_extensions import TypedDict -from writer.ss_types import ComponentDefinition +from writer.ss_types import ComponentDefinition, ServeMode current_parent_container: ContextVar[Union["Component", None]] = \ ContextVar("current_parent_container") @@ -432,6 +432,23 @@ def session_components_list(component_tree: ComponentTree) -> list: """ return list(component_tree.branch(Branch.session_cmc).components.values()) +def export_component_tree(component_tree: ComponentTree, mode: ServeMode, only_update=False) -> Optional[Dict]: + """ + Exports the component tree to the ui. + + >>> filtered_component_tree = core_ui.export_component_tree(session.session_component_tree, mode=writer.Config.mode) + + This function filters artifacts that should be hidden from the user, for example workflows in run mode. + + :param component_tree: the full component tree + :param mode: the mode of the application (edit, run) + :param updated: return something only if component tree has been updated + :return: a dictionary representing the component tree + """ + if only_update is True and component_tree.updated is False: + return None + + return component_tree.to_dict() class UIError(Exception): ... diff --git a/tests/backend/test_core.py b/tests/backend/test_core.py index 39c008b6f..adfc74300 100644 --- a/tests/backend/test_core.py +++ b/tests/backend/test_core.py @@ -595,7 +595,7 @@ def cumulative_sum(state): "total": 0 }) - initial_state.subscribe_mutation('a\.b', cumulative_sum) + initial_state.subscribe_mutation(r'a\.b', cumulative_sum) # Acts initial_state['a.b'] = 1 @@ -1708,6 +1708,6 @@ def test_parse_state_variable_expression_should_process_expression(): # When assert parse_state_variable_expression('features') == ['features'] assert parse_state_variable_expression('features.eyes') == ['features', 'eyes'] - assert parse_state_variable_expression('features\.eyes') == ['features.eyes'] - assert parse_state_variable_expression('features\.eyes.color') == ['features.eyes', 'color'] + assert parse_state_variable_expression(r'features\.eyes') == ['features.eyes'] + assert parse_state_variable_expression(r'features\.eyes.color') == ['features.eyes', 'color'] From 3d76257aefd4f4fe2ad7bc5a0d682e891513dbd5 Mon Sep 17 00:00:00 2001 From: Fabien Arcellier Date: Mon, 7 Oct 2024 20:42:03 +0200 Subject: [PATCH 2/4] feat: prevent workflows tree to be sent during run mode * feat: send workflow in edit mode --- src/writer/app_runner.py | 8 +++++++- src/writer/core_ui.py | 12 +++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/writer/app_runner.py b/src/writer/app_runner.py index e7e7b0e3e..1355474fe 100644 --- a/src/writer/app_runner.py +++ b/src/writer/app_runner.py @@ -19,7 +19,13 @@ from watchdog.observers.polling import PollingObserver from writer import VERSION, audit_and_fix, core_ui, wf_project -from writer.core import Config, EventHandlerRegistry, MiddlewareRegistry, WriterSession, use_request_context +from writer.core import ( + Config, + EventHandlerRegistry, + MiddlewareRegistry, + WriterSession, + use_request_context, +) from writer.core_ui import ingest_bmc_component_tree from writer.ss_types import ( AppProcessServerRequest, diff --git a/src/writer/core_ui.py b/src/writer/core_ui.py index 9997b4000..ca413c2d1 100644 --- a/src/writer/core_ui.py +++ b/src/writer/core_ui.py @@ -448,7 +448,17 @@ def export_component_tree(component_tree: ComponentTree, mode: ServeMode, only_u if only_update is True and component_tree.updated is False: return None - return component_tree.to_dict() + roots = ['root'] + if mode == "edit": + roots.append('workflows_root') + + _components: List[Component] = [] + for root in roots: + _root_component = cast(Component, component_tree.get_component(root)) + _components.append(_root_component) + _components += component_tree.get_descendents(root) + + return {c.id: c.to_dict() for c in _components} class UIError(Exception): ... From 9ce9fedbf4093c67a624ab04948af4b640eb21d2 Mon Sep 17 00:00:00 2001 From: Fabien Arcellier Date: Mon, 7 Oct 2024 20:42:33 +0200 Subject: [PATCH 3/4] feat: prevent workflows tree to be sent during run mode * refact: add fixtures to test workflow export * chore: implement test on filtering workflow export --- tests/backend/test_app_runner.py | 72 ++++++++++ ...bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl | 129 +++++++++--------- .../.wf/components-workflows_root.jsonl | 1 + ...orkflows_workflow-0-hywgzgfetx6rpiqz.jsonl | 5 + tests/backend/testapp/main.py | 6 + 5 files changed, 149 insertions(+), 64 deletions(-) create mode 100644 tests/backend/testapp/.wf/components-workflows_root.jsonl create mode 100644 tests/backend/testapp/.wf/components-workflows_workflow-0-hywgzgfetx6rpiqz.jsonl diff --git a/tests/backend/test_app_runner.py b/tests/backend/test_app_runner.py index 4f3c771c2..47ca54763 100644 --- a/tests/backend/test_app_runner.py +++ b/tests/backend/test_app_runner.py @@ -45,6 +45,78 @@ def test_init_wrong_mode(self) -> None: with pytest.raises(ValueError): AppRunner(test_app_dir, "virus") + @pytest.mark.asyncio + @pytest.mark.usefixtures("setup_app_runner") + async def test_init_should_not_load_workflow_component_in_run_mode(self, setup_app_runner) -> None: + ar: AppRunner + with setup_app_runner(test_app_dir, "run", load = True) as ar: + response = await ar.init_session(InitSessionRequestPayload( + cookies={}, + headers={}, + proposedSessionId=self.proposed_session_id + )) + + assert response.payload.components.get("workflows_root") is None + + @pytest.mark.asyncio + @pytest.mark.usefixtures("setup_app_runner") + async def test_init_should_load_workflow_component_in_edit_mode(self, setup_app_runner) -> None: + ar: AppRunner + with setup_app_runner(test_app_dir, "edit", load = True) as ar: + response = await ar.init_session(InitSessionRequestPayload( + cookies={}, + headers={}, + proposedSessionId=self.proposed_session_id + )) + + assert response.payload.components.get("workflows_root") is not None + + @pytest.mark.asyncio + @pytest.mark.usefixtures("setup_app_runner") + async def test_backend_ui_event_should_not_load_workflow_component_in_run_mode(self, setup_app_runner) -> None: + ar: AppRunner + with setup_app_runner(test_app_dir, "run", load = True) as ar: + await init_app_session(ar, session_id=self.proposed_session_id) + + ev_req = EventRequest(type="event", payload=WriterEvent( + type="wf-click", + instancePath=[ + {"componentId": "root", "instanceNumber": 0}, + {"componentId": "bb4d0e86-619e-4367-a180-be28ab6059f4", "instanceNumber": 0}, + {"componentId": "92a2c0c8-7ab4-4865-b7eb-ed437408c8f5", "instanceNumber": 0}, + {"componentId": "d1e01ce1-fab1-4a6e-91a1-1f45f9e57aa5", "instanceNumber": 0}, + {"componentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "instanceNumber": 0}, + {"componentId": "8ykyk5avd9ioyr6l", "instanceNumber": 0}, + ], + payload={} + )) + + rev = await ar.dispatch_message(self.proposed_session_id, ev_req) + assert rev.payload.components.get("workflows_root") is None + + @pytest.mark.asyncio + @pytest.mark.usefixtures("setup_app_runner") + async def test_backend_ui_event_should_load_workflow_component_in_edit_mode(self, setup_app_runner) -> None: + ar: AppRunner + with setup_app_runner(test_app_dir, "edit", load = True) as ar: + await init_app_session(ar, session_id=self.proposed_session_id) + + ev_req = EventRequest(type="event", payload=WriterEvent( + type="wf-click", + instancePath=[ + {"componentId": "root", "instanceNumber": 0}, + {"componentId": "bb4d0e86-619e-4367-a180-be28ab6059f4", "instanceNumber": 0}, + {"componentId": "92a2c0c8-7ab4-4865-b7eb-ed437408c8f5", "instanceNumber": 0}, + {"componentId": "d1e01ce1-fab1-4a6e-91a1-1f45f9e57aa5", "instanceNumber": 0}, + {"componentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "instanceNumber": 0}, + {"componentId": "8ykyk5avd9ioyr6l", "instanceNumber": 0}, + ], + payload={} + )) + + rev = await ar.dispatch_message(self.proposed_session_id, ev_req) + assert rev.payload.components.get("workflows_root") is not None + @pytest.mark.asyncio @pytest.mark.usefixtures("setup_app_runner") async def test_pre_session(self, setup_app_runner) -> None: diff --git a/tests/backend/testapp/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl b/tests/backend/testapp/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl index dbc94c740..35f8bb1c8 100644 --- a/tests/backend/testapp/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl +++ b/tests/backend/testapp/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl @@ -1,90 +1,91 @@ {"id": "bb4d0e86-619e-4367-a180-be28ab6059f4", "type": "page", "content": {"pageMode": "", "key": "main"}, "isCodeManaged": false, "position": 0, "parentId": "root"} {"id": "84378aea-b64c-49a3-9539-f854532279ee", "type": "header", "content": {"text": "TEST APP", "emptinessColor": "#ffffff"}, "isCodeManaged": false, "position": 0, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4"} {"id": "92a2c0c8-7ab4-4865-b7eb-ed437408c8f5", "type": "columns", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4"} +{"id": "fbad9feb-5c88-4425-bb17-0d138286a875", "type": "sidebar", "content": {"startCollapsed": "yes"}, "isCodeManaged": false, "position": -2, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4"} +{"id": "b3df6ccd-20d4-4761-b8ed-8eabcf95f4be", "type": "webcamcapture", "content": {"refreshRate": "200"}, "isCodeManaged": false, "position": 2, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "15bd7d51-1a8c-4359-a892-253294684e1d", "type": "text", "content": {"text": "@{counter}"}, "isCodeManaged": false, "position": 0, "parentId": "84378aea-b64c-49a3-9539-f854532279ee", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "d1e01ce1-fab1-4a6e-91a1-1f45f9e57aa5", "type": "column", "content": {"width": "1", "isCollapsible": "", "title": "", "isSticky": "yes"}, "isCodeManaged": false, "position": 0, "parentId": "92a2c0c8-7ab4-4865-b7eb-ed437408c8f5"} {"id": "0569937e-c72c-4fb9-820e-2ae56e17bcc0", "type": "column", "content": {"width": "1.61"}, "isCodeManaged": false, "position": 1, "parentId": "92a2c0c8-7ab4-4865-b7eb-ed437408c8f5"} {"id": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "type": "section", "content": {"title": ""}, "isCodeManaged": false, "position": 0, "parentId": "d1e01ce1-fab1-4a6e-91a1-1f45f9e57aa5"} +{"id": "7b710d37-1c68-4d06-a65a-e6596ccc826a", "type": "text", "content": {"text": "In the bustling city, a hidden group of hacker pigeons works together to break into computer systems and create advanced data apps. These smart birds have different roles, like leaders, developers, security experts, and designers, and they use their skills to complete various tech challenges."}, "isCodeManaged": false, "position": 2, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} +{"id": "31c1b0d5-bfb6-4304-82bd-1687d492f0a2", "type": "heading", "content": {"text": "Pigeon Power: Clever Birds Tackle Data Apps", "alignment": "right", "primaryTextColor": "#2fa0c6"}, "isCodeManaged": false, "position": 0, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} +{"id": "804e15bf-11a7-463d-8082-f46ea3acac1b", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} +{"id": "d99d976a-bdf0-4fc4-8379-4998e7a58440", "type": "text", "content": {"text": "UTF-8 state: @{utf\u0800}"}, "isCodeManaged": false, "position": 3, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "3991ac75-1219-49e7-a70d-6b782c27b4e0", "type": "button", "content": {"text": "Go to page \"inputTest\""}, "isCodeManaged": false, "position": 4, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "$goToPage_inputTest"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "add45c35-6a73-4f9a-bade-22fecd738967", "type": "button", "content": {"text": "Go to page \"content\""}, "isCodeManaged": false, "position": 5, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "$goToPage_content"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "6e912116-4cc5-4840-96b9-84106bee795d", "type": "button", "content": {"text": "File download", "icon": "download"}, "isCodeManaged": false, "position": 6, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "handle_file_download"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "272f0871-585e-4662-8ae1-a8cd6067b60b", "type": "button", "content": {"text": "Add notifications", "icon": "notifications"}, "isCodeManaged": false, "position": 7, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "add_notification"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "5c0df6e8-4dd8-4485-a244-8e9e7f4b4675", "type": "button", "content": {"text": "Increment", "icon": "arrow_upward"}, "isCodeManaged": false, "position": 9, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "increment"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "2e46c38b-6405-42ad-ad9c-d237a53a7d30", "type": "dropdowninput", "content": {"label": "Input Label", "options": "{\n \"ar\": \"Argentina\",\n \"uk\": \"United Kingdom\"\n}"}, "isCodeManaged": false, "position": 12, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"wf-option-change": "update_cities"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "20a4329c-4b14-4743-adaa-698ff0629aed", "type": "dropdowninput", "content": {"label": "Input Label", "options": "@{cities}"}, "isCodeManaged": false, "position": 13, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "242a8268-10d1-4e2c-96bd-f17ffe9e9b28", "type": "textinput", "content": {"label": "Input Label", "passwordMode": "yes"}, "isCodeManaged": false, "position": 14, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "nyo5vc79sb031yz8", "type": "button", "content": {"text": "Increment asynchronically"}, "isCodeManaged": false, "position": 10, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"wf-click": "test_async_handler"}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "3apnnxxg7pubdeqp", "type": "text", "content": {"text": "@{counter}"}, "isCodeManaged": false, "position": 8, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "ee919cd6-8153-4f34-8c6a-bfc1153df360", "type": "tabs", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "0569937e-c72c-4fb9-820e-2ae56e17bcc0"} {"id": "c6392876-7cfd-4680-8725-b04f43ff294f", "type": "tab", "content": {"name": "Data and Charts"}, "isCodeManaged": false, "position": 0, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360"} {"id": "da00a61f-0ee2-434e-acd6-228d32eae5c6", "type": "tab", "content": {"name": "Repeater"}, "isCodeManaged": false, "position": 2, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360"} -{"id": "d0298b1c-7c64-4b58-a018-db97dd49675b", "type": "separator", "content": {}, "isCodeManaged": false, "position": 3, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} -{"id": "f1d6bc8e-a780-4ae5-8b7c-082fe8a6867d", "type": "dataframe", "content": {"dataframe": "@{main_df}", "dataframeBackgroundColor": "#f5fdff", "dataframeHeaderRowBackgroundColor": "#d0e6eb", "showIndex": "", "enableSearch": ""}, "isCodeManaged": false, "position": 4, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} -{"id": "fbad9feb-5c88-4425-bb17-0d138286a875", "type": "sidebar", "content": {"startCollapsed": "yes"}, "isCodeManaged": false, "position": -2, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4"} -{"id": "7e625201-20c2-4b05-951c-d825de28b216", "type": "section", "content": {"title": "Filter data"}, "isCodeManaged": false, "position": 0, "parentId": "fbad9feb-5c88-4425-bb17-0d138286a875"} -{"id": "385247e5-5c89-4352-a598-b8da81146a5a", "type": "sliderinput", "content": {"label": "Minimum weight", "minValue": "300", "maxValue": "600", "stepSize": "0.1"}, "isCodeManaged": false, "position": 1, "parentId": "7e625201-20c2-4b05-951c-d825de28b216", "handlers": {"wf-number-change": "update"}, "binding": {"eventType": "wf-number-change", "stateRef": "filter.min_weight"}} -{"id": "10c156df-7464-4889-8b69-1b54cb1ee80a", "type": "sliderinput", "content": {"label": "Minimum length", "minValue": "25", "maxValue": "35", "stepSize": "1"}, "isCodeManaged": false, "position": 0, "parentId": "7e625201-20c2-4b05-951c-d825de28b216", "handlers": {"wf-number-change": "update"}, "binding": {"eventType": "wf-number-change", "stateRef": "filter.min_length"}} -{"id": "70d82458-a08f-4005-8f96-dc8d3ba92fad", "type": "section", "content": {"title": "About this app"}, "isCodeManaged": false, "position": 1, "parentId": "fbad9feb-5c88-4425-bb17-0d138286a875"} -{"id": "12e11005-3b5e-4bd8-9a8c-fc7b8cb757d0", "type": "text", "content": {"text": "This app is meant to serve as a lighthearted introduction to Writer Framework. It's not a comprehensive demonstration of its capabilities."}, "isCodeManaged": false, "position": 0, "parentId": "70d82458-a08f-4005-8f96-dc8d3ba92fad"} {"id": "18f73ad0-3961-4aed-9dc1-5ed54d939fdf", "type": "tab", "content": {"name": "Download"}, "isCodeManaged": false, "position": 3, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360"} -{"id": "7b710d37-1c68-4d06-a65a-e6596ccc826a", "type": "text", "content": {"text": "In the bustling city, a hidden group of hacker pigeons works together to break into computer systems and create advanced data apps. These smart birds have different roles, like leaders, developers, security experts, and designers, and they use their skills to complete various tech challenges."}, "isCodeManaged": false, "position": 2, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} -{"id": "b9cb10e5-1ead-448b-afcc-909e23afb72a", "type": "columns", "content": {}, "isCodeManaged": false, "position": 2, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} -{"id": "31c1b0d5-bfb6-4304-82bd-1687d492f0a2", "type": "heading", "content": {"text": "Pigeon Power: Clever Birds Tackle Data Apps", "alignment": "right", "primaryTextColor": "#2fa0c6"}, "isCodeManaged": false, "position": 0, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} -{"id": "804e15bf-11a7-463d-8082-f46ea3acac1b", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} {"id": "3cc9c5e9-6c77-401d-ab82-7805d9df760c", "type": "tab", "content": {"name": "Layout"}, "isCodeManaged": false, "position": 1, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360"} -{"id": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff", "type": "columns", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "3cc9c5e9-6c77-401d-ab82-7805d9df760c"} -{"id": "25dda22d-1b18-4584-aa99-aaae9f3b8edf", "type": "column", "content": {"width": "1", "contentVAlign": ""}, "isCodeManaged": false, "position": 0, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} -{"id": "b1ee642e-f2e7-453b-a6ef-3d96eea37140", "type": "column", "content": {"title": "HTML Element", "width": "1", "isCollapsible": "yes", "startCollapsed": ""}, "isCodeManaged": false, "position": 2, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} -{"id": "d1f43b6f-5b0f-4c8f-95bf-a92dd7ed723b", "type": "text", "content": {"text": "\n## Role Distribution\n\nThe hacker pigeons dataframe consists of five distinct roles:\n\n1. Leader\n2. Developer\n3. Data Expert\n4. Designer\n5. Security Expert\n\nThe role distribution among the hacker pigeons can be analyzed to understand the composition of the secret society and the proportions of various roles.\n\n## Length and Weight Statistics\n\n- The length and weight of hacker pigeons in the dataframe can be used to gain insights into their physical characteristics.\n- Examining the minimum, maximum, and average values for length and weight can provide an overview of the size variation among the hacker pigeons.\n- By grouping the data by role, we can analyze the differences in length and weight for each role and identify any patterns or trends.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 2, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} -{"id": "71766c0c-e1e5-4675-9dd0-3aa2627773a0", "type": "html", "content": {"styles": "{\n \"padding\": \"16px\",\n \"margin\": \"24px\",\n \"min-height\": \"64px\",\n \"min-width\": \"64px\",\n \"border-radius\": \"8px\",\n \"transform\": \"rotate(-3deg)\",\n \"box-shadow\": \"0 4px 16px -8px black\"\n}"}, "isCodeManaged": false, "position": 0, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} -{"id": "c73602a6-453d-4ccf-b8e3-b1774ab4ff17", "type": "text", "content": {"text": "Use the HTML Element component when you need additional control."}, "isCodeManaged": false, "position": 1, "parentId": "71766c0c-e1e5-4675-9dd0-3aa2627773a0"} -{"id": "573f095f-94a7-43e4-a94e-b2f69439a164", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} -{"id": "feedc43f-a7cf-499b-962e-8170f1032b69", "type": "text", "content": {"text": "You can use Markdown, as shown below."}, "isCodeManaged": false, "position": 0, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} -{"id": "1ee57415-557d-4529-be99-36f4d91fdf69", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} -{"id": "ec5bc32e-1456-4abd-8d3e-97c640e32339", "type": "section", "content": {"title": "", "containerBackgroundColor": "#ebfcff"}, "isCodeManaged": false, "position": 1, "parentId": "3cc9c5e9-6c77-401d-ab82-7805d9df760c"} -{"id": "919b0d26-ea9b-4364-b5af-865236b3fc3a", "type": "horizontalstack", "content": {"contentHAlign": "start"}, "isCodeManaged": false, "position": 0, "parentId": "ec5bc32e-1456-4abd-8d3e-97c640e32339"} -{"id": "0cf677a9-34f6-461a-a531-5578939400c7", "type": "text", "content": {"text": "Used to stack things horizontally."}, "isCodeManaged": false, "position": 2, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} -{"id": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d", "type": "html", "content": {"styles": "{\n \"padding\": \"16px\",\n \"margin\": \"24px\",\n \"min-height\": \"64px\",\n \"min-width\": \"64px\",\n \"border-radius\": \"8px\",\n \"transform\": \"rotate(3deg)\",\n \"box-shadow\": \"0 4px 16px -8px black\"\n}"}, "isCodeManaged": false, "position": 1, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} -{"id": "5da5e007-d60a-4313-9d21-885deae7b37d", "type": "text", "content": {"text": "You can put other Writer Framework components inside HTML Elements."}, "isCodeManaged": false, "position": 1, "parentId": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d"} {"id": "85120b55-69c6-4b50-853a-bbbf73ff8121", "type": "tab", "content": {"name": "Timer"}, "isCodeManaged": false, "position": 4, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360"} -{"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12", "type": "dataframe", "content": {"dataframe": "@{random_df}", "fontStyle": "monospace"}, "isCodeManaged": false, "position": 1, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} -{"id": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "type": "horizontalstack", "content": {"contentHAlign": "start"}, "isCodeManaged": false, "position": 0, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} -{"id": "50a05488-d6fe-47bf-b681-36870d04f5d7", "type": "timer", "content": {"intervalMs": "500", "isActive": "no"}, "isCodeManaged": false, "position": 0, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "handlers": {"wf-tick": "handle_timer_tick"}} -{"id": "e296866a-75d2-4677-b55d-3c1456113b89", "type": "text", "content": {"text": "Refreshing automatically using a timer."}, "isCodeManaged": false, "position": 1, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d"} -{"id": "fdf38e46-c01e-4a93-94d5-e187f9e4c823", "type": "text", "content": {"text": "_pgcf_ stands for \"Pigeon Coefficient\" and is a meaningless, randomly-generated value.", "useMarkdown": "yes", "primaryTextColor": "#8a8a8a"}, "isCodeManaged": false, "position": 2, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} -{"id": "c921816d-6d45-4ce3-9c18-2c78ff850e0e", "type": "html", "content": {"element": "img", "styles": "{\n \"filter\": \"hue-rotate(calc(80deg + @{hue_rotation}deg))\"\n}", "attrs": "{ \"src\": \"static/pigeon1.jpg\"}"}, "isCodeManaged": false, "position": 0, "parentId": "71766c0c-e1e5-4675-9dd0-3aa2627773a0"} -{"id": "c684f61e-0c79-4cb1-af9f-46c9cab5dfea", "type": "html", "content": {"element": "img", "styles": "{\n \"filter\": \"hue-rotate(calc(140deg + @{hue_rotation}deg))\"\n}", "attrs": "{ \"src\": \"static/pigeon1.jpg\"}"}, "isCodeManaged": false, "position": 0, "parentId": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d"} -{"id": "ee82e035-cfb2-4d00-95ce-ccbb9eb2dbb9", "type": "sliderinput", "content": {"label": "Hue rotation", "minValue": "0", "maxValue": "360", "stepSize": "1"}, "isCodeManaged": false, "position": 2, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140", "binding": {"eventType": "wf-number-change", "stateRef": "hue_rotation"}} -{"id": "c9bb4720-d07a-4fd8-bc53-5bda8dc64046", "type": "text", "content": {"text": "As shown above, you can use _HTML Element_ components together with state references.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 3, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} -{"id": "e77448dd-4e7c-451c-8f31-4edde9bbf9f5", "type": "text", "content": {"text": "Horizontal Stack"}, "isCodeManaged": false, "position": 0, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} -{"id": "b4dc3e9c-ce01-4690-8e0e-e4357dbe70ad", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} +{"id": "d0298b1c-7c64-4b58-a018-db97dd49675b", "type": "separator", "content": {}, "isCodeManaged": false, "position": 3, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} +{"id": "f1d6bc8e-a780-4ae5-8b7c-082fe8a6867d", "type": "dataframe", "content": {"dataframe": "@{main_df}", "dataframeBackgroundColor": "#f5fdff", "dataframeHeaderRowBackgroundColor": "#d0e6eb", "showIndex": "", "enableSearch": ""}, "isCodeManaged": false, "position": 4, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} +{"id": "b9cb10e5-1ead-448b-afcc-909e23afb72a", "type": "columns", "content": {}, "isCodeManaged": false, "position": 2, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f"} +{"id": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "type": "columns", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "4cca0893-5ad7-4152-b805-5c87babc4dee", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "6a490318-239e-4fe9-a56b-f0f33d628c87", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 2, "parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a"} {"id": "888b1eb9-609c-4205-bbda-262999d197ff", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a"} +{"id": "6d895924-e808-44aa-a119-f4e2d7f394f3", "type": "column", "content": {"width": "1", "not-a-real-field": "not-a-real-value", "contentHAlign": "", "contentVAlign": ""}, "isCodeManaged": false, "position": 0, "parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a"} +{"id": "f67c98aa-3a7b-4f40-ac1f-0dd4aa06e22d", "type": "plotlygraph", "content": {"spec": "@{scatter_chart}"}, "isCodeManaged": false, "position": 0, "parentId": "6a490318-239e-4fe9-a56b-f0f33d628c87", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "70bd9ea8-baa9-4e1d-bce5-deee6a3c5440", "type": "plotlygraph", "content": {"spec": "@{role_chart}"}, "isCodeManaged": false, "position": 0, "parentId": "6d895924-e808-44aa-a119-f4e2d7f394f3", "handlers": {}} +{"id": "feb9ca67-6670-483d-a895-22b031426a13", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 0, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "3b325899-e560-40ea-ba54-9c55967af1e3", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 1, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "a0cd99db-0cbe-40ca-b9cb-b1670ec60dd8", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 2, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "5dcd137b-76bd-4a5f-ae5c-5b629035500e", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 3, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "6a81f847-4d1d-4110-9cc1-12c716150e66", "type": "metric", "content": {"metricValue": "@{metrics.average_length}cm", "name": "Average length", "note": "@{metrics.average_length_note}", "description": ""}, "isCodeManaged": false, "position": 0, "parentId": "3b325899-e560-40ea-ba54-9c55967af1e3", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "8e54e9d2-a7c8-4f74-897f-fa5791cd82da", "type": "metric", "content": {"metricValue": "@{metrics.average_bmi}", "name": "Average BMI", "note": "@{metrics.average_bmi_note}", "description": ""}, "isCodeManaged": false, "position": 0, "parentId": "a0cd99db-0cbe-40ca-b9cb-b1670ec60dd8", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "07f50628-4679-48a8-9a5d-07dcaf171afb", "type": "metric", "content": {"metricValue": "@{metrics.diversity}", "name": "Diversity Index", "description": "", "note": "@{metrics.diversity_note}"}, "isCodeManaged": false, "position": 0, "parentId": "5dcd137b-76bd-4a5f-ae5c-5b629035500e", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "de70a15a-2ff6-42d2-ab12-c7fc9c3ed4e1", "type": "heading", "content": {"text": "Highlighted Members \ud83c\udfc6", "headingType": "h1", "alignment": "center"}, "isCodeManaged": false, "position": 0, "parentId": "da00a61f-0ee2-434e-acd6-228d32eae5c6"} -{"id": "e72f2050-3156-4fbf-9961-9a31945425fb", "type": "heading", "content": {"text": "The Story", "headingType": "h1"}, "isCodeManaged": false, "position": 0, "parentId": "18f73ad0-3961-4aed-9dc1-5ed54d939fdf"} {"id": "2f4969e2-e248-43ed-9e63-222fc35250e2", "type": "columns", "content": {}, "isCodeManaged": false, "position": 2, "parentId": "da00a61f-0ee2-434e-acd6-228d32eae5c6"} +{"id": "01c33c6e-3788-4b5d-b7aa-2addaa7b503f", "type": "text", "content": {"text": "The following Hacker Pigeons have made an impressive contribution this month.", "alignment": "center"}, "isCodeManaged": false, "position": 1, "parentId": "da00a61f-0ee2-434e-acd6-228d32eae5c6"} {"id": "f46cd60d-f01e-4390-a161-4353006b72a1", "type": "repeater", "content": {"repeaterObject": "@{highlighted_members_dict}", "keyVariable": "itemId", "valueVariable": "item"}, "isCodeManaged": false, "position": 0, "parentId": "2f4969e2-e248-43ed-9e63-222fc35250e2"} {"id": "0dd29423-3867-478a-997e-eeaafb6b811e", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 0, "parentId": "f46cd60d-f01e-4390-a161-4353006b72a1"} {"id": "2d326b15-da90-496e-86e8-7fdd4bcbe822", "type": "section", "content": {"title": "@{item.name}", "containerBackgroundColor": "#eff4f6"}, "isCodeManaged": false, "position": 0, "parentId": "0dd29423-3867-478a-997e-eeaafb6b811e"} {"id": "7ea0d29a-5dca-4b6c-a067-322ccaee5032", "type": "text", "content": {"text": "You can use a _Repeater_ component to repeat components, based on a dictionary.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 4, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822"} {"id": "cd611ce2-f594-4b55-9932-d48e657b2e31", "type": "text", "content": {"text": "**Role:** @{item.role}\n", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 1, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822"} {"id": "9d6ee245-b8f7-4391-9934-89598b7fa9f8", "type": "separator", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822"} -{"id": "01c33c6e-3788-4b5d-b7aa-2addaa7b503f", "type": "text", "content": {"text": "The following Hacker Pigeons have made an impressive contribution this month.", "alignment": "center"}, "isCodeManaged": false, "position": 1, "parentId": "da00a61f-0ee2-434e-acd6-228d32eae5c6"} {"id": "e0e2391e-7bab-4c68-ad92-841942cb12fb", "type": "text", "content": {"text": "The ability @{item.name} demonstrated in @{item.specialty} has earned them this recognition.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 2, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822"} {"id": "4ef4655b-45ca-495c-9f8d-fa1d7ae3565e", "type": "separator", "content": {}, "isCodeManaged": false, "position": 3, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822"} -{"id": "f67c98aa-3a7b-4f40-ac1f-0dd4aa06e22d", "type": "plotlygraph", "content": {"spec": "@{scatter_chart}"}, "isCodeManaged": false, "position": 0, "parentId": "6a490318-239e-4fe9-a56b-f0f33d628c87", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "6d895924-e808-44aa-a119-f4e2d7f394f3", "type": "column", "content": {"width": "1", "not-a-real-field": "not-a-real-value", "contentHAlign": "", "contentVAlign": ""}, "isCodeManaged": false, "position": 0, "parentId": "b9cb10e5-1ead-448b-afcc-909e23afb72a"} -{"id": "70bd9ea8-baa9-4e1d-bce5-deee6a3c5440", "type": "plotlygraph", "content": {"spec": "@{role_chart}"}, "isCodeManaged": false, "position": 0, "parentId": "6d895924-e808-44aa-a119-f4e2d7f394f3", "handlers": {}} -{"id": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "type": "columns", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "feb9ca67-6670-483d-a895-22b031426a13", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 0, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "3b325899-e560-40ea-ba54-9c55967af1e3", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 1, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "a0cd99db-0cbe-40ca-b9cb-b1670ec60dd8", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 2, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "5dcd137b-76bd-4a5f-ae5c-5b629035500e", "type": "column", "content": {"width": "1"}, "isCodeManaged": false, "position": 3, "parentId": "1c05b2e7-3a31-40dd-b6b8-77ded7c6bc0f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "6a81f847-4d1d-4110-9cc1-12c716150e66", "type": "metric", "content": {"metricValue": "@{metrics.average_length}cm", "name": "Average length", "note": "@{metrics.average_length_note}", "description": ""}, "isCodeManaged": false, "position": 0, "parentId": "3b325899-e560-40ea-ba54-9c55967af1e3", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "8e54e9d2-a7c8-4f74-897f-fa5791cd82da", "type": "metric", "content": {"metricValue": "@{metrics.average_bmi}", "name": "Average BMI", "note": "@{metrics.average_bmi_note}", "description": ""}, "isCodeManaged": false, "position": 0, "parentId": "a0cd99db-0cbe-40ca-b9cb-b1670ec60dd8", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "07f50628-4679-48a8-9a5d-07dcaf171afb", "type": "metric", "content": {"metricValue": "@{metrics.diversity}", "name": "Diversity Index", "description": "", "note": "@{metrics.diversity_note}"}, "isCodeManaged": false, "position": 0, "parentId": "5dcd137b-76bd-4a5f-ae5c-5b629035500e", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "4cca0893-5ad7-4152-b805-5c87babc4dee", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "c6392876-7cfd-4680-8725-b04f43ff294f", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "b5915be5-774d-441a-a616-9f85e8e85c8e", "type": "button", "content": {"text": "Test context"}, "isCodeManaged": false, "position": 5, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822", "handlers": {"click": "test_context"}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "6014485e-2418-42bd-a111-8683a8b87de4", "type": "text", "content": {"text": "Selected info: @{highlighted_context}"}, "isCodeManaged": false, "position": 6, "parentId": "2d326b15-da90-496e-86e8-7fdd4bcbe822", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "d99d976a-bdf0-4fc4-8379-4998e7a58440", "type": "text", "content": {"text": "UTF-8 state: @{utf\u0800}"}, "isCodeManaged": false, "position": 3, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "3991ac75-1219-49e7-a70d-6b782c27b4e0", "type": "button", "content": {"text": "Go to page \"inputTest\""}, "isCodeManaged": false, "position": 4, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "$goToPage_inputTest"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "add45c35-6a73-4f9a-bade-22fecd738967", "type": "button", "content": {"text": "Go to page \"content\""}, "isCodeManaged": false, "position": 5, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "$goToPage_content"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "6e912116-4cc5-4840-96b9-84106bee795d", "type": "button", "content": {"text": "File download", "icon": "download"}, "isCodeManaged": false, "position": 6, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "handle_file_download"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "272f0871-585e-4662-8ae1-a8cd6067b60b", "type": "button", "content": {"text": "Add notifications", "icon": "notifications"}, "isCodeManaged": false, "position": 7, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "add_notification"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "5c0df6e8-4dd8-4485-a244-8e9e7f4b4675", "type": "button", "content": {"text": "Increment", "icon": "arrow_upward"}, "isCodeManaged": false, "position": 9, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"click": "increment"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "15bd7d51-1a8c-4359-a892-253294684e1d", "type": "text", "content": {"text": "@{counter}"}, "isCodeManaged": false, "position": 0, "parentId": "84378aea-b64c-49a3-9539-f854532279ee", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "b3df6ccd-20d4-4761-b8ed-8eabcf95f4be", "type": "webcamcapture", "content": {"refreshRate": "200"}, "isCodeManaged": false, "position": 2, "parentId": "bb4d0e86-619e-4367-a180-be28ab6059f4", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "2e46c38b-6405-42ad-ad9c-d237a53a7d30", "type": "dropdowninput", "content": {"label": "Input Label", "options": "{\n \"ar\": \"Argentina\",\n \"uk\": \"United Kingdom\"\n}"}, "isCodeManaged": false, "position": 11, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"wf-option-change": "update_cities"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "20a4329c-4b14-4743-adaa-698ff0629aed", "type": "dropdowninput", "content": {"label": "Input Label", "options": "@{cities}"}, "isCodeManaged": false, "position": 12, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "242a8268-10d1-4e2c-96bd-f17ffe9e9b28", "type": "textinput", "content": {"label": "Input Label", "passwordMode": "yes"}, "isCodeManaged": false, "position": 13, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "nyo5vc79sb031yz8", "type": "button", "content": {"text": "Increment asynchronically"}, "isCodeManaged": false, "position": 10, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {"wf-click": "test_async_handler"}, "visible": {"expression": true, "binding": "", "reversed": false}} -{"id": "3apnnxxg7pubdeqp", "type": "text", "content": {"text": "@{counter}"}, "isCodeManaged": false, "position": 8, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} +{"id": "e72f2050-3156-4fbf-9961-9a31945425fb", "type": "heading", "content": {"text": "The Story", "headingType": "h1"}, "isCodeManaged": false, "position": 0, "parentId": "18f73ad0-3961-4aed-9dc1-5ed54d939fdf"} +{"id": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff", "type": "columns", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "3cc9c5e9-6c77-401d-ab82-7805d9df760c"} +{"id": "ec5bc32e-1456-4abd-8d3e-97c640e32339", "type": "section", "content": {"title": "", "containerBackgroundColor": "#ebfcff"}, "isCodeManaged": false, "position": 1, "parentId": "3cc9c5e9-6c77-401d-ab82-7805d9df760c"} +{"id": "25dda22d-1b18-4584-aa99-aaae9f3b8edf", "type": "column", "content": {"width": "1", "contentVAlign": ""}, "isCodeManaged": false, "position": 0, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} +{"id": "b1ee642e-f2e7-453b-a6ef-3d96eea37140", "type": "column", "content": {"title": "HTML Element", "width": "1", "isCollapsible": "yes", "startCollapsed": ""}, "isCodeManaged": false, "position": 2, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} +{"id": "573f095f-94a7-43e4-a94e-b2f69439a164", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "fb22acfc-cdb5-44b6-9e97-76c3a51a8fff"} +{"id": "d1f43b6f-5b0f-4c8f-95bf-a92dd7ed723b", "type": "text", "content": {"text": "\n## Role Distribution\n\nThe hacker pigeons dataframe consists of five distinct roles:\n\n1. Leader\n2. Developer\n3. Data Expert\n4. Designer\n5. Security Expert\n\nThe role distribution among the hacker pigeons can be analyzed to understand the composition of the secret society and the proportions of various roles.\n\n## Length and Weight Statistics\n\n- The length and weight of hacker pigeons in the dataframe can be used to gain insights into their physical characteristics.\n- Examining the minimum, maximum, and average values for length and weight can provide an overview of the size variation among the hacker pigeons.\n- By grouping the data by role, we can analyze the differences in length and weight for each role and identify any patterns or trends.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 2, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} +{"id": "feedc43f-a7cf-499b-962e-8170f1032b69", "type": "text", "content": {"text": "You can use Markdown, as shown below."}, "isCodeManaged": false, "position": 0, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} +{"id": "1ee57415-557d-4529-be99-36f4d91fdf69", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "25dda22d-1b18-4584-aa99-aaae9f3b8edf"} +{"id": "71766c0c-e1e5-4675-9dd0-3aa2627773a0", "type": "html", "content": {"styles": "{\n \"padding\": \"16px\",\n \"margin\": \"24px\",\n \"min-height\": \"64px\",\n \"min-width\": \"64px\",\n \"border-radius\": \"8px\",\n \"transform\": \"rotate(-3deg)\",\n \"box-shadow\": \"0 4px 16px -8px black\"\n}"}, "isCodeManaged": false, "position": 0, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} +{"id": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d", "type": "html", "content": {"styles": "{\n \"padding\": \"16px\",\n \"margin\": \"24px\",\n \"min-height\": \"64px\",\n \"min-width\": \"64px\",\n \"border-radius\": \"8px\",\n \"transform\": \"rotate(3deg)\",\n \"box-shadow\": \"0 4px 16px -8px black\"\n}"}, "isCodeManaged": false, "position": 1, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} +{"id": "ee82e035-cfb2-4d00-95ce-ccbb9eb2dbb9", "type": "sliderinput", "content": {"label": "Hue rotation", "minValue": "0", "maxValue": "360", "stepSize": "1"}, "isCodeManaged": false, "position": 2, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140", "binding": {"eventType": "wf-number-change", "stateRef": "hue_rotation"}} +{"id": "c9bb4720-d07a-4fd8-bc53-5bda8dc64046", "type": "text", "content": {"text": "As shown above, you can use _HTML Element_ components together with state references.", "useMarkdown": "yes"}, "isCodeManaged": false, "position": 3, "parentId": "b1ee642e-f2e7-453b-a6ef-3d96eea37140"} +{"id": "c73602a6-453d-4ccf-b8e3-b1774ab4ff17", "type": "text", "content": {"text": "Use the HTML Element component when you need additional control."}, "isCodeManaged": false, "position": 1, "parentId": "71766c0c-e1e5-4675-9dd0-3aa2627773a0"} +{"id": "c921816d-6d45-4ce3-9c18-2c78ff850e0e", "type": "html", "content": {"element": "img", "styles": "{\n \"filter\": \"hue-rotate(calc(80deg + @{hue_rotation}deg))\"\n}", "attrs": "{ \"src\": \"static/pigeon1.jpg\"}"}, "isCodeManaged": false, "position": 0, "parentId": "71766c0c-e1e5-4675-9dd0-3aa2627773a0"} +{"id": "5da5e007-d60a-4313-9d21-885deae7b37d", "type": "text", "content": {"text": "You can put other Writer Framework components inside HTML Elements."}, "isCodeManaged": false, "position": 1, "parentId": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d"} +{"id": "c684f61e-0c79-4cb1-af9f-46c9cab5dfea", "type": "html", "content": {"element": "img", "styles": "{\n \"filter\": \"hue-rotate(calc(140deg + @{hue_rotation}deg))\"\n}", "attrs": "{ \"src\": \"static/pigeon1.jpg\"}"}, "isCodeManaged": false, "position": 0, "parentId": "a36b75bc-58e6-48ba-bdef-0824e6b21e8d"} +{"id": "919b0d26-ea9b-4364-b5af-865236b3fc3a", "type": "horizontalstack", "content": {"contentHAlign": "start"}, "isCodeManaged": false, "position": 0, "parentId": "ec5bc32e-1456-4abd-8d3e-97c640e32339"} +{"id": "0cf677a9-34f6-461a-a531-5578939400c7", "type": "text", "content": {"text": "Used to stack things horizontally."}, "isCodeManaged": false, "position": 2, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} +{"id": "e77448dd-4e7c-451c-8f31-4edde9bbf9f5", "type": "text", "content": {"text": "Horizontal Stack"}, "isCodeManaged": false, "position": 0, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} +{"id": "b4dc3e9c-ce01-4690-8e0e-e4357dbe70ad", "type": "separator", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "919b0d26-ea9b-4364-b5af-865236b3fc3a"} +{"id": "db4c66d6-1eb7-44d3-a2d4-65d0b3e5cf12", "type": "dataframe", "content": {"dataframe": "@{random_df}", "fontStyle": "monospace"}, "isCodeManaged": false, "position": 1, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} +{"id": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "type": "horizontalstack", "content": {"contentHAlign": "start"}, "isCodeManaged": false, "position": 0, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} +{"id": "fdf38e46-c01e-4a93-94d5-e187f9e4c823", "type": "text", "content": {"text": "_pgcf_ stands for \"Pigeon Coefficient\" and is a meaningless, randomly-generated value.", "useMarkdown": "yes", "primaryTextColor": "#8a8a8a"}, "isCodeManaged": false, "position": 2, "parentId": "85120b55-69c6-4b50-853a-bbbf73ff8121"} +{"id": "50a05488-d6fe-47bf-b681-36870d04f5d7", "type": "timer", "content": {"intervalMs": "500", "isActive": "no"}, "isCodeManaged": false, "position": 0, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d", "handlers": {"wf-tick": "handle_timer_tick"}} +{"id": "e296866a-75d2-4677-b55d-3c1456113b89", "type": "text", "content": {"text": "Refreshing automatically using a timer."}, "isCodeManaged": false, "position": 1, "parentId": "09ddb2da-6fa3-4157-8da3-4d5d44a6a58d"} +{"id": "7e625201-20c2-4b05-951c-d825de28b216", "type": "section", "content": {"title": "Filter data"}, "isCodeManaged": false, "position": 0, "parentId": "fbad9feb-5c88-4425-bb17-0d138286a875"} +{"id": "70d82458-a08f-4005-8f96-dc8d3ba92fad", "type": "section", "content": {"title": "About this app"}, "isCodeManaged": false, "position": 1, "parentId": "fbad9feb-5c88-4425-bb17-0d138286a875"} +{"id": "385247e5-5c89-4352-a598-b8da81146a5a", "type": "sliderinput", "content": {"label": "Minimum weight", "minValue": "300", "maxValue": "600", "stepSize": "0.1"}, "isCodeManaged": false, "position": 1, "parentId": "7e625201-20c2-4b05-951c-d825de28b216", "handlers": {"wf-number-change": "update"}, "binding": {"eventType": "wf-number-change", "stateRef": "filter.min_weight"}} +{"id": "10c156df-7464-4889-8b69-1b54cb1ee80a", "type": "sliderinput", "content": {"label": "Minimum length", "minValue": "25", "maxValue": "35", "stepSize": "1"}, "isCodeManaged": false, "position": 0, "parentId": "7e625201-20c2-4b05-951c-d825de28b216", "handlers": {"wf-number-change": "update"}, "binding": {"eventType": "wf-number-change", "stateRef": "filter.min_length"}} +{"id": "12e11005-3b5e-4bd8-9a8c-fc7b8cb757d0", "type": "text", "content": {"text": "This app is meant to serve as a lighthearted introduction to Writer Framework. It's not a comprehensive demonstration of its capabilities."}, "isCodeManaged": false, "position": 0, "parentId": "70d82458-a08f-4005-8f96-dc8d3ba92fad"} +{"id": "8ykyk5avd9ioyr6l", "type": "button", "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "content": {"text": "Create text widget"}, "handlers": {"wf-click": "create_text_widget"}, "position": 11} diff --git a/tests/backend/testapp/.wf/components-workflows_root.jsonl b/tests/backend/testapp/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/backend/testapp/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/backend/testapp/.wf/components-workflows_workflow-0-hywgzgfetx6rpiqz.jsonl b/tests/backend/testapp/.wf/components-workflows_workflow-0-hywgzgfetx6rpiqz.jsonl new file mode 100644 index 000000000..c310e2ec9 --- /dev/null +++ b/tests/backend/testapp/.wf/components-workflows_workflow-0-hywgzgfetx6rpiqz.jsonl @@ -0,0 +1,5 @@ +{"id": "hywgzgfetx6rpiqz", "type": "workflows_workflow", "content": {}, "isCodeManaged": false, "position": 0, "parentId": "workflows_root", "handlers": {}} +{"x": 248, "y": 204, "id": "rbh725i69ilo6gsr", "type": "workflows_setstate", "parentId": "hywgzgfetx6rpiqz", "content": {"element": "test", "value": "test"}, "handlers": {}, "position": 0, "outs": [{"toNodeId": "f052suq3dgzb5np7", "outId": "success"}, {"toNodeId": "f052suq3dgzb5np7", "outId": "success"}]} +{"x": 935, "y": 153, "id": "mw5rz7ay5p8pg2fm", "type": "workflows_writerclassification", "parentId": "hywgzgfetx6rpiqz", "content": {}, "handlers": {}, "position": 1} +{"x": 645, "y": 328, "id": "3cy7f577x6xsijiq", "type": "workflows_runworkflow", "parentId": "hywgzgfetx6rpiqz", "content": {}, "handlers": {}, "position": 2} +{"x": 497, "y": 80, "id": "c0v1pnroo32gfsye", "type": "workflows_httprequest", "parentId": "hywgzgfetx6rpiqz", "content": {}, "handlers": {}, "position": 3} diff --git a/tests/backend/testapp/main.py b/tests/backend/testapp/main.py index 7742d84b9..cbf88ce3d 100644 --- a/tests/backend/testapp/main.py +++ b/tests/backend/testapp/main.py @@ -8,6 +8,7 @@ import plotly.express as px import writer as wf import writer.core +from writer import WriterUIManager writer.Config.feature_flags.append("flag_one") writer.Config.feature_flags.append("flag_two") @@ -51,6 +52,11 @@ def update_cities(state, payload): "br": "Bristol" } +def create_text_widget(ui: WriterUIManager): + with ui.find('bb4d0e86-619e-4367-a180-be28ab6059f4'): + ui.Text({"text": "Hello world"}) + + logging.info("VERIFIERS") logging.info(writer.core.session_manager.verifiers) From cc6efb91ca95277c0ff0b191215d63dcc4bdf0b5 Mon Sep 17 00:00:00 2001 From: Fabien Arcellier Date: Tue, 8 Oct 2024 17:36:24 +0200 Subject: [PATCH 4/4] feat: prevent workflows tree to be sent during run mode * create default create_default_workflows_root * migrate e2e:presets --- alfred/apps.py | 10 ++++++++++ apps/ai-starter/.wf/components-workflows_root.jsonl | 1 + apps/hello/.wf/components-workflows_root.jsonl | 1 + apps/pdg-tutorial/.wf/components-workflows_root.jsonl | 1 + apps/quickstart/.wf/components-workflows_root.jsonl | 1 + apps/text-demo/.wf/components-workflows_root.jsonl | 1 + src/writer/app_runner.py | 3 +++ src/writer/wf_project.py | 6 ++++++ .../testbasicauth/.wf/components-workflows_root.jsonl | 1 + .../app1/.wf/components-workflows_root.jsonl | 1 + .../app2/.wf/components-workflows_root.jsonl | 1 + .../2columns/.wf/components-workflows_root.jsonl | 1 + .../presets/2pages/.wf/components-workflows_root.jsonl | 1 + .../empty_page/.wf/components-workflows_root.jsonl | 1 + .../jsonviewer/.wf/components-workflows_root.jsonl | 1 + .../low_code/.wf/components-workflows_root.jsonl | 1 + .../section/.wf/components-workflows_root.jsonl | 1 + .../presets/state/.wf/components-workflows_root.jsonl | 1 + 18 files changed, 34 insertions(+) create mode 100644 apps/ai-starter/.wf/components-workflows_root.jsonl create mode 100644 apps/hello/.wf/components-workflows_root.jsonl create mode 100644 apps/pdg-tutorial/.wf/components-workflows_root.jsonl create mode 100644 apps/quickstart/.wf/components-workflows_root.jsonl create mode 100644 apps/text-demo/.wf/components-workflows_root.jsonl create mode 100644 tests/backend/testbasicauth/.wf/components-workflows_root.jsonl create mode 100644 tests/backend/testmultiapp/app1/.wf/components-workflows_root.jsonl create mode 100644 tests/backend/testmultiapp/app2/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/2columns/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/2pages/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/empty_page/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/jsonviewer/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/low_code/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/section/.wf/components-workflows_root.jsonl create mode 100644 tests/e2e/presets/state/.wf/components-workflows_root.jsonl diff --git a/alfred/apps.py b/alfred/apps.py index 359dcf4aa..1f6d9ae57 100644 --- a/alfred/apps.py +++ b/alfred/apps.py @@ -24,6 +24,13 @@ def apps_update(app: str = None): 'tests/backend/testbasicauth', 'tests/backend/testmultiapp/app1', 'tests/backend/testmultiapp/app2', + 'tests/e2e/presets/2columns', + 'tests/e2e/presets/2pages', + 'tests/e2e/presets/empty_page', + 'tests/e2e/presets/jsonviewer', + 'tests/e2e/presets/low_code', + 'tests/e2e/presets/section', + 'tests/e2e/presets/state', ] for app in apps: @@ -35,6 +42,9 @@ def apps_update(app: str = None): print(f'{app} : migrate ui.json') wf_project.migrate_obsolete_ui_json(abs_path) + if not os.path.isfile(os.path.join(abs_path, ".wf", 'components-workflows_root.jsonl')): + wf_project.create_default_workflows_root(abs_path) + metadata, components = wf_project.read_files(abs_path) if metadata.get('writer_version') == writer.VERSION: print("The app is already up to date") diff --git a/apps/ai-starter/.wf/components-workflows_root.jsonl b/apps/ai-starter/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/apps/ai-starter/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/apps/hello/.wf/components-workflows_root.jsonl b/apps/hello/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/apps/hello/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/apps/pdg-tutorial/.wf/components-workflows_root.jsonl b/apps/pdg-tutorial/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/apps/pdg-tutorial/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/apps/quickstart/.wf/components-workflows_root.jsonl b/apps/quickstart/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/apps/quickstart/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/apps/text-demo/.wf/components-workflows_root.jsonl b/apps/text-demo/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/apps/text-demo/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/src/writer/app_runner.py b/src/writer/app_runner.py index 1355474fe..a39e055f4 100644 --- a/src/writer/app_runner.py +++ b/src/writer/app_runner.py @@ -699,6 +699,9 @@ def _load_persisted_components(self) -> Dict[str, ComponentDefinition]: if os.path.isfile(os.path.join(self.app_path, "ui.json")): wf_project.migrate_obsolete_ui_json(self.app_path) + if not os.path.isfile(os.path.join(self.app_path, ".wf", 'components-workflows_root.jsonl')): + wf_project.create_default_workflows_root(self.app_path) + if not os.path.isdir(os.path.join(self.app_path, ".wf")): logger.error("Couldn't find .wf in the path provided: %s.", self.app_path) sys.exit(1) diff --git a/src/writer/wf_project.py b/src/writer/wf_project.py index 5166d20af..d5c0b610e 100644 --- a/src/writer/wf_project.py +++ b/src/writer/wf_project.py @@ -135,3 +135,9 @@ def migrate_obsolete_ui_json(app_path: str) -> None: os.remove(os.path.join(app_path, "ui.json")) logger.warning('project format has changed and has been migrated with success. ui.json file has been removed.') + +def create_default_workflows_root(abs_path: str) -> None: + with io.open(os.path.join(abs_path, '.wf', 'components-workflows_root.jsonl'), 'w') as f: + f.write('{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}}') + logger = logging.getLogger('writer') + logger.warning('project format has changed and has been migrated with success. components-workflows_root.jsonl has been added.') diff --git a/tests/backend/testbasicauth/.wf/components-workflows_root.jsonl b/tests/backend/testbasicauth/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/backend/testbasicauth/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/backend/testmultiapp/app1/.wf/components-workflows_root.jsonl b/tests/backend/testmultiapp/app1/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/backend/testmultiapp/app1/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/backend/testmultiapp/app2/.wf/components-workflows_root.jsonl b/tests/backend/testmultiapp/app2/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/backend/testmultiapp/app2/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/2columns/.wf/components-workflows_root.jsonl b/tests/e2e/presets/2columns/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/2columns/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/2pages/.wf/components-workflows_root.jsonl b/tests/e2e/presets/2pages/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/2pages/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/empty_page/.wf/components-workflows_root.jsonl b/tests/e2e/presets/empty_page/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/empty_page/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/jsonviewer/.wf/components-workflows_root.jsonl b/tests/e2e/presets/jsonviewer/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/jsonviewer/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/low_code/.wf/components-workflows_root.jsonl b/tests/e2e/presets/low_code/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/low_code/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/section/.wf/components-workflows_root.jsonl b/tests/e2e/presets/section/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/section/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file diff --git a/tests/e2e/presets/state/.wf/components-workflows_root.jsonl b/tests/e2e/presets/state/.wf/components-workflows_root.jsonl new file mode 100644 index 000000000..4e9c84ea1 --- /dev/null +++ b/tests/e2e/presets/state/.wf/components-workflows_root.jsonl @@ -0,0 +1 @@ +{"id": "workflows_root", "type": "workflows_root", "content": {}, "isCodeManaged": false, "position": 0, "handlers": {}, "visible": {"expression": true, "binding": "", "reversed": false}} \ No newline at end of file