Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: prepare UI component handling for enhanced session and CMC integration #258

Merged
merged 7 commits into from
Mar 1, 2024
1 change: 1 addition & 0 deletions src/streamsync/app_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def _handle_event(self, session: StreamsyncSession, event: StreamsyncEvent) -> E
res_payload = EventResponsePayload(
result=result,
mutations=mutations,
components=session.session_component_tree.to_dict(),
mail=mail
)

Expand Down
80 changes: 39 additions & 41 deletions src/streamsync/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import json
import math
from streamsync.ss_types import Readable, InstancePath, StreamsyncEvent, StreamsyncEventResult, StreamsyncFileItem
from pydantic import BaseModel, Field


class Config:
Expand Down Expand Up @@ -475,41 +476,32 @@ def call_frontend_function(self, module_key: str, function_name: str, args: List

# TODO Consider switching Component to use Pydantic

class Component:

def __init__(self, id: str, type: str, content: Dict[str, str] = {}):
self.id = id
self.type = type
self.content = content
self.position: int = 0
self.parentId: Optional[str] = None
self.handlers: Optional[Dict[str, str]] = None
self.visible: Optional[bool] = None
self.binding: Optional[Dict] = None
class Component(BaseModel):
id: str
type: str
content: Dict[str, str] = Field(default_factory=dict)
flag: Optional[str] = None
position: int = 0
parentId: Optional[str] = None
handlers: Optional[Dict[str, str]] = None
visible: Optional[Union[bool, str]] = None
binding: Optional[Dict] = None

def to_dict(self) -> Dict:
c_dict = {
"id": self.id,
"type": self.type,
"content": self.content,
"parentId": self.parentId,
"position": self.position,
}
if self.handlers is not None:
c_dict["handlers"] = self.handlers
if self.binding is not None:
c_dict["binding"] = self.binding
if self.visible is not None:
c_dict["visible"] = self.visible
return c_dict
"""
Wrapper for model_dump to ensure backward compatibility.
"""
return self.model_dump(exclude_none=True)


class ComponentTree:

def __init__(self) -> None:
self.counter: int = 0
self.components: Dict[str, Component] = {}
root_component = Component("root", "root", {})
root_component = Component(
id="root", type="root", content={}
)
self.attach(root_component)

def get_component(self, component_id: str) -> Optional[Component]:
Expand All @@ -536,21 +528,15 @@ def ingest(self, serialised_components: Dict[str, Any]) -> None:
continue
self.components.pop(component_id)
for component_id, sc in serialised_components.items():
component = Component(
component_id, sc["type"], sc["content"])
component.parentId = sc.get("parentId")
component.handlers = sc.get("handlers")
component.position = sc.get("position")
component.visible = sc.get("visible")
component.binding = sc.get("binding")
component = Component(**sc)
self.components[component_id] = component

def to_dict(self) -> Dict:
active_components = {}
for id, component in self.components.items():
active_components[id] = component.to_dict()
return active_components


class SessionComponentTree(ComponentTree):

Expand All @@ -559,15 +545,27 @@ def __init__(self, base_component_tree: ComponentTree):
self.base_component_tree = base_component_tree

def get_component(self, component_id: str) -> Optional[Component]:
base_component = self.base_component_tree.get_component(component_id)
if base_component:
return base_component
return self.components.get(component_id)
# Check if session component tree contains requested key
session_component_present = component_id in self.components

if session_component_present:
# If present, return session component (even if it's None)
session_component = self.components.get(component_id)
return session_component

# Otherwise, try to obtain the base tree component
return self.base_component_tree.get_component(component_id)

def to_dict(self) -> Dict:
active_components = {}
for id, component in {**self.components, **self.base_component_tree.components}.items():
active_components[id] = component.to_dict()
active_components = {
# Collecting serialized base tree components
component_id: base_component.to_dict()
for component_id, base_component
in self.base_component_tree.components.items()
}
for component_id, session_component in self.components.items():
# Overriding base tree components with session-specific ones
active_components[component_id] = session_component.to_dict()
return active_components


Expand Down
1 change: 1 addition & 0 deletions src/streamsync/ss_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class EventResponsePayload(BaseModel):
result: Any
mutations: Dict[str, Any]
mail: List
components: Dict


class StateEnquiryResponsePayload(BaseModel):
Expand Down
21 changes: 20 additions & 1 deletion ui/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ export function generateCore() {
});
}

function ingestComponents(newComponents: Record<string, any>) {
if (!newComponents) return;
components.value = newComponents
}

function clearFrontendMap() {
frontendMessageMap.value.forEach(({ callback }) => {
callback?.({ ok: false });
Expand Down Expand Up @@ -198,6 +203,7 @@ export function generateCore() {
) {
ingestMutations(message.payload?.mutations);
collateMail(message.payload?.mail);
ingestComponents(message.payload?.components);
}

const mapItem = frontendMessageMap.value.get(message.trackingId);
Expand Down Expand Up @@ -429,8 +435,21 @@ export function generateCore() {
* @returns
*/
async function sendComponentUpdate(): Promise<void> {
/*
Ensure that the backend receives only components
created by the frontend (Builder-managed components, BMC),
and not the components it generated (Code-managed components, CMC).
*/

const builderManagedComponents = {};

Object.entries(components.value).forEach(([componentId, component]) => {
if (component.flag === 'cmc') return;
builderManagedComponents[componentId] = component;
});

const payload = {
components: components.value,
components: builderManagedComponents,
};

return new Promise((resolve, reject) => {
Expand Down
1 change: 1 addition & 0 deletions ui/src/streamsyncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Component = {
type: string;
position: number;
content: Record<string, string>;
flag?: string;
handlers?: Record<string, string>;
visible?: boolean | string;
binding?: {
Expand Down
Loading