diff --git a/app/web/src/components/ManagementRunPrototype.vue b/app/web/src/components/ManagementRunPrototype.vue index c7552f491d..a781340205 100644 --- a/app/web/src/components/ManagementRunPrototype.vue +++ b/app/web/src/components/ManagementRunPrototype.vue @@ -7,7 +7,18 @@ ) " > - + + + + + + + {{ `Run ${props.prototype.label}` }} @@ -31,6 +42,8 @@ import { ref, computed, onMounted, watch } from "vue"; import clsx from "clsx"; import { useToast } from "vue-toastification"; import { + DropdownMenu, + DropdownMenuItem, IconButton, themeClasses, TruncateWithTooltip, @@ -44,6 +57,8 @@ import { import { useComponentsStore } from "@/store/components.store"; import { FuncRunId } from "@/store/func_runs.store"; import { useManagementRunsStore } from "@/store/management_runs.store"; +import { useViewsStore } from "@/store/views.store"; +import { ViewId } from "@/api/sdf/dal/views"; import { DiagramGroupData, DiagramNodeData, @@ -56,6 +71,9 @@ const componentsStore = useComponentsStore(); const router = useRouter(); const toast = useToast(); const managementRunsStore = useManagementRunsStore(); +const viewsStore = useViewsStore(); + +const viewSelectorMenuRef = ref>(); const lastExecution = ref(undefined); @@ -89,16 +107,25 @@ const latestRunId = computed(() => ), ); +const componentViews = computed(() => + Object.keys(viewsStore.viewsById).filter( + (viewId) => + !!viewsStore.viewsById[viewId]?.components[props.component.def.id] || + !!viewsStore.viewsById[viewId]?.groups[props.component.def.id], + ), +); + watch(latestRunId, (latest) => { if (latest) { emit("runUpdated", latest); } }); -const runPrototype = async () => { +const runPrototype = async (viewId: ViewId) => { const result = await funcStore.RUN_MGMT_PROTOTYPE( props.prototype.managementPrototypeId, props.component.def.id, + viewId, ); if (result.result.success) { @@ -123,6 +150,16 @@ const runPrototype = async () => { } }; +const runClick = async (e?: MouseEvent) => { + if (componentViews.value.length === 1 && componentViews.value[0]) { + await runPrototype(componentViews.value[0]); + } else if (componentViews.value.length > 1) { + viewSelectorMenuRef.value?.open(e, false); + } else if (viewsStore.selectedViewId) { + await runPrototype(viewsStore.selectedViewId); + } +}; + function onClickView() { router.push({ name: "workspace-lab-assets", diff --git a/app/web/src/components/ModelingView/ModelingRightClickMenu.vue b/app/web/src/components/ModelingView/ModelingRightClickMenu.vue index 53f4df395d..ab59e8abc2 100644 --- a/app/web/src/components/ModelingView/ModelingRightClickMenu.vue +++ b/app/web/src/components/ModelingView/ModelingRightClickMenu.vue @@ -433,10 +433,12 @@ const rightClickMenuItems = computed(() => { const runManagementFunc = async (prototype: MgmtPrototype) => { if (!selectedComponent.value) return; + if (!viewStore.selectedViewId) return; const result = await funcStore.RUN_MGMT_PROTOTYPE( prototype.managementPrototypeId, selectedComponent.value.def.id, + viewStore.selectedViewId, ); if (result.result.success && result.result.data.message) { diff --git a/app/web/src/store/func/funcs.store.ts b/app/web/src/store/func/funcs.store.ts index 59fed9bc37..26d6f8e128 100644 --- a/app/web/src/store/func/funcs.store.ts +++ b/app/web/src/store/func/funcs.store.ts @@ -31,6 +31,7 @@ import { useAssetStore } from "@/store/asset.store"; import { SchemaVariant, SchemaVariantId } from "@/api/sdf/dal/schema"; import { DefaultMap } from "@/utils/defaultmap"; import { ComponentId } from "@/api/sdf/dal/component"; +import { ViewId } from "@/api/sdf/dal/views"; import { useChangeSetsStore } from "../change_sets.store"; import { useRealtimeStore } from "../realtime/realtime.store"; import { useComponentsStore } from "../components.store"; @@ -287,6 +288,7 @@ export const useFuncStore = () => { async RUN_MGMT_PROTOTYPE( prototypeId: ManagementPrototypeId, componentId: ComponentId, + viewId: ViewId, ) { return new ApiRequest({ method: "post", @@ -296,6 +298,7 @@ export const useFuncStore = () => { "prototype", { prototypeId }, { componentId }, + { viewId }, ]), }); }, diff --git a/bin/lang-js/src/component.ts b/bin/lang-js/src/component.ts index 88eefcab47..c1e59c2a44 100644 --- a/bin/lang-js/src/component.ts +++ b/bin/lang-js/src/component.ts @@ -4,13 +4,13 @@ export interface Component { } export interface Geometry { - x: number; - y: number; + x?: number; + y?: number; width?: number; height?: number; } export interface ComponentWithGeometry { properties: Record; - geometry: Geometry; + geometry: { [key: string]: Geometry }; } diff --git a/bin/lang-js/src/function_kinds/management.ts b/bin/lang-js/src/function_kinds/management.ts index fad0774210..1fe94e78db 100644 --- a/bin/lang-js/src/function_kinds/management.ts +++ b/bin/lang-js/src/function_kinds/management.ts @@ -13,6 +13,7 @@ import { RequestCtx } from "../request"; const debug = Debug("langJs:management"); export interface ManagementFunc extends Func { + currentView: string; thisComponent: ComponentWithGeometry; components: { [key: string]: ComponentWithGeometry; @@ -20,8 +21,8 @@ export interface ManagementFunc extends Func { } export type ManagementFuncResult = - | ManagementFuncResultSuccess - | ManagementFuncResultFailure; + | ManagementFuncResultSuccess + | ManagementFuncResultFailure; export interface ManagmentConnect { from: string, @@ -32,22 +33,26 @@ export interface ManagmentConnect { } export interface ManagementOperations { - create?: { [key: string]: { - kind: string; - properties?: object; - geometry?: Geometry; - parent?: string; - connect?: ManagmentConnect[], - } }; - update?: { [key: string]: { - properties?: object; - geometry?: Geometry; - parent?: string; - connect: { - add?: ManagmentConnect[], - remove?: ManagmentConnect[], + create?: { + [key: string]: { + kind: string; + properties?: object; + geometry?: Geometry; + parent?: string; + connect?: ManagmentConnect[], } - } }; + }; + update?: { + [key: string]: { + properties?: object; + geometry?: { [key: string]: Geometry }; + parent?: string; + connect: { + add?: ManagmentConnect[], + remove?: ManagmentConnect[], + } + } + }; actions?: { [key: string]: { add?: string[], @@ -66,14 +71,14 @@ export interface ManagementFuncResultFailure extends ResultFailure { } async function execute( vm: NodeVM, { executionId }: RequestCtx, - { thisComponent, components }: ManagementFunc, + { thisComponent, components, currentView }: ManagementFunc, code: string, ): Promise { let managementResult: Record | undefined | null; try { const runner = vm.run(code); managementResult = await new Promise((resolve) => { - runner({ thisComponent, components }, (resolution: Record) => resolve(resolution)); + runner({ thisComponent, components, currentView }, (resolution: Record) => resolve(resolution)); }); } catch (err) { return failureExecution(err as Error, executionId); diff --git a/lib/cyclone-client/src/client.rs b/lib/cyclone-client/src/client.rs index f675a60584..c362b304c8 100644 --- a/lib/cyclone-client/src/client.rs +++ b/lib/cyclone-client/src/client.rs @@ -1325,6 +1325,7 @@ mod tests { let req = ManagementRequest { execution_id: "1234".to_string(), handler: "manage".to_string(), + current_view: "DEFAULT".to_string(), this_component: ComponentViewWithGeometry { kind: None, properties: serde_json::json!({"it": "is", "a": "principle", "of": "music", "to": "repeat the theme"}), @@ -1409,6 +1410,7 @@ mod tests { let req = ManagementRequest { execution_id: "1234".to_string(), handler: "manage".to_string(), + current_view: "DEFAULT".to_string(), this_component: ComponentViewWithGeometry { kind: None, properties: serde_json::json!({"it": "is", "a": "principle", "of": "music", "to": "repeat the theme"}), diff --git a/lib/cyclone-core/src/management.rs b/lib/cyclone-core/src/management.rs index 358875ba68..0b7d7bc593 100644 --- a/lib/cyclone-core/src/management.rs +++ b/lib/cyclone-core/src/management.rs @@ -12,6 +12,7 @@ pub struct ManagementRequest { pub execution_id: String, pub handler: String, pub code_base64: String, + pub current_view: String, pub this_component: ComponentViewWithGeometry, pub components: HashMap, pub before: Vec, diff --git a/lib/dal-test/src/expected.rs b/lib/dal-test/src/expected.rs index 260658ba6b..a8be0e89cc 100644 --- a/lib/dal-test/src/expected.rs +++ b/lib/dal-test/src/expected.rs @@ -880,4 +880,8 @@ impl ExpectView { let name = generate_fake_name(); View::new(ctx, name).await.expect("create view") } + + pub async fn create_with_name(ctx: &DalContext, name: &str) -> View { + View::new(ctx, name).await.expect("create view") + } } diff --git a/lib/dal-test/src/test_exclusive_schemas/legos/small.rs b/lib/dal-test/src/test_exclusive_schemas/legos/small.rs index 7ca2bb8a6a..5dcafa9cfd 100644 --- a/lib/dal-test/src/test_exclusive_schemas/legos/small.rs +++ b/lib/dal-test/src/test_exclusive_schemas/legos/small.rs @@ -149,6 +149,44 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( let update_mgmt_func_name = "test:updateManagedComponent"; let update_mgmt_func = build_management_func(update_managed_func_code, update_mgmt_func_name)?; + let update_managed_func_in_view_code = r#" + async function main({ thisComponent, components, currentView }: Input): Promise { + const thisName = thisComponent.properties?.si?.name ?? "unknown"; + + const update: { [key: string]: unknown } = {}; + + for (let [id, component] of Object.entries(components)) { + let name = component.properties?.si?.name ?? "unknown"; + update[id] = { + properties: { + ...component.properties, + si: { + ...component.properties?.si, + name: `${name} managed by ${thisName}`, + } + }, + geometry: { + [currentView]: { + x: 1000, + y: 750, + } + } + }; + } + + return { + status: "ok", + message: currentView, + ops: { update }, + } + } + "#; + let update_in_view_mgmt_func_name = "test:updateManagedComponentInView"; + let update_in_view_mgmt_func = build_management_func( + update_managed_func_in_view_code, + update_in_view_mgmt_func_name, + )?; + let create_and_connect_from_self_func_code = r#" async function main({ thisComponent, components }: Input): Promise { const thisName = thisComponent.properties?.si?.name ?? "unknown"; @@ -353,6 +391,15 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( .func_unique_id(&update_mgmt_func.unique_id) .build()?, ) + .management_func( + ManagementFuncSpec::builder() + .name("Update in View") + .managed_schemas(Some(HashSet::from([ + SCHEMA_ID_SMALL_EVEN_LEGO.to_string() + ]))) + .func_unique_id(&update_in_view_mgmt_func.unique_id) + .build()?, + ) .management_func( ManagementFuncSpec::builder() .name("Create and Connect From Self") @@ -395,6 +442,7 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( .func(import_management_func) .func(clone_me_mgmt_func) .func(update_mgmt_func) + .func(update_in_view_mgmt_func) .func(create_and_connect_from_self_func) .func(create_and_connect_to_self_func) .func(create_and_connect_to_self_as_children_func) diff --git a/lib/dal/src/diagram/geometry.rs b/lib/dal/src/diagram/geometry.rs index 0a42790d9e..9d496c52d9 100644 --- a/lib/dal/src/diagram/geometry.rs +++ b/lib/dal/src/diagram/geometry.rs @@ -62,20 +62,20 @@ impl Geometry { self.id } - pub fn x(&self) -> &isize { - &self.x + pub fn x(&self) -> isize { + self.x } - pub fn y(&self) -> &isize { - &self.y + pub fn y(&self) -> isize { + self.y } - pub fn width(&self) -> Option<&isize> { - self.width.as_ref() + pub fn width(&self) -> Option { + self.width } - pub fn height(&self) -> Option<&isize> { - self.height.as_ref() + pub fn height(&self) -> Option { + self.height } fn assemble(node_weight: GeometryNodeWeight, content: GeometryContent) -> Self { diff --git a/lib/dal/src/func/backend/management.rs b/lib/dal/src/func/backend/management.rs index f9ccb6b17b..f9fa4d66e3 100644 --- a/lib/dal/src/func/backend/management.rs +++ b/lib/dal/src/func/backend/management.rs @@ -15,6 +15,7 @@ use super::ExtractPayload; pub struct FuncBackendManagementArgs { this_component: ComponentViewWithGeometry, components: HashMap, + current_view: String, } #[derive(Debug)] @@ -41,6 +42,7 @@ impl FuncDispatch for FuncBackendManagement { code_base64: code_base64.into(), this_component: args.this_component, components: args.components, + current_view: args.current_view, before, }; diff --git a/lib/dal/src/func/binding/management.rs b/lib/dal/src/func/binding/management.rs index 9f907eb789..235e4ac142 100644 --- a/lib/dal/src/func/binding/management.rs +++ b/lib/dal/src/func/binding/management.rs @@ -54,9 +54,13 @@ impl ManagementBinding { ) -> FuncBindingResult { let default_this_component = "object".to_string(); let default_component_types = "object".to_string(); - let default_types = (default_this_component, default_component_types.to_owned()); + let default_types = ( + default_this_component, + default_component_types.to_owned(), + default_component_types.to_owned(), + ); - let (this_component_iface, component_types) = + let (this_component_iface, component_create_type, component_input_type) = match ManagementPrototype::prototype_id_for_func_id(ctx, func_id).await? { Some(prototype_id) => { match ManagementPrototype::get_by_id(ctx, prototype_id).await? { @@ -72,7 +76,8 @@ impl ManagementBinding { let (_, reverse_map) = prototype.managed_schemas_map(ctx).await?; - let mut component_types = vec![]; + let mut component_create_types = vec![]; + let mut component_update_types = vec![]; for (schema_id, name) in reverse_map { let variant_id = Schema::get_or_install_default_variant(ctx, schema_id).await?; @@ -85,7 +90,25 @@ impl ManagementBinding { let sv_type = root_prop.ts_type(ctx).await?; - let component_type = format!( + let component_update_type = format!( + r#" + {{ + kind: "{name}", + properties?: {sv_type}, + geometry?: {{ [key: string]: Geometry }}, + connect?: {{ + from: string, + to: {{ + component: string; + socket: string; + }} + }}[], + parent?: string, + }} + "# + ); + + let component_create_type = format!( r#" {{ kind: "{name}", @@ -102,12 +125,18 @@ impl ManagementBinding { }} "# ); - component_types.push(component_type); + component_create_types.push(component_create_type); + component_update_types.push(component_update_type); } - let component_types = component_types.join("|\n"); + let component_create_type = component_create_types.join("|\n"); + let component_input_type = component_update_types.join("|\n"); - (root_prop.ts_type(ctx).await?, component_types) + ( + root_prop.ts_type(ctx).await?, + component_create_type, + component_input_type, + ) } None => default_types, } @@ -118,18 +147,18 @@ impl ManagementBinding { Ok(format!( r#" type Geometry = {{ - x: number, - y: number, + x?: number, + y?: number, width?: number, height?: number, }}; type Output = {{ status: 'ok' | 'error'; ops?: {{ - create?: {{ [key: string]: {component_types} }}, + create?: {{ [key: string]: {component_create_type} }}, update?: {{ [key: string]: {{ properties?: {{ [key: string]: unknown }}, - geometry?: Geometry, + geometry?: {{ [key: string]: Geometry }}, connect?: {{ add?: {{ from: string, to: {{ component: string; socket: string; }} }}[], remove?: {{ from: string, to: {{ component: string; socket: string; }} }}[], @@ -144,11 +173,12 @@ type Output = {{ message?: string | null; }}; type Input = {{ + currentView: string, thisComponent: {{ properties: {this_component_iface}, - geometry: Geometry, + geometry: {{ [key: string]: Geometry }}, }}, - components: {{ [key: string]: {component_types} }} + components: {{ [key: string]: {component_input_type} }} }};"# )) } diff --git a/lib/dal/src/management/mod.rs b/lib/dal/src/management/mod.rs index d96e9c4b2f..9a03aec9ce 100644 --- a/lib/dal/src/management/mod.rs +++ b/lib/dal/src/management/mod.rs @@ -7,6 +7,7 @@ use thiserror::Error; use veritech_client::{ManagementFuncStatus, ManagementResultSuccess}; use crate::component::frame::{Frame, FrameError}; +use crate::diagram::geometry::Geometry; use crate::diagram::view::{View, ViewId}; use crate::diagram::{DiagramError, SummaryDiagramManagementEdge}; use crate::{ @@ -89,15 +90,18 @@ pub type ManagementResult = Result; /// Geometry type for deserialization lang-js, so even if we should only care about integers, /// until we implement custom deserialization we can't merge it with [RawGeometry](RawGeometry) #[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq)] -pub struct NumericGeometry { - pub x: f64, - pub y: f64, +pub struct ManagementGeometry { + pub x: Option, + pub y: Option, pub width: Option, pub height: Option, } -impl NumericGeometry { - pub fn offset_by(&self, mut x_off: f64, mut y_off: f64) -> Self { +impl ManagementGeometry { + pub fn offset_by(&self, x_off: Option, y_off: Option) -> Self { + let mut x_off = x_off.unwrap_or(0.0); + let mut y_off = y_off.unwrap_or(0.0); + if !x_off.is_normal() { x_off = 0.0; } @@ -105,21 +109,24 @@ impl NumericGeometry { y_off = 0.0; } - let x = if self.x.is_normal() { - self.x + x_off + let self_x = self.x.unwrap_or(0.0); + let self_y = self.y.unwrap_or(0.0); + + let x = if self_x.is_normal() { + self_x + x_off } else { x_off }; - let y = if self.y.is_normal() { - self.y + y_off + let y = if self_y.is_normal() { + self_y + y_off } else { y_off }; Self { - x, - y, + x: Some(x), + y: Some(y), width: self.width, height: self.height, } @@ -132,22 +139,22 @@ fn avoid_nan_string(n: f64, fallback: f64) -> String { if n.is_normal() { n.round() } else { fallback }.to_string() } -impl From for RawGeometry { - fn from(value: NumericGeometry) -> Self { +impl From for RawGeometry { + fn from(value: ManagementGeometry) -> Self { Self { - x: value.x as isize, - y: value.y as isize, + x: value.x.unwrap_or(0.0) as isize, + y: value.y.unwrap_or(0.0) as isize, width: value.width.map(|w| w as isize), height: value.height.map(|h| h as isize), } } } -impl From for NumericGeometry { +impl From for ManagementGeometry { fn from(value: RawGeometry) -> Self { Self { - x: value.x as f64, - y: value.y as f64, + x: Some(value.x as f64), + y: Some(value.y as f64), width: value.width.map(|w| w as f64), height: value.height.map(|h| h as f64), } @@ -179,7 +186,7 @@ pub struct ManagementUpdateConnections { #[serde(rename_all = "camelCase")] pub struct ManagementUpdateOperation { properties: Option, - geometry: Option, + geometry: Option>, connect: Option, parent: Option, } @@ -189,7 +196,7 @@ pub struct ManagementUpdateOperation { pub struct ManagementCreateOperation { kind: Option, properties: Option, - geometry: Option, + geometry: Option, connect: Option>, parent: Option, } @@ -368,15 +375,16 @@ impl VariantSocketMap { pub struct ManagementOperator<'a> { ctx: &'a DalContext, manager_component_id: ComponentId, - manager_component_geometry: NumericGeometry, + manager_component_geometry: ManagementGeometry, manager_schema_id: SchemaId, - last_component_geometry: NumericGeometry, + last_component_geometry: ManagementGeometry, operations: ManagementOperations, schema_map: HashMap, component_id_placeholders: HashMap, component_schema_map: ComponentSchemaMap, socket_map: VariantSocketMap, view_id: ViewId, + views: HashMap, created_components: Vec, updated_components: Vec, } @@ -409,7 +417,7 @@ enum PendingOperation { struct CreatedComponent { component: Component, - geometry: NumericGeometry, + geometry: ManagementGeometry, schema_id: SchemaId, } @@ -437,18 +445,30 @@ impl<'a> ManagementOperator<'a> { None => View::get_id_for_default(ctx).await?, }; + let manager_component_geometry_in_view: ManagementGeometry = + Geometry::get_by_component_and_view(ctx, manager_component_id, view_id) + .await? + .into_raw() + .into(); + + let mut views = HashMap::new(); + for view in View::list(ctx).await? { + views.insert(view.name().to_owned(), view_id); + } + Ok(Self { ctx, manager_component_id, manager_schema_id, - last_component_geometry: management_execution.manager_component_geometry, - manager_component_geometry: management_execution.manager_component_geometry, + last_component_geometry: manager_component_geometry_in_view, + manager_component_geometry: manager_component_geometry_in_view, operations, schema_map: management_execution.managed_schema_map, component_id_placeholders, component_schema_map, socket_map: VariantSocketMap::new(), view_id, + views, created_components: vec![], updated_components: vec![], }) @@ -471,28 +491,43 @@ impl<'a> ManagementOperator<'a> { let variant_id = Schema::get_or_install_default_variant(self.ctx, schema_id).await?; let mut component = Component::new(self.ctx, placeholder, variant_id, self.view_id).await?; - let geometry = if let Some(numeric_geometry) = &operation.geometry { - let real_geometry = numeric_geometry.offset_by( + let will_be_frame = + component_will_be_frame(self.ctx, &component, operation.properties.as_ref()).await?; + + let mut auto_geometry = self + .last_component_geometry + .offset_by(75.0.into(), 75.0.into()); + + auto_geometry.width.take(); + auto_geometry.height.take(); + + let mut geometry = if let Some(mut create_geometry) = &operation.geometry { + create_geometry + .x + .get_or_insert(auto_geometry.x.unwrap_or(0.0)); + + create_geometry + .y + .get_or_insert(auto_geometry.y.unwrap_or(0.0)); + + // Creation geometry is relative to the manager component + create_geometry.offset_by( self.manager_component_geometry.x, self.manager_component_geometry.y, - ); - component - .set_raw_geometry(self.ctx, (real_geometry).into(), self.view_id) - .await?; - - *numeric_geometry + ) } else { - // We don't want to just stack components on top of each other if no - // geometry is provided, so we're gonna do a bit of you-just-won - // solitaire staggering - let auto_geometry = self.last_component_geometry.offset_by(50.0, 50.0); - component - .set_raw_geometry(self.ctx, auto_geometry.into(), self.view_id) - .await?; - auto_geometry }; + if will_be_frame && geometry.width.zip(geometry.height).is_none() { + geometry.width = Some(500.0); + geometry.height = Some(500.0); + } + + component + .set_raw_geometry(self.ctx, geometry.into(), self.view_id) + .await?; + Ok(CreatedComponent { component, geometry, @@ -725,16 +760,6 @@ impl<'a> ManagementOperator<'a> { .insert(placeholder.to_owned(), component_id); if let Some(properties) = &operation.properties { - if is_setting_type_to_frame(Some(properties)) { - ensure_geometry_has_width_and_height( - self.ctx, - component_id, - self.view_id, - Some(geometry), - ) - .await?; - } - update_component( self.ctx, component_id, @@ -790,29 +815,64 @@ impl<'a> ManagementOperator<'a> { for (placeholder, operation) in updates { let component_id = self.get_real_component_id(placeholder).await?; + let mut component = Component::get_by_id(self.ctx, component_id).await?; - let offset_geometry = operation.geometry.as_ref().map(|geo| { - let numeric_geometry: NumericGeometry = geo.clone().into(); - numeric_geometry.offset_by( - self.manager_component_geometry.x, - self.manager_component_geometry.y, - ) - }); + let will_be_frame = + component_will_be_frame(self.ctx, &component, operation.properties.as_ref()) + .await?; - if is_setting_type_to_frame(operation.properties.as_ref()) { - ensure_geometry_has_width_and_height( - self.ctx, - component_id, - self.view_id, - offset_geometry, - ) - .await?; - } else if let Some(offset_geo) = offset_geometry { - let mut component = Component::get_by_id(self.ctx, component_id).await?; + for (view_name, &view_id) in &self.views { + let maybe_geometry = operation + .geometry + .as_ref() + .and_then(|geo_map| geo_map.get(view_name)) + .copied(); + + let maybe_geometry = if will_be_frame && maybe_geometry.is_none() { + Some(ManagementGeometry { + x: None, + y: None, + width: None, + height: None, + }) + } else { + maybe_geometry + }; - component - .set_raw_geometry(self.ctx, offset_geo.into(), self.view_id) - .await?; + if let Some(mut view_geometry) = maybe_geometry { + // If the component does not exist in this view then there's nothing to do + let Some(current_geometry) = + Geometry::try_get_by_component_and_view(self.ctx, component_id, view_id) + .await? + else { + continue; + }; + + let current_geometry: ManagementGeometry = current_geometry.into_raw().into(); + + view_geometry + .x + .get_or_insert(current_geometry.x.unwrap_or(0.0)); + view_geometry + .y + .get_or_insert(current_geometry.y.unwrap_or(0.0)); + if let Some(current_width) = current_geometry.width { + view_geometry.width.get_or_insert(current_width); + } + if let Some(current_height) = current_geometry.height { + view_geometry.width.get_or_insert(current_height); + } + + // Ensure frames have a width and height + if view_geometry.width.zip(view_geometry.height).is_none() && will_be_frame { + view_geometry.width = Some(500.0); + view_geometry.height = Some(500.0); + } + + component + .set_raw_geometry(self.ctx, view_geometry.into(), view_id) + .await?; + } } if let Some(properties) = &operation.properties { @@ -1113,73 +1173,6 @@ const IGNORE_PATHS: [&[&str]; 6] = [ const ROOT_SI_TYPE_PATH: &[&str] = &["root", "si", "type"]; -async fn ensure_geometry_has_width_and_height( - ctx: &DalContext, - component_id: ComponentId, - view_id: ViewId, - input_geometry: Option, -) -> ManagementResult { - let mut component = Component::get_by_id(ctx, component_id).await?; - let mut raw_geometry = match input_geometry { - Some(geometry) => geometry.into(), - None => component.geometry(ctx, view_id).await?.into_raw(), - }; - - raw_geometry.width.get_or_insert(500); - raw_geometry.height.get_or_insert(500); - - component - .set_raw_geometry(ctx, raw_geometry.to_owned(), view_id) - .await?; - - Ok(raw_geometry.into()) -} - -fn is_setting_type_to_frame(properties: Option<&serde_json::Value>) -> bool { - let Some(properties) = properties else { - return false; - }; - - let mut work_queue = VecDeque::from([("root", properties)]); - - while let Some((path, current_val)) = work_queue.pop_front() { - let match_key = match path { - "root" => "si", - "si" => "type", - "type" => { - let Ok(new_type) = serde_json::from_value::(current_val.to_owned()) - else { - break; - }; - - if matches!( - new_type, - ComponentType::ConfigurationFrameDown - | ComponentType::ConfigurationFrameUp - | ComponentType::AggregationFrame - ) { - return true; - } - - break; - } - _ => break, - }; - - let serde_json::Value::Object(map) = current_val else { - break; - }; - - let Some(next_value) = map.get(match_key) else { - break; - }; - - work_queue.push_back((match_key, next_value)); - } - - false -} - async fn update_component( ctx: &DalContext, component_id: ComponentId, @@ -1332,3 +1325,46 @@ async fn update_component( Ok(()) } + +async fn component_will_be_frame( + ctx: &DalContext, + component: &Component, + new_properties: Option<&serde_json::Value>, +) -> ManagementResult { + if type_being_set(new_properties).is_some_and(|c_type| c_type.is_frame()) { + return Ok(true); + } + + Ok(component.get_type(ctx).await?.is_frame()) +} + +fn type_being_set(properties: Option<&serde_json::Value>) -> Option { + let mut work_queue = VecDeque::from([("root", properties?)]); + + while let Some((path, current_val)) = work_queue.pop_front() { + let match_key = match path { + "root" => "si", + "si" => "type", + "type" => { + let Ok(new_type) = serde_json::from_value::(current_val.to_owned()) + else { + break; + }; + return Some(new_type); + } + _ => break, + }; + + let serde_json::Value::Object(map) = current_val else { + break; + }; + + let Some(next_value) = map.get(match_key) else { + break; + }; + + work_queue.push_back((match_key, next_value)); + } + + None +} diff --git a/lib/dal/src/management/prototype.rs b/lib/dal/src/management/prototype.rs index adef14952c..d4611b09e1 100644 --- a/lib/dal/src/management/prototype.rs +++ b/lib/dal/src/management/prototype.rs @@ -10,9 +10,13 @@ use si_events::FuncRunId; use thiserror::Error; use veritech_client::ManagementResultSuccess; -use crate::diagram::view::View; use crate::{ cached_module::{CachedModule, CachedModuleError}, + diagram::{ + geometry::Geometry, + view::{View, ViewId}, + DiagramError, + }, func::runner::{FuncRunner, FuncRunnerError}, id, implement_add_edge_to, layer_db_types::{ManagementPrototypeContent, ManagementPrototypeContentV1}, @@ -20,11 +24,10 @@ use crate::{ Component, ComponentError, ComponentId, DalContext, EdgeWeightKind, EdgeWeightKindDiscriminants, FuncId, HelperError, NodeWeightDiscriminants, Schema, SchemaError, SchemaId, SchemaVariant, SchemaVariantError, SchemaVariantId, TransactionsError, - WorkspaceSnapshotError, WsEventError, WsEventResult, + WorkspaceSnapshotError, WsEvent, WsEventError, WsEventResult, }; -use crate::{diagram::DiagramError, WsEvent}; -use super::NumericGeometry; +use super::ManagementGeometry; #[remain::sorted] #[derive(Debug, Error)] @@ -98,7 +101,7 @@ impl From for ManagementPrototypeContent { pub struct ManagementPrototypeExecution { pub func_run_id: FuncRunId, pub result: Option, - pub manager_component_geometry: NumericGeometry, + pub manager_component_geometry: HashMap, pub managed_schema_map: HashMap, pub placeholders: HashMap, } @@ -108,7 +111,45 @@ pub struct ManagementPrototypeExecution { pub struct ManagedComponent { kind: String, properties: Option, - geometry: NumericGeometry, + geometry: HashMap, +} + +async fn build_management_geometry_map( + ctx: &DalContext, + component_id: ComponentId, + views: &HashMap, +) -> ManagementPrototypeResult> { + let mut geometry_map = HashMap::new(); + for (&view_id, view_name) in views { + let Some(geometry) = + Geometry::try_get_by_component_and_view(ctx, component_id, view_id).await? + else { + continue; + }; + + geometry_map.insert(view_name.clone(), geometry.into_raw().into()); + } + + Ok(geometry_map) +} + +impl ManagedComponent { + pub async fn new( + ctx: &DalContext, + component_id: ComponentId, + kind: &str, + views: &HashMap, + ) -> ManagementPrototypeResult { + let component = Component::get_by_id(ctx, component_id).await?; + let properties = component.view(ctx).await?; + let geometry = build_management_geometry_map(ctx, component_id, views).await?; + + Ok(Self { + kind: kind.to_owned(), + properties, + geometry, + }) + } } impl ManagementPrototype { @@ -336,54 +377,48 @@ impl ManagementPrototype { &self, ctx: &DalContext, manager_component_id: ComponentId, + view_id: Option, ) -> ManagementPrototypeResult { - Self::execute_by_id(ctx, self.id, manager_component_id).await + Self::execute_by_id(ctx, self.id, manager_component_id, view_id).await } pub async fn execute_by_id( ctx: &DalContext, id: ManagementPrototypeId, manager_component_id: ComponentId, + view_id: Option, ) -> ManagementPrototypeResult { let prototype = Self::get_by_id(ctx, id) .await? .ok_or(ManagementPrototypeError::NotFound(id))?; + let mut views = HashMap::new(); + for view in View::list(ctx).await? { + views.insert(view.id(), view.name().to_owned()); + } + let (managed_schema_map, reverse_map) = prototype.managed_schemas_map(ctx).await?; + let view_id = match view_id { + Some(view_id) => view_id, + None => View::get_id_for_default(ctx).await?, + }; + let current_view = views.get(&view_id).cloned().unwrap_or(view_id.to_string()); + let management_func_id = ManagementPrototype::func_id(ctx, id).await?; let manager_component = Component::get_by_id(ctx, manager_component_id).await?; - let manager_component_view = manager_component.view(ctx).await?; - let default_view_id = View::get_id_for_default(ctx).await?; - let manager_geometry: NumericGeometry = manager_component - .geometry(ctx, default_view_id) - .await? - .into_raw() - .into(); - - let managed_schema_names: Vec = managed_schema_map.keys().cloned().collect(); let mut managed_components = HashMap::new(); for component_id in manager_component.get_managed(ctx).await? { - let component = Component::get_by_id(ctx, component_id).await?; - let component_view = component.view(ctx).await?; - let component_geometry: NumericGeometry = component - .geometry(ctx, default_view_id) + let schema_id = Component::schema_for_component_id(ctx, component_id) .await? - .into_raw() - .into(); - let schema_id = component.schema(ctx).await?.id(); + .id(); if let Some(managed_schema_name) = reverse_map.get(&schema_id) { - managed_components.insert( - component_id, - ManagedComponent { - kind: managed_schema_name.clone(), - properties: component_view, - geometry: component_geometry - .offset_by(-manager_geometry.x, -manager_geometry.y), - }, - ); + let managed_component = + ManagedComponent::new(ctx, component_id, managed_schema_name, &views).await?; + + managed_components.insert(component_id, managed_component); } } @@ -393,17 +428,18 @@ impl ManagementPrototype { .map(|id| (id.to_string(), id)) .collect(); + let this_schema = Component::schema_for_component_id(ctx, manager_component_id) + .await? + .name() + .to_string(); + + let manager_component = + ManagedComponent::new(ctx, manager_component_id, &this_schema, &views).await?; + let manager_component_geometry = manager_component.geometry.to_owned(); + let args = serde_json::json!({ - "this_component": { - "properties": manager_component_view, - "geometry": NumericGeometry { - x: 0.0, - y: 0.0, - width: manager_geometry.width, - height: manager_geometry.height, - } - }, - "managed_schemas": managed_schema_names, + "current_view": current_view, + "this_component": manager_component, "components": managed_components, }); @@ -473,7 +509,7 @@ impl ManagementPrototype { Ok(ManagementPrototypeExecution { func_run_id, result: maybe_run_result, - manager_component_geometry: manager_geometry, + manager_component_geometry, managed_schema_map, placeholders, }) diff --git a/lib/dal/src/schema/variant/root_prop/component_type.rs b/lib/dal/src/schema/variant/root_prop/component_type.rs index 7d69d92eee..f366f8dcc3 100644 --- a/lib/dal/src/schema/variant/root_prop/component_type.rs +++ b/lib/dal/src/schema/variant/root_prop/component_type.rs @@ -106,4 +106,11 @@ impl ComponentType { Self::ConfigurationFrameUp => "Configuration Frame (up)", } } + + pub fn is_frame(&self) -> bool { + matches!( + self, + Self::AggregationFrame | Self::ConfigurationFrameDown | Self::ConfigurationFrameUp + ) + } } diff --git a/lib/dal/tests/integration_test/management.rs b/lib/dal/tests/integration_test/management.rs index ebeab26e62..bb08330e8d 100644 --- a/lib/dal/tests/integration_test/management.rs +++ b/lib/dal/tests/integration_test/management.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; use dal::{ - diagram::view::View, + diagram::{geometry::Geometry, view::View}, management::{ - prototype::ManagementPrototype, ManagementFuncReturn, ManagementOperator, NumericGeometry, + prototype::ManagementPrototype, ManagementFuncReturn, ManagementGeometry, + ManagementOperator, }, AttributeValue, Component, DalContext, SchemaId, }; @@ -14,6 +15,107 @@ use dal_test::{ }; use veritech_client::ManagementFuncStatus; +#[test] +async fn update_managed_components_in_view(ctx: &DalContext) { + let small_odd_lego = create_component_for_default_schema_name_in_default_view( + ctx, + "small odd lego", + "small odd lego", + ) + .await + .expect("could not create component"); + let small_even_lego = create_component_for_default_schema_name_in_default_view( + ctx, + "small even lego", + "small even lego", + ) + .await + .expect("could not create component"); + + let view_name = "a view askew"; + let new_view_id = ExpectView::create_with_name(ctx, view_name).await.id(); + Geometry::new(ctx, small_odd_lego.id(), new_view_id) + .await + .expect("create geometry in view"); + Geometry::new(ctx, small_even_lego.id(), new_view_id) + .await + .expect("create geometry in view"); + + Component::manage_component(ctx, small_odd_lego.id(), small_even_lego.id()) + .await + .expect("add manages edge"); + + let manager_variant = small_odd_lego + .schema_variant(ctx) + .await + .expect("get variant"); + + let management_prototype = ManagementPrototype::list_for_variant_id(ctx, manager_variant.id()) + .await + .expect("get prototypes") + .into_iter() + .find(|proto| proto.name() == "Update in View") + .expect("could not find prototype"); + + let mut execution_result = management_prototype + .execute(ctx, small_odd_lego.id(), Some(new_view_id)) + .await + .expect("should execute management prototype func"); + + let result: ManagementFuncReturn = execution_result + .result + .take() + .expect("should have a result success") + .try_into() + .expect("should be a valid management func return"); + + assert_eq!(ManagementFuncStatus::Ok, result.status); + assert_eq!(Some(view_name), result.message.as_deref()); + + let operations = result.operations.expect("should have operations"); + + ManagementOperator::new( + ctx, + small_odd_lego.id(), + operations, + execution_result, + Some(new_view_id), + ) + .await + .expect("should create operator") + .operate() + .await + .expect("should operate"); + + let mut new_component = None; + let components = Component::list(ctx).await.expect("list components"); + assert_eq!(2, components.len()); + for c in components { + if c.name(ctx).await.expect("get name") == "small even lego managed by small odd lego" { + new_component = Some(c); + break; + } + } + + let new_component = new_component.expect("should have found the cloned component"); + let default_view_id = ExpectView::get_id_for_default(ctx).await; + let default_view_geometry = new_component + .geometry(ctx, default_view_id) + .await + .expect("get geometry for default view"); + + assert_eq!(0, default_view_geometry.x()); + assert_eq!(0, default_view_geometry.y()); + + let new_view_geometry = new_component + .geometry(ctx, new_view_id) + .await + .expect("get geo for view askew"); + + assert_eq!(1000, new_view_geometry.x()); + assert_eq!(750, new_view_geometry.y()); +} + #[test] async fn update_managed_components(ctx: &DalContext) { let small_odd_lego = create_component_for_default_schema_name_in_default_view( @@ -48,7 +150,7 @@ async fn update_managed_components(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -133,7 +235,7 @@ async fn create_component_of_other_schema(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -222,7 +324,7 @@ async fn create_and_connect_to_self_as_children(ctx: &mut DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -244,23 +346,17 @@ async fn create_and_connect_to_self_as_children(ctx: &mut DalContext) { .await .expect("should operate"); + let geometry = small_odd_lego + .geometry(ctx, ExpectView::get_id_for_default(ctx).await) + .await + .expect("get geometry"); + + assert_eq!(Some(500), geometry.width()); + assert_eq!(Some(500), geometry.height()); + let components = Component::list(ctx).await.expect("get components"); assert_eq!(4, components.len()); - let workspace_snapshot = ctx.workspace_snapshot().expect("get snap"); - let edges = workspace_snapshot - .edges_directed(small_odd_lego.id(), petgraph::Direction::Outgoing) - .await - .expect("get edges"); - for (weight, _, tgt) in edges { - let target_id = workspace_snapshot - .get_node_weight(tgt) - .await - .expect("get target") - .id(); - println!("{:?} -> {}", weight.kind(), target_id); - } - let children: HashSet<_> = Component::get_children_for_id(ctx, small_odd_lego.id()) .await .expect("get frame children") @@ -361,7 +457,7 @@ async fn create_and_connect_to_self(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -453,7 +549,7 @@ async fn create_and_connect_from_self(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -517,7 +613,7 @@ async fn create_component_of_same_schema(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -552,15 +648,15 @@ async fn create_component_of_same_schema(ctx: &DalContext) { let default_view_id = ExpectView::get_id_for_default(ctx).await; let new_component = new_component.expect("should have found the cloned component"); - let new_geometry: NumericGeometry = new_component + let new_geometry: ManagementGeometry = new_component .geometry(ctx, default_view_id) .await .expect("get geometry") .into_raw() .into(); - assert_eq!(10.0, new_geometry.x); - assert_eq!(20.0, new_geometry.y); + assert_eq!(Some(10.0), new_geometry.x); + assert_eq!(Some(20.0), new_geometry.y); let managers = new_component.managers(ctx).await.expect("get managers"); @@ -572,7 +668,7 @@ async fn create_component_of_same_schema(ctx: &DalContext) { ); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); @@ -642,7 +738,7 @@ async fn execute_management_func(ctx: &DalContext) { .expect("could not find prototype"); let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id()) + .execute(ctx, small_odd_lego.id(), None) .await .expect("should execute management prototype func"); diff --git a/lib/dal/tests/integration_test/secret.rs b/lib/dal/tests/integration_test/secret.rs index 531feed867..c0bb5f49df 100644 --- a/lib/dal/tests/integration_test/secret.rs +++ b/lib/dal/tests/integration_test/secret.rs @@ -314,8 +314,8 @@ async fn copy_paste_component_with_secrets_being_used(ctx: &mut DalContext, nw: ctx, default_view_id, RawGeometry { - x: *geometry.x(), - y: *geometry.y(), + x: geometry.x(), + y: geometry.y(), width: None, height: None, }, @@ -340,8 +340,8 @@ async fn copy_paste_component_with_secrets_being_used(ctx: &mut DalContext, nw: ctx, default_view_id, RawGeometry { - x: *user_component_geometry.x(), - y: *user_component_geometry.y(), + x: user_component_geometry.x(), + y: user_component_geometry.y(), width: None, height: None, }, diff --git a/lib/sdf-server/src/service/diagram/create_component.rs b/lib/sdf-server/src/service/diagram/create_component.rs index 57e66916c3..999b0b6007 100644 --- a/lib/sdf-server/src/service/diagram/create_component.rs +++ b/lib/sdf-server/src/service/diagram/create_component.rs @@ -132,8 +132,8 @@ pub async fn create_component( view_id, x, y, - width.or_else(|| initial_geometry.width().copied()), - height.or_else(|| initial_geometry.height().copied()), + width.or_else(|| initial_geometry.width()), + height.or_else(|| initial_geometry.height()), ) .await?; } else { diff --git a/lib/sdf-server/src/service/diagram/set_component_position.rs b/lib/sdf-server/src/service/diagram/set_component_position.rs index 692b224e26..572cd58f55 100644 --- a/lib/sdf-server/src/service/diagram/set_component_position.rs +++ b/lib/sdf-server/src/service/diagram/set_component_position.rs @@ -126,8 +126,8 @@ pub async fn set_component_position( if component_type != ComponentType::Component { size = ( - new_geometry.width.or_else(|| geometry.width().copied()), - new_geometry.height.or_else(|| geometry.height().copied()), + new_geometry.width.or_else(|| geometry.width()), + new_geometry.height.or_else(|| geometry.height()), ); } diff --git a/lib/sdf-server/src/service/v2/management.rs b/lib/sdf-server/src/service/v2/management.rs index c50d3d6009..363b8af58a 100644 --- a/lib/sdf-server/src/service/v2/management.rs +++ b/lib/sdf-server/src/service/v2/management.rs @@ -10,6 +10,7 @@ use axum::{ Router, }; use dal::{ + diagram::view::ViewId, management::{ prototype::{ManagementPrototype, ManagementPrototypeError, ManagementPrototypeId}, ManagementError, ManagementFuncReturn, ManagementOperator, @@ -78,11 +79,12 @@ pub async fn run_prototype( PosthogClient(posthog_client): PosthogClient, OriginalUri(original_uri): OriginalUri, Host(host_name): Host, - Path((_workspace_pk, change_set_id, prototype_id, component_id)): Path<( + Path((_workspace_pk, change_set_id, prototype_id, component_id, view_id)): Path<( WorkspacePk, ChangeSetId, ManagementPrototypeId, ComponentId, + ViewId, )>, ) -> ManagementApiResult> { let mut ctx = builder @@ -93,7 +95,8 @@ pub async fn run_prototype( // TODO check that this is a valid prototypeId let mut execution_result = - ManagementPrototype::execute_by_id(&ctx, prototype_id, component_id).await?; + ManagementPrototype::execute_by_id(&ctx, prototype_id, component_id, view_id.into()) + .await?; track( &posthog_client, @@ -103,8 +106,9 @@ pub async fn run_prototype( "run_prototype", serde_json::json!({ "how": "/management/run_prototype", - "prototype_id": prototype_id.clone(), - "component_id": component_id.clone(), + "view_id": view_id, + "prototype_id": prototype_id, + "component_id": component_id, }), ); @@ -112,10 +116,16 @@ pub async fn run_prototype( let result: ManagementFuncReturn = result.try_into()?; if result.status == ManagementFuncStatus::Ok { if let Some(operations) = result.operations { - ManagementOperator::new(&ctx, component_id, operations, execution_result, None) - .await? - .operate() - .await?; + ManagementOperator::new( + &ctx, + component_id, + operations, + execution_result, + Some(view_id), + ) + .await? + .operate() + .await?; } } @@ -137,7 +147,10 @@ pub async fn run_prototype( pub fn v2_routes() -> Router { Router::new() - .route("/prototype/:prototypeId/:componentId", post(run_prototype)) + .route( + "/prototype/:prototypeId/:componentId/:viewId", + post(run_prototype), + ) .route( "/prototype/:prototypeId/:componentId/latest", get(latest::latest), diff --git a/lib/sdf-server/src/service/v2/view/create_component.rs b/lib/sdf-server/src/service/v2/view/create_component.rs index e9569e3210..90fee7e186 100644 --- a/lib/sdf-server/src/service/v2/view/create_component.rs +++ b/lib/sdf-server/src/service/v2/view/create_component.rs @@ -161,8 +161,8 @@ pub async fn create_component( view_id, x, y, - width.or_else(|| initial_geometry.width().copied()), - height.or_else(|| initial_geometry.height().copied()), + width.or_else(|| initial_geometry.width()), + height.or_else(|| initial_geometry.height()), ) .await?; } else { diff --git a/lib/sdf-server/src/service/v2/view/set_component_geometry.rs b/lib/sdf-server/src/service/v2/view/set_component_geometry.rs index 30a5b64739..b6bbe191ac 100644 --- a/lib/sdf-server/src/service/v2/view/set_component_geometry.rs +++ b/lib/sdf-server/src/service/v2/view/set_component_geometry.rs @@ -49,12 +49,8 @@ pub async fn set_component_geometry( let new_geometry_cache = new_geometry.clone(); let (width, height) = ( - new_geometry - .width - .or_else(|| current_geometry.width().copied()), - new_geometry - .height - .or_else(|| current_geometry.height().copied()), + new_geometry.width.or_else(|| current_geometry.width()), + new_geometry.height.or_else(|| current_geometry.height()), ); component diff --git a/lib/veritech-client/tests/integration.rs b/lib/veritech-client/tests/integration.rs index 99c36ee2c5..50979b13b6 100644 --- a/lib/veritech-client/tests/integration.rs +++ b/lib/veritech-client/tests/integration.rs @@ -105,6 +105,7 @@ async fn executes_simple_management_function() { let request = ManagementRequest { execution_id: "1234".to_string(), handler: "numberOfInputs".to_string(), + current_view: "DEFAULT".to_string(), this_component: ComponentViewWithGeometry { kind: None, properties: serde_json::json!({ "foo": "bar", "baz": "quux", "bar": "foo" }),