From f433f648eb9999e21a86ac146cfac413dc2acac8 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Thu, 15 Aug 2024 23:20:40 +0200 Subject: [PATCH] feat: Implement Data frame editor. WF-66 --- ...bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl | 7 +- apps/hello/.wf/metadata.json | 2 +- apps/hello/main.py | 39 ++- docs/framework/dataframe.mdx | 4 +- .../src/components/core/base/BaseDropdown.vue | 133 +++++++ .../components/core/content/CoreDataframe.vue | 331 ++++++++++++------ .../CoreDataframe/CoreDataframeCell.vue | 41 +++ .../CoreDataframeCellBoolean.vue | 50 +++ .../CoreDataframe/CoreDataframeCellNumber.vue | 73 ++++ .../CoreDataframe/CoreDataframeCellText.vue | 79 +++++ .../CoreDataframeCellUnknown.vue | 25 ++ .../CoreDataframe/CoreDataframeRow.vue | 76 ++++ .../core/content/CoreDataframe/constants.ts | 8 + .../CoreDataframe/useDataframeValueBroker.ts | 251 +++++++++++++ .../core/content/CoreDataframe/useJobs.ts | 37 ++ src/ui/src/core/index.ts | 7 +- src/ui/src/core/serializer.ts | 9 + src/writer/core.py | 110 +++++- tests/backend/test_core.py | 60 ++++ 19 files changed, 1228 insertions(+), 114 deletions(-) create mode 100644 src/ui/src/components/core/base/BaseDropdown.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeCell.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellBoolean.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellNumber.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellText.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue create mode 100644 src/ui/src/components/core/content/CoreDataframe/constants.ts create mode 100644 src/ui/src/components/core/content/CoreDataframe/useDataframeValueBroker.ts create mode 100644 src/ui/src/components/core/content/CoreDataframe/useJobs.ts create mode 100644 src/ui/src/core/serializer.ts diff --git a/apps/hello/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl b/apps/hello/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl index f3f7c9f71..b6e3c1774 100644 --- a/apps/hello/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl +++ b/apps/hello/.wf/components-page-0-bb4d0e86-619e-4367-a180-be28ab6059f4.jsonl @@ -4,7 +4,7 @@ {"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": "7fdd1d02-53de-4466-bd3c-4822cbc2694f", "type": "image", "content": {"src": "static/pigeon1.jpg?8", "caption": ""}, "isCodeManaged": false, "position": 0, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} +{"id": "7fdd1d02-53de-4466-bd3c-4822cbc2694f", "type": "image", "content": {"src": "static/pigeon1.jpg?8", "caption": ""}, "isCodeManaged": false, "position": 0, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "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"} @@ -74,3 +74,8 @@ {"id": "zfp1koasiuleygmz", "type": "pagination", "content": {"page": "@{paginated_members_page}", "pageSize": "@{paginated_members_page_size}", "totalItems": "@{paginated_members_total_items}", "pageSizeOptions": "1,2,5", "pageSizeShowAll": "no", "jumpTo": "no", "urlParam": "no"}, "isCodeManaged": false, "position": 1, "parentId": "e1ax8ctt8lrao0e4", "handlers": {"wf-change-page": "handle_paginated_members_page_change", "wf-change-page-size": "handle_paginated_members_page_size_change"}, "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "b27lw9ex8ig3x17p", "type": "tags", "content": {"tags": "{\n \"fiction\": \"fiction\",\n \"inspirational\": \"inspirational\",\n \"ai-generated\": \"ai-generated\"\n}", "seed": ""}, "isCodeManaged": false, "position": 2, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76", "visible": {"expression": true, "binding": "", "reversed": false}} {"id": "804e15bf-11a7-463d-8082-f46ea3acac1b", "type": "separator", "content": {}, "isCodeManaged": false, "position": 3, "parentId": "9c30af6d-4ee5-4782-9169-0f361d67fa76"} +{"id": "1sajvc1p1c293w26", "type": "dataframe", "content": {"dataframe": "@{editable_df}", "enableEdit": "yes", "enableRecordAdd": "yes", "enableRecordUpdate": "yes", "enableSearch": "yes", "showIndex": "yes", "actions": "{\n \"remove\": \"Remove\",\n \"open\": \"Open\",\n \"notify\": \"Notify\"\n}", "enableDownload": "yes"}, "isCodeManaged": false, "position": 1, "parentId": "axdad4l298047mxv", "handlers": {"wf-dataframe-add": "on_editable_df_record_add", "wf-dataframe-update": "on_editable_df_record_change", "wf-dataframe-action": "on_editable_df_record_action"}} +{"id": "xmbpsuxhu7om9165", "type": "text", "content": {"text": "Opened row text: @{editable_df_open_text}\n(use row action from the dataframe to open a row)"}, "isCodeManaged": false, "position": 2, "parentId": "axdad4l298047mxv", "handlers": {}} +{"id": "cqmyovmwxnxp7ffz", "type": "tabs", "content": {}, "isCodeManaged": false, "position": 1, "parentId": "0569937e-c72c-4fb9-820e-2ae56e17bcc0", "handlers": {}} +{"id": "axdad4l298047mxv", "type": "tab", "content": {"name": "Dataframe"}, "isCodeManaged": false, "position": 5, "parentId": "ee919cd6-8153-4f34-8c6a-bfc1153df360", "handlers": {}} +{"id": "3nluyquva0mt1a41", "type": "text", "content": {"text": "You can use Dataframe to visualize and edit Python Panda's data"}, "isCodeManaged": false, "position": 0, "parentId": "axdad4l298047mxv", "handlers": {}} diff --git a/apps/hello/.wf/metadata.json b/apps/hello/.wf/metadata.json index 1f76abe5a..d0c3a6a20 100644 --- a/apps/hello/.wf/metadata.json +++ b/apps/hello/.wf/metadata.json @@ -1,3 +1,3 @@ { - "writer_version": "0.7.0rc2" + "writer_version": "0.7.5" } \ No newline at end of file diff --git a/apps/hello/main.py b/apps/hello/main.py index fb11ecac0..d97a965a9 100644 --- a/apps/hello/main.py +++ b/apps/hello/main.py @@ -1,3 +1,4 @@ +import datetime import statistics import numpy as np @@ -65,6 +66,17 @@ def _get_main_df(): main_df = pd.read_csv("assets/main_df.csv") return main_df + +def _get_editable_df(): + + df = pd.DataFrame({ + 'number': [1, 2, 3], + 'boolean': [True, False, True], + 'object': [{"updatedAt": None}, {"updatedAt": None}, {"updatedAt": None}], + 'text': ['one', 'two', 'three'], + }) + return wf.EditableDataframe(df) + def _get_highlighted_members(): sample_df = _get_main_df().sample(3).set_index("name", drop=False) sample = sample_df.to_dict("index") @@ -126,11 +138,36 @@ def _update_scatter_chart(state): ) state["scatter_chart"] = fig -# STATE INIT +# UPDATES DATAFRAME + +# Subscribe this event handler to the `wf-dataframe-add` event +def on_editable_df_record_add(state, payload): + state['editable_df'].record_add(payload) + + +# Subscribe this event handler to the `wf-dataframe-update` event +def on_editable_df_record_change(state, payload): + payload['record']['object']['updatedAt'] = str(datetime.datetime.now()) + state['editable_df'].record_update(payload) + +# Subscribe this event handler to the `wf-dataframe-action` event +def on_editable_df_record_action(state, payload): + record_index = payload['record_index'] + if payload['action'] == 'remove': + state['editable_df'].record_remove(payload) + if payload['action'] == 'open': + state['editable_df_open_text'] = str(state['editable_df'].record(record_index)['text']) + if payload['action'] == 'notify': + state.add_notification("info", "Test", "Notify on this row : " + str(state['editable_df'].record(record_index))) + + +# STATE INIT initial_state = wf.init_state({ "main_df": _get_main_df(), + "editable_df": _get_editable_df(), + "editable_df_open_text": "", "highlighted_members": _get_highlighted_members(), "random_df": _generate_random_df(), "hue_rotation": 26, diff --git a/docs/framework/dataframe.mdx b/docs/framework/dataframe.mdx index 77414035d..ec52caf57 100644 --- a/docs/framework/dataframe.mdx +++ b/docs/framework/dataframe.mdx @@ -70,9 +70,9 @@ def on_record_action(state, payload): This event corresponds to a quick action in the drop-down menu to the left of the dataframe. """ record_index = payload['record_index'] - if payload.action == 'remove': + if payload['action'] == 'remove': state['mydf'].record_remove(payload) - if payload.action == 'open': + if payload['action'] == 'open': state['record'] = state['df'].record(record_index) # dict representation of record ``` diff --git a/src/ui/src/components/core/base/BaseDropdown.vue b/src/ui/src/components/core/base/BaseDropdown.vue new file mode 100644 index 000000000..32d62e905 --- /dev/null +++ b/src/ui/src/components/core/base/BaseDropdown.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/ui/src/components/core/content/CoreDataframe.vue b/src/ui/src/components/core/content/CoreDataframe.vue index cddbe3073..fa9470da4 100644 --- a/src/ui/src/components/core/content/CoreDataframe.vue +++ b/src/ui/src/components/core/content/CoreDataframe.vue @@ -1,5 +1,5 @@