From c8cdf4db4797603ca9cbf30a6a793835a745c1d9 Mon Sep 17 00:00:00 2001 From: mikebender Date: Tue, 21 Nov 2023 15:41:39 -0500 Subject: [PATCH] A little cleanup from self-review --- plugins/ui/DESIGN.md | 36 +++++++++---------- .../deephaven/ui/components/make_component.py | 9 +++-- .../ui/object_types/ElementMessageStream.py | 30 ++++++++++++++++ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index e92ce6501..9ac7187d0 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1436,13 +1436,13 @@ use_table_listener( ###### Parameters -| Parameter | Type | Description | -|---------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `table` | `Table` | The table to listen to. | -| `listener` | `Callable[[TableUpdate, bool], None] \| TableListener` | Either a function or a [TableListener](https://deephaven.io/core/pydoc/code/deephaven.table_listener.html#deephaven.table_listener.TableListener) with an on_update function. The function must take a [TableUpdate](https://deephaven.io/core/pydoc/code/deephaven.table_listener.html#deephaven.table_listener.TableUpdate) and is_replay bool. [More table listener info](https://deephaven.io/core/docs/how-to-guides/table-listeners-python/) | -| `description` | `str \| None` | An optional description for the UpdatePerformanceTracker to append to the listener’s entry description, default is None. -| `do_replay` | `bool` | Whether to replay the initial snapshot of the table, default is False. | -| `replay_lock` | `LockType` | The lock type used during replay, default is ‘shared’, can also be ‘exclusive’. | +| Parameter | Type | Description | +| ------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `table` | `Table` | The table to listen to. | +| `listener` | `Callable[[TableUpdate, bool], None] \| TableListener` | Either a function or a [TableListener](https://deephaven.io/core/pydoc/code/deephaven.table_listener.html#deephaven.table_listener.TableListener) with an on_update function. The function must take a [TableUpdate](https://deephaven.io/core/pydoc/code/deephaven.table_listener.html#deephaven.table_listener.TableUpdate) and is_replay bool. [More table listener info](https://deephaven.io/core/docs/how-to-guides/table-listeners-python/) | +| `description` | `str \| None` | An optional description for the UpdatePerformanceTracker to append to the listener’s entry description, default is None. | +| `do_replay` | `bool` | Whether to replay the initial snapshot of the table, default is False. | +| `replay_lock` | `LockType` | The lock type used during replay, default is ‘shared’, can also be ‘exclusive’. | #### Custom Types @@ -1515,7 +1515,6 @@ class LinkPoint(TypedDict): | - #### Context By default, the context of a `@ui.component` will be created per client session (same as [Parameterized Query's "parallel universe" today](https://github.com/deephaven-ent/iris/blob/868b868fc9e180ee948137b10b6addbac043605e/ParameterizedQuery/src/main/java/io/deephaven/query/parameterized/impl/ParameterizedQueryServerImpl.java#L140)). However, it would be interesting if it were possible to share a context among all sessions for the current user, and/or share a context with other users even; e.g. if one user selects and applies a filter, it updates immediately for all other users with that dashboard open. So three cases: @@ -1650,15 +1649,15 @@ sequenceDiagram UIP->>SP: Render tft SP->>SP: Run sym_exchange Note over SP: sym_exchange executes, running text_filter_table twice - SP-->>UIP: Result (flex([tft1, tft2])) - UIP-->>W: Display (flex([tft1, tft2])) + SP-->>UIP: Result (document=flex([tft1, tft2]), exported_objects=[tft1, tft2]) + UIP-->>W: Display Result U->>UIP: Change text input 1 UIP->>SP: Change state SP->>SP: Run sym_exchange - Note over SP: sym_exchange executes, text_filter_table only
runs once for the one changed input - SP-->>UIP: Result (flex([tft1', tft2])) - UIP-->>W: Display (flex([tft1', tft2])) + Note over SP: sym_exchange executes, text_filter_table only
runs once for the one changed input
only exports the new table, as client already has previous tables + SP-->>UIP: Result (document=flex([tft1', tft2], exported_objects=[tft1'])) + UIP-->>W: Display Result ``` ##### Communication/Callbacks @@ -1685,12 +1684,11 @@ sequenceDiagram A component that is created on the server side runs through a few steps before it is rendered on the client side: -1. Element - The basis for all UI components. Generally a `FunctionElement`, and does not run the function until it is requested by the UI. The result can change depending on the context that it is rendered in (e.g. what "state" is set). -2. RenderedNode - After an element has been rendered using a renderer, it becomes a `RenderedNode`. This is an immutable representation of the document. -3. JSONEncodedNode - The `RenderedNode` is then encoded into JSON using `NodeEncoder`. It pulls out all the objects and maps them to exported objects, and all the callables to be mapped to commands that can be accepted by JSON-RPC. This is the final representation of the document that is sent to the client. -4. ElementPanel - Client side where it's receiving the `documentUpdated` from the server plugin, and then rendering the `JSONEncodedNode` into a `ElementPanel` (e.g. a `GoldenLayout` panel). Decodes the JSON, maps all the exported objects to the actual objects, and all the callables to async methods that will call to the server. -5. ElementView - Renders the decoded panel into the UI. Picks the element based on the name of it. -6. ObjectView - Render an exported object +1. [Element](./src/deephaven/ui/elements/Element.py) - The basis for all UI components. Generally a [FunctionElement](./src/deephaven/ui/elements/FunctionElement.py) created by a script using the [@ui.component](./src/deephaven/ui/components/make_component.py) decorator, and does not run the function until it is rendered. The result can change depending on the context that it is rendered in (e.g. what "state" is set). +2. [ElementMessageStream](./src/deephaven/ui/object_types/ElementMessageStream.py) - The `ElementMessageStream` is responsible for rendering one instance of an element in a specific rendering context and handling the server-client communication. The element is rendered to create a [RenderedNode](./src/deephaven/ui/renderer/RenderedNode.py), which is an immutable representation of a rendered document. The `RenderedNode` is then encoded into JSON using [NodeEncoder](./src/deephaven/ui/renderer/NodeEncoder.py), which pulls out all the non-serializable objects (such as Tables) and maps them to exported objects, and all the callables to be mapped to commands that can be accepted by JSON-RPC. This is the final representation of the document that is sent to the client, and ultimately handled by the `WidgetHandler`. +3. [DashboardPlugin](./src/js/src/DashboardPlugin.tsx) - Client side `DashboardPlugin` that listens for when a widget of type `Element` is opened, and manage the `WidgetHandler` instances that are created for each widget. +4. [WidgetHandler](./src/js/src/WidgetHandler.tsx) - Uses JSON-RPC communication with an `ElementMessageStream` instance to load the initial rendered document and associated exported objects. Listens for any changes and updates the document accordingly. +5. [DocumentHandler](./src/js/src/DocumentHandler.tsx) - Handles the root of a rendered document, laying out the appropriate panels or dashboard specified. #### Other Decisions diff --git a/plugins/ui/src/deephaven/ui/components/make_component.py b/plugins/ui/src/deephaven/ui/components/make_component.py index 326873686..b7e43c652 100644 --- a/plugins/ui/src/deephaven/ui/components/make_component.py +++ b/plugins/ui/src/deephaven/ui/components/make_component.py @@ -1,18 +1,23 @@ import functools import logging +from typing import Any, Callable from .._internal import get_component_qualname from ..elements import FunctionElement logger = logging.getLogger(__name__) -def make_component(func): +def make_component(func: Callable[..., Any]): """ Create a FunctionalElement from the passed in function. + + Args: + func: The function to create a FunctionalElement from. + Runs when the component is being rendered. """ @functools.wraps(func) - def make_component_node(*args, **kwargs): + def make_component_node(*args: Any, **kwargs: Any): component_type = get_component_qualname(func) return FunctionElement(component_type, lambda: func(*args, **kwargs)) diff --git a/plugins/ui/src/deephaven/ui/object_types/ElementMessageStream.py b/plugins/ui/src/deephaven/ui/object_types/ElementMessageStream.py index 2cd23cc07..627bac70d 100644 --- a/plugins/ui/src/deephaven/ui/object_types/ElementMessageStream.py +++ b/plugins/ui/src/deephaven/ui/object_types/ElementMessageStream.py @@ -13,6 +13,36 @@ class ElementMessageStream(MessageStream): + _manager: JSONRPCResponseManager + """ + Handle incoming requests from the client. + """ + + _dispatcher: Dispatcher + """ + The dispatcher to use when client calls callables. + """ + + _encoder: NodeEncoder + """ + Encoder to use to encode the document. + """ + + _message_id: int + """ + The next message ID to use. + """ + + _element: Element + """ + The element to render. + """ + + _connection: MessageStream + """ + The connection to send the rendered element to. + """ + def __init__(self, element: Element, connection: MessageStream): """ Create a new ElementMessageStream. Renders the element in a render context, and sends the rendered result to the