Skip to content

Commit

Permalink
Merge pull request #258 from mmikita95/chore-components-in-event-payload
Browse files Browse the repository at this point in the history
chore: prepare UI component handling for enhanced session and CMC integration
  • Loading branch information
ramedina86 authored Mar 1, 2024
2 parents d295165 + df9aced commit a36f5ec
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
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

0 comments on commit a36f5ec

Please sign in to comment.