From 98142f3be223d446858a6a15f06d5759bb4904ce Mon Sep 17 00:00:00 2001 From: Zachary Hamm Date: Mon, 23 Dec 2024 14:12:32 -0600 Subject: [PATCH] feat: mgmt fns can create views, create components in views The return type for management functions has been extended to support creating views, and creating new components in views other than the current view. The "geometry" property on a create can now *either* be a plain geometry object, which will create the component in the current view, or it can be a map of view names to geometries, which will create the component in the specified view with that geometry. Additionally, you can add to your ops return an object like so: ``` ops: { views: { create: ["view a", "view b", "view c"] } } ``` to create new views in a mangement function. The views will be available for create and update operations in the management func itself. --- Cargo.lock | 1 + bin/lang-js/src/function_kinds/management.ts | 20 +- lib/dal-test/Cargo.toml | 1 + .../src/test_exclusive_schemas/legos/small.rs | 73 ++- lib/dal/BUCK | 1 + lib/dal/src/component.rs | 4 +- lib/dal/src/diagram/geometry.rs | 44 +- lib/dal/src/diagram/view.rs | 2 +- lib/dal/src/func/binding/management.rs | 3 +- lib/dal/src/management/generator.rs | 6 +- lib/dal/src/management/mod.rs | 524 +++++++++++++----- lib/dal/tests/integration_test/management.rs | 434 +++++++-------- .../integration_test/management/generator.rs | 8 +- 13 files changed, 740 insertions(+), 381 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8546ff21ae..d819e483b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1939,6 +1939,7 @@ dependencies = [ "si-data-nats", "si-data-pg", "si-events", + "si-id", "si-jwt-public-key", "si-layer-cache", "si-pkg", diff --git a/bin/lang-js/src/function_kinds/management.ts b/bin/lang-js/src/function_kinds/management.ts index 1fe94e78db..729f510412 100644 --- a/bin/lang-js/src/function_kinds/management.ts +++ b/bin/lang-js/src/function_kinds/management.ts @@ -32,16 +32,18 @@ export interface ManagmentConnect { } } +export interface ManagementCreate { + [key: string]: { + kind: string; + properties?: object; + geometry?: Geometry | { [key: string]: Geometry }; + parent?: string; + connect?: ManagmentConnect[], + } +} + export interface ManagementOperations { - create?: { - [key: string]: { - kind: string; - properties?: object; - geometry?: Geometry; - parent?: string; - connect?: ManagmentConnect[], - } - }; + create?: ManagementCreate, update?: { [key: string]: { properties?: object; diff --git a/lib/dal-test/Cargo.toml b/lib/dal-test/Cargo.toml index 71cdc1f500..1ecc1bf765 100644 --- a/lib/dal-test/Cargo.toml +++ b/lib/dal-test/Cargo.toml @@ -20,6 +20,7 @@ si-crypto = { path = "../../lib/si-crypto" } si-data-nats = { path = "../../lib/si-data-nats" } si-data-pg = { path = "../../lib/si-data-pg" } si-events = { path = "../../lib/si-events-rs" } +si-id = { path = "../../lib/si-id" } si-jwt-public-key = { path = "../../lib/si-jwt-public-key" } si-layer-cache = { path = "../../lib/si-layer-cache" } si-pkg = { path = "../../lib/si-pkg" } 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 88e061cf99..ee1d3c4a78 100644 --- a/lib/dal-test/src/test_exclusive_schemas/legos/small.rs +++ b/lib/dal-test/src/test_exclusive_schemas/legos/small.rs @@ -333,6 +333,61 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( let deeply_nested_children = build_management_func(deeply_nested_children_code, "test:deeplyNestedChildren")?; + let create_component_in_other_views_code = r#" + async function main({ thisComponent, currentView }: Input): Promise { + const thisView = thisComponent.properties?.si?.resourceId ?? currentView; + + const name = `component in ${thisView}`; + + return { + status: "ok", + ops: { + create: { + [name]: { + geometry: { + [currentView]: { x: 100, y: 100 }, + [thisView]: { x: 15, y: 15 } + } + } + } + } + } + } + "#; + + let create_component_in_other_views = build_management_func( + create_component_in_other_views_code, + "test:createComponentsInOtherViews", + )?; + + let create_view_and_component_in_view_code = r#" + async function main({ thisComponent, currentView }: Input): Promise { + const thisView = thisComponent.properties?.si?.resourceId ?? currentView; + + const componentName = `component in ${thisView}`; + + return { + status: "ok", + ops: { + views: { + create: [thisView], + }, + create: { + [componentName]: { + geometry: { + [thisView]: { x: 315, y: 315 } + } + } + } + } + } + } + "#; + let create_view_and_component_in_view = build_management_func( + create_view_and_component_in_view_code, + "test:createViewAndComponentInView", + )?; + let fn_name = "test:deleteActionSmallLego"; let delete_action_func = build_action_func(delete_action_code, fn_name)?; @@ -466,11 +521,23 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( .func_unique_id(&create_and_connect_to_self_as_children_func.unique_id) .build()?, ) + .management_func( + ManagementFuncSpec::builder() + .name("Create in Other Views") + .func_unique_id(&create_component_in_other_views.unique_id) + .build()?, + ) + .management_func( + ManagementFuncSpec::builder() + .name("Create View and Component in View") + .func_unique_id(&create_view_and_component_in_view.unique_id) + .build()?, + ) .build()?, ) .build()?; - let small_lego_spec = small_lego_builder + let small_odd_lego_spec = small_lego_builder .func(identity_func_spec) .func(refresh_action_func) .func(create_action_func) @@ -486,10 +553,12 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego( .func(create_and_connect_to_self_func) .func(create_and_connect_to_self_as_children_func) .func(deeply_nested_children) + .func(create_component_in_other_views) + .func(create_view_and_component_in_view) .schema(small_lego_schema) .build()?; - let pkg = SiPkg::load_from_spec(small_lego_spec)?; + let pkg = SiPkg::load_from_spec(small_odd_lego_spec)?; import_pkg_from_pkg( ctx, &pkg, diff --git a/lib/dal/BUCK b/lib/dal/BUCK index 1912ce5471..01d8ddc232 100644 --- a/lib/dal/BUCK +++ b/lib/dal/BUCK @@ -101,6 +101,7 @@ rust_test( "//lib/pending-events:pending-events", "//lib/rebaser-server:rebaser-server", "//lib/si-events-rs:si-events", + "//lib/si-id:si-id", "//lib/si-frontend-types-rs:si-frontend-types", "//lib/si-layer-cache:si-layer-cache", "//lib/si-pkg:si-pkg", diff --git a/lib/dal/src/component.rs b/lib/dal/src/component.rs index 51f2e54504..621ea8b1c5 100644 --- a/lib/dal/src/component.rs +++ b/lib/dal/src/component.rs @@ -1487,7 +1487,7 @@ impl Component { view_id: ViewId, ) -> ComponentResult { let mut geometry_pre = self.geometry(ctx, view_id).await?; - if geometry_pre.clone().into_raw() != raw_geometry { + if geometry_pre.into_raw() != raw_geometry { geometry_pre .update(ctx, raw_geometry) .await @@ -3594,7 +3594,7 @@ impl Component { Some(GeometryAndView { view_id, - geometry: geometry.clone().into_raw(), + geometry: geometry.into_raw(), }) } else { None diff --git a/lib/dal/src/diagram/geometry.rs b/lib/dal/src/diagram/geometry.rs index bf4fd834e7..a25291a767 100644 --- a/lib/dal/src/diagram/geometry.rs +++ b/lib/dal/src/diagram/geometry.rs @@ -15,6 +15,7 @@ use jwt_simple::prelude::{Deserialize, Serialize}; use si_events::ulid::Ulid; use si_events::ContentHash; pub use si_frontend_types::RawGeometry; +use std::collections::HashMap; use std::sync::Arc; const DEFAULT_COMPONENT_X_POSITION: &str = "0"; @@ -41,7 +42,7 @@ pub enum GeometryRepresents { } /// Represents spatial data for something to be shown on a view -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Copy)] pub struct Geometry { id: GeometryId, #[serde(flatten)] @@ -264,6 +265,47 @@ impl Geometry { Ok(geometries) } + /// Returns all geometries for a component in a map, keyed by the view id + pub async fn by_view_for_component_id( + ctx: &DalContext, + component_id: ComponentId, + ) -> DiagramResult> { + let mut result = HashMap::new(); + let snap = ctx.workspace_snapshot()?; + + for geometry_idx in snap + .incoming_sources_for_edge_weight_kind( + component_id, + EdgeWeightKindDiscriminants::Represents, + ) + .await? + { + let NodeWeight::Geometry(geo_inner) = snap.get_node_weight(geometry_idx).await? else { + continue; + }; + + let geo_id = geo_inner.id(); + + let Some(view_idx) = snap + .incoming_sources_for_edge_weight_kind(geo_id, EdgeWeightKindDiscriminants::Use) + .await? + .pop() + else { + continue; + }; + + let NodeWeight::View(view_inner) = snap.get_node_weight(view_idx).await? else { + continue; + }; + + let geo = Self::get_by_id(ctx, geo_id.into()).await?; + + result.insert(view_inner.id().into(), geo); + } + + Ok(result) + } + pub async fn list_by_view_id( ctx: &DalContext, view_id: ViewId, diff --git a/lib/dal/src/diagram/view.rs b/lib/dal/src/diagram/view.rs index 278d0df9a9..debac36b95 100644 --- a/lib/dal/src/diagram/view.rs +++ b/lib/dal/src/diagram/view.rs @@ -378,7 +378,7 @@ impl View { view_id: ViewId, ) -> DiagramResult { let mut geometry_pre = self.geometry(ctx, view_id).await?; - if geometry_pre.clone().into_raw() != raw_geometry { + if geometry_pre.into_raw() != raw_geometry { geometry_pre.update(ctx, raw_geometry).await?; } diff --git a/lib/dal/src/func/binding/management.rs b/lib/dal/src/func/binding/management.rs index 235e4ac142..d88703f24b 100644 --- a/lib/dal/src/func/binding/management.rs +++ b/lib/dal/src/func/binding/management.rs @@ -113,7 +113,7 @@ impl ManagementBinding { {{ kind: "{name}", properties?: {sv_type}, - geometry?: Geometry, + geometry?: Geometry | {{ [key: string]: Geometry }}, connect?: {{ from: string, to: {{ @@ -155,6 +155,7 @@ type Geometry = {{ type Output = {{ status: 'ok' | 'error'; ops?: {{ + views?: {{ create: string[]; }}, create?: {{ [key: string]: {component_create_type} }}, update?: {{ [key: string]: {{ properties?: {{ [key: string]: unknown }}, diff --git a/lib/dal/src/management/generator.rs b/lib/dal/src/management/generator.rs index 6f855b5bbe..a3cc725516 100644 --- a/lib/dal/src/management/generator.rs +++ b/lib/dal/src/management/generator.rs @@ -4,8 +4,8 @@ use si_frontend_types::RawGeometry; use si_id::{ComponentId, SchemaId, ViewId}; use crate::management::{ - ConnectionIdentifier, ManagementConnection, ManagementCreateOperation, ManagementGeometry, - IGNORE_PATHS, + ConnectionIdentifier, ManagementConnection, ManagementCreateGeometry, + ManagementCreateOperation, ManagementGeometry, IGNORE_PATHS, }; use crate::prop::PropPath; use crate::{AttributeValue, Component, DalContext, InputSocket, OutputSocket, Prop, PropKind}; @@ -191,7 +191,7 @@ pub async fn generate_template( let create = ManagementCreateOperation { kind: Some(kind), properties, - geometry, + geometry: geometry.map(ManagementCreateGeometry::CurrentView), connect, parent, }; diff --git a/lib/dal/src/management/mod.rs b/lib/dal/src/management/mod.rs index a7830da808..9f85ada453 100644 --- a/lib/dal/src/management/mod.rs +++ b/lib/dal/src/management/mod.rs @@ -2,6 +2,7 @@ use std::collections::{hash_map, HashMap, HashSet, VecDeque}; use prototype::ManagementPrototypeExecution; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use thiserror::Error; use veritech_client::{ManagementFuncStatus, ManagementResultSuccess}; @@ -9,7 +10,7 @@ use veritech_client::{ManagementFuncStatus, ManagementResultSuccess}; use crate::component::frame::{Frame, FrameError}; use crate::dependency_graph::DependencyGraph; use crate::diagram::geometry::Geometry; -use crate::diagram::view::{View, ViewId}; +use crate::diagram::view::{View, ViewId, ViewView}; use crate::diagram::{DiagramError, SummaryDiagramInferredEdge, SummaryDiagramManagementEdge}; use crate::{ action::{ @@ -30,7 +31,7 @@ use crate::{ Func, FuncError, InputSocket, InputSocketId, OutputSocket, OutputSocketId, Prop, PropKind, Schema, SchemaError, SchemaId, SchemaVariantId, StandardModelError, WsEvent, WsEventError, }; -use crate::{EdgeWeightKind, WorkspaceSnapshotError}; +use crate::{EdgeWeightKind, TransactionsError, WorkspaceSnapshotError}; pub mod generator; pub mod prototype; @@ -55,6 +56,8 @@ pub enum ManagementError { "cannot add a manual action named {0} because component {1} does not have a manual action with that name" )] ComponentDoesNotHaveManualAction(String, ComponentId), + #[error("Component somehow not created! This is a bug.")] + ComponentNotCreated, #[error("Component with management placeholder {0} could not be found")] ComponentWithPlaceholderNotFound(String), #[error("Diagram Error {0}")] @@ -69,6 +72,8 @@ pub enum ManagementError { InputSocket(#[from] InputSocketError), #[error("Cannot connect component {0} to component {1} because component {1} does not have an input socket with name {2}")] InputSocketDoesNotExist(ComponentId, ComponentId, String), + #[error("No existing or created view could be found named: {0}")] + NoSuchView(String), #[error("output socket error: {0}")] OutputSocket(#[from] OutputSocketError), #[error("Cannot connect component {0} to component {1} because component {0} does not have an output socket with name {2}")] @@ -81,6 +86,8 @@ pub enum ManagementError { SchemaDoesNotExist(String), #[error("standard model error: {0}")] StandardModel(#[from] StandardModelError), + #[error("transactions error: {0}")] + Transactions(#[from] TransactionsError), #[error("workspace snapshot error: {0}")] WorkspaceSnapshot(#[from] WorkspaceSnapshotError), #[error("ws event error: {0}")] @@ -89,6 +96,9 @@ pub enum ManagementError { pub type ManagementResult = Result; +const DEFAULT_FRAME_WIDTH: f64 = 950.0; +const DEFAULT_FRAME_HEIGHT: f64 = 750.0; + /// 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)] @@ -193,12 +203,21 @@ pub struct ManagementUpdateOperation { parent: Option, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum ManagementCreateGeometry { + #[serde(rename_all = "camelCase")] + WithViews(HashMap), + #[serde(rename_all = "camelCase")] + CurrentView(ManagementGeometry), +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ManagementCreateOperation { kind: Option, properties: Option, - geometry: Option, + geometry: Option, connect: Option>, parent: Option, } @@ -219,12 +238,20 @@ pub type ManagementCreateOperations = HashMap pub type ManagementUpdateOperations = HashMap; pub type ManagementActionOperations = HashMap; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ManagementViewOperations { + create: Option>, + remove: Option>, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ManagementOperations { create: Option, update: Option, actions: Option, + views: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -381,18 +408,18 @@ impl VariantSocketMap { pub struct ManagementOperator<'a> { ctx: &'a DalContext, manager_component_id: ComponentId, - manager_component_geometry: ManagementGeometry, + manager_component_geometry: HashMap, manager_schema_id: SchemaId, - last_component_geometry: ManagementGeometry, + last_component_geometry: HashMap, operations: ManagementOperations, schema_map: HashMap, component_id_placeholders: HashMap, component_schema_map: ComponentSchemaMap, socket_map: VariantSocketMap, - view_id: ViewId, - views: HashMap, - created_components: HashSet, - updated_components: HashSet, + current_view_id: ViewId, + view_placeholders: HashMap, + created_components: HashMap>, + updated_components: HashMap>, } #[derive(Clone, Debug)] @@ -423,7 +450,7 @@ enum PendingOperation { struct CreatedComponent { component: Component, - geometry: ManagementGeometry, + geometry: HashMap, schema_id: SchemaId, } @@ -446,40 +473,62 @@ impl<'a> ManagementOperator<'a> { .variant_for_component_id(ctx, manager_component_id) .await?; - let view_id = match view_id { + let current_view_id = match view_id { Some(view_id) => view_id, 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 manager_component_geometry_in_view = HashMap::new(); let mut views = HashMap::new(); for view in View::list(ctx).await? { - views.insert(view.name().to_owned(), view_id); + views.insert(view.name().to_owned(), view.id()); + if let Some(geo) = + Geometry::try_get_by_component_and_view(ctx, manager_component_id, view.id()) + .await? + .map(|geo| geo.into_raw().into()) + { + manager_component_geometry_in_view.insert(view.id(), geo); + } } Ok(Self { ctx, manager_component_id, manager_schema_id, - last_component_geometry: manager_component_geometry_in_view, + last_component_geometry: manager_component_geometry_in_view.clone(), 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: HashSet::new(), - updated_components: HashSet::new(), + current_view_id, + view_placeholders: views, + created_components: HashMap::new(), + updated_components: HashMap::new(), }) } + fn get_auto_geometry_for_view(&self, view_id: ViewId) -> ManagementGeometry { + let mut geo = self + .last_component_geometry + .get(&view_id) + .cloned() + .unwrap_or(ManagementGeometry { + x: Some(0.0), + y: Some(0.0), + width: None, + height: None, + }) + .offset_by(Some(75.0), Some(75.0)); + + geo.height.take(); + geo.width.take(); + + geo + } + async fn create_component( &self, placeholder: &str, @@ -495,48 +544,138 @@ impl<'a> ManagementOperator<'a> { }; let variant_id = Schema::get_or_install_default_variant(self.ctx, schema_id).await?; + let mut created_geometries = HashMap::new(); + + let component = match &operation.geometry { + Some(ManagementCreateGeometry::WithViews(geometries)) => { + let mut component: Option = None; + let mut will_be_frame = None; + + for (view_placeholder, geometry) in geometries { + let geometry_view_id = self + .view_placeholders + .get(view_placeholder) + .copied() + .ok_or(ManagementError::NoSuchView(view_placeholder.to_owned()))?; + + let mut comp = match component.as_ref() { + Some(component) => component.to_owned(), + None => { + let comp = + Component::new(self.ctx, placeholder, variant_id, geometry_view_id) + .await?; + will_be_frame = Some( + component_will_be_frame( + self.ctx, + &comp, + operation.properties.as_ref(), + ) + .await?, + ); + + comp + } + }; - let mut component = Component::new(self.ctx, placeholder, variant_id, self.view_id).await?; - let will_be_frame = - component_will_be_frame(self.ctx, &component, operation.properties.as_ref()).await?; + let auto_geometry = self.get_auto_geometry_for_view(geometry_view_id); + + // If the manager component exists in this view, then use + // that as the origin. Otherwise, use the position of the + // manager component in the view the function was executed + // in as the origin for relative geometry + let origin_geometry = self + .manager_component_geometry + .get(&geometry_view_id) + .or_else(|| self.manager_component_geometry.get(&self.current_view_id)) + .copied(); + + let geometry = process_geometry( + geometry, + auto_geometry.x, + auto_geometry.y, + origin_geometry.and_then(|geo| geo.x), + origin_geometry.and_then(|geo| geo.y), + will_be_frame.unwrap_or(false), + ); + + created_geometries.insert(geometry_view_id, geometry); + + match component.as_ref().map(|c| c.id()) { + Some(component_id) => { + Component::add_to_view( + self.ctx, + component_id, + geometry_view_id, + geometry.into(), + ) + .await?; + } + None => { + comp.set_raw_geometry(self.ctx, geometry.into(), geometry_view_id) + .await?; + component = Some(comp); + } + } + } - let mut auto_geometry = self - .last_component_geometry - .offset_by(75.0.into(), 75.0.into()); + component + } + Some(ManagementCreateGeometry::CurrentView(geometry)) => { + let mut component = + Component::new(self.ctx, placeholder, variant_id, self.current_view_id).await?; + let will_be_frame = + component_will_be_frame(self.ctx, &component, operation.properties.as_ref()) + .await?; - auto_geometry.width.take(); - auto_geometry.height.take(); + let auto_geometry = self.get_auto_geometry_for_view(self.current_view_id); + let geometry = process_geometry( + geometry, + auto_geometry.x, + auto_geometry.y, + self.manager_component_geometry + .get(&self.current_view_id) + .and_then(|geo| geo.x), + self.manager_component_geometry + .get(&self.current_view_id) + .and_then(|geo| geo.y), + will_be_frame, + ); + + created_geometries.insert(self.current_view_id, geometry); + + component + .set_raw_geometry(self.ctx, geometry.into(), self.current_view_id) + .await?; - let mut geometry = if let Some(mut create_geometry) = &operation.geometry { - create_geometry - .x - .get_or_insert(auto_geometry.x.unwrap_or(0.0)); + Some(component) + } + None => { + let mut component = + Component::new(self.ctx, placeholder, variant_id, self.current_view_id).await?; + let will_be_frame = + component_will_be_frame(self.ctx, &component, operation.properties.as_ref()) + .await?; - create_geometry - .y - .get_or_insert(auto_geometry.y.unwrap_or(0.0)); + let auto_geometry = self.get_auto_geometry_for_view(self.current_view_id); - // Creation geometry is relative to the manager component - create_geometry.offset_by( - self.manager_component_geometry.x, - self.manager_component_geometry.y, - ) - } else { - auto_geometry - }; + let geometry = + process_geometry(&auto_geometry, None, None, None, None, will_be_frame); - if will_be_frame && geometry.width.zip(geometry.height).is_none() { - geometry.width = Some(500.0); - geometry.height = Some(500.0); - } + created_geometries.insert(self.current_view_id, geometry); - component - .set_raw_geometry(self.ctx, geometry.into(), self.view_id) - .await?; + component + .set_raw_geometry(self.ctx, geometry.into(), self.current_view_id) + .await?; + + Some(component) + } + }; + + let component = component.ok_or(ManagementError::ComponentNotCreated)?; Ok(CreatedComponent { component, - geometry, + geometry: created_geometries, schema_id, }) } @@ -760,7 +899,8 @@ impl<'a> ManagementOperator<'a> { let component_id = component.id(); - self.created_components.insert(component_id); + self.created_components + .insert(component_id, geometry.keys().copied().collect()); self.component_id_placeholders .insert(placeholder.to_owned(), component_id); @@ -784,7 +924,7 @@ impl<'a> ManagementOperator<'a> { } } - self.last_component_geometry = geometry; + self.last_component_geometry.extend(geometry); if let Some(parent) = &operation.parent { pending_operations.push(PendingOperation::Parent(PendingParent { @@ -822,63 +962,95 @@ 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 mut view_ids = HashSet::new(); let will_be_frame = component_will_be_frame(self.ctx, &component, operation.properties.as_ref()) .await?; - for (view_name, &view_id) in &self.views { - let maybe_geometry = operation + for geometry_id in Geometry::list_ids_by_component(self.ctx, component_id).await? { + let view_id = Geometry::get_view_id_by_id(self.ctx, geometry_id).await?; + view_ids.insert(view_id); + } + + // we have to ensure frames get a size + let geometries = if will_be_frame + && 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 - }; - - 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) = + .is_none_or(|geometries| geometries.is_empty()) + { + let mut geometries = HashMap::new(); + for &view_id in &view_ids { + if let Some(geo) = Geometry::try_get_by_component_and_view(self.ctx, component_id, view_id) .await? - else { - continue; - }; + { + geometries.insert(view_id, (geo.into_raw().into(), true)); + } + } + geometries + } else if let Some(update_geometries) = &operation.geometry { + let mut geometries = HashMap::new(); + + for (view_name, &geometry) in update_geometries { + let view_id = self + .view_placeholders + .get(view_name) + .copied() + .ok_or(ManagementError::NoSuchView(view_name.to_owned()))?; + geometries.insert(view_id, (geometry, false)); + } - let current_geometry: ManagementGeometry = current_geometry.into_raw().into(); + geometries + } else { + HashMap::new() + }; + for (view_id, (mut view_geometry, is_current)) in geometries { + let current_geometry: ManagementGeometry = if is_current { 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); + } else { + match Geometry::try_get_by_component_and_view(self.ctx, component_id, view_id) + .await? + { + Some(geometry) => geometry.into_raw().into(), + None => { + view_ids.insert(view_id); + Component::add_to_view( + self.ctx, + component_id, + view_id, + view_geometry.into(), + ) + .await?; + view_geometry + } } + }; - // 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); - } + 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); + } - component - .set_raw_geometry(self.ctx, view_geometry.into(), view_id) - .await?; + // 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 { @@ -912,7 +1084,8 @@ impl<'a> ManagementOperator<'a> { })); } - self.updated_components.insert(component_id); + self.updated_components + .insert(component_id, view_ids.iter().copied().collect()); } Ok(pending) @@ -938,7 +1111,8 @@ impl<'a> ManagementOperator<'a> { // Using the dep graph to ensure we send ws events for components in parent // to child order, so that parents exist in the frontend before their // children / parents are rendered as frames before their children report - // their parentage + // their parentage. We have to send an update for every view for which the + // component has a geometry. async fn send_component_ws_events( &mut self, mut parentage_graph: DependencyGraph, @@ -950,28 +1124,33 @@ impl<'a> ManagementOperator<'a> { break; } for id in independent_ids { - if self.created_components.contains(&id) { - self.send_created_event(id, inferred_edges_by_component_id.get(&id).cloned()) - .await?; + if let Some(view_ids) = self.created_components.get(&id) { + self.send_created_event( + id, + view_ids, + inferred_edges_by_component_id.get(&id).cloned(), + ) + .await?; self.created_components.remove(&id); - } else if self.updated_components.contains(&id) { - self.send_updated_event(id).await?; + } else if let Some(view_ids) = self.updated_components.get(&id) { + self.send_updated_event(id, view_ids).await?; self.updated_components.remove(&id); } parentage_graph.remove_id(id); } } - for &created_id in &self.created_components { + for (&created_id, view_ids) in &self.created_components { self.send_created_event( created_id, + view_ids, inferred_edges_by_component_id.get(&created_id).cloned(), ) .await?; } - for &updated_id in &self.updated_components { - self.send_updated_event(updated_id).await?; + for (&updated_id, view_ids) in &self.updated_components { + self.send_updated_event(updated_id, view_ids).await?; } Ok(()) @@ -980,48 +1159,100 @@ impl<'a> ManagementOperator<'a> { async fn send_created_event( &self, id: ComponentId, + view_ids: &[ViewId], inferred_edges: Option>, ) -> ManagementResult<()> { let component = Component::get_by_id(self.ctx, id).await?; - WsEvent::component_created_with_inferred_edges( - self.ctx, - component - .into_frontend_type( - self.ctx, - Some(&component.geometry(self.ctx, self.view_id).await?), - Added, - &mut HashMap::new(), - ) - .await?, - inferred_edges, - ) - .await? - .publish_on_commit(self.ctx) - .await?; + + for &view_id in view_ids { + WsEvent::component_created_with_inferred_edges( + self.ctx, + component + .into_frontend_type( + self.ctx, + Some(&component.geometry(self.ctx, view_id).await?), + Added, + &mut HashMap::new(), + ) + .await?, + inferred_edges.clone(), + ) + .await? + .publish_on_commit(self.ctx) + .await?; + } Ok(()) } - async fn send_updated_event(&self, id: ComponentId) -> ManagementResult<()> { + async fn send_updated_event( + &self, + id: ComponentId, + view_ids: &[ViewId], + ) -> ManagementResult<()> { let component = Component::get_by_id(self.ctx, id).await?; - WsEvent::component_updated( - self.ctx, - component - .into_frontend_type( - self.ctx, - Some(&component.geometry(self.ctx, self.view_id).await?), - component.change_status(self.ctx).await?, - &mut HashMap::new(), + for &view_id in view_ids { + WsEvent::component_updated( + self.ctx, + component + .into_frontend_type( + self.ctx, + Some(&component.geometry(self.ctx, view_id).await?), + component.change_status(self.ctx).await?, + &mut HashMap::new(), + ) + .await?, + ) + .await? + .publish_on_commit(self.ctx) + .await?; + } + + Ok(()) + } + + async fn create_views(&mut self) -> ManagementResult<()> { + let Some(create_views) = self + .operations + .views + .as_ref() + .and_then(|view_ops| view_ops.create.to_owned()) + else { + return Ok(()); + }; + + for new_view_name in create_views { + if View::find_by_name(self.ctx, &new_view_name) + .await? + .is_some() + { + // view already exists, just skip it + continue; + } + + let new_view = View::new(self.ctx, &new_view_name).await?; + let view_id = new_view.id(); + let view_view = ViewView::from_view(self.ctx, new_view).await?; + + self.ctx + .write_audit_log( + AuditLogKind::CreateView { view_id }, + new_view_name.to_owned(), ) - .await?, - ) - .await? - .publish_on_commit(self.ctx) - .await?; + .await?; + + WsEvent::view_created(self.ctx, view_view) + .await? + .publish_on_commit(self.ctx) + .await?; + + self.view_placeholders.insert(new_view_name, view_id); + } Ok(()) } pub async fn operate(&mut self) -> ManagementResult>> { + self.create_views().await?; let mut pending_operations = self.creates().await?; let mut component_graph = DependencyGraph::new(); pending_operations.extend(self.updates().await?); @@ -1047,7 +1278,7 @@ impl<'a> ManagementOperator<'a> { } let created_component_ids = (!self.created_components.is_empty()) - .then_some(self.created_components.iter().copied().collect()); + .then_some(self.created_components.keys().copied().collect()); self.send_component_ws_events(component_graph, inferred_edges_by_component_id) .await?; @@ -1439,3 +1670,24 @@ fn type_being_set(properties: Option<&serde_json::Value>) -> Option, + default_y: Option, + origin_x: Option, + origin_y: Option, + will_be_frame: bool, +) -> ManagementGeometry { + let mut geometry = geometry.to_owned(); + + if will_be_frame && geometry.height.zip(geometry.width).is_none() { + geometry.height = Some(DEFAULT_FRAME_HEIGHT); + geometry.width = Some(DEFAULT_FRAME_WIDTH); + } + + geometry.x.get_or_insert(default_x.unwrap_or(0.0)); + geometry.y.get_or_insert(default_y.unwrap_or(0.0)); + + geometry.offset_by(origin_x, origin_y) +} diff --git a/lib/dal/tests/integration_test/management.rs b/lib/dal/tests/integration_test/management.rs index ae663a5f80..35d52c917f 100644 --- a/lib/dal/tests/integration_test/management.rs +++ b/lib/dal/tests/integration_test/management.rs @@ -3,20 +3,53 @@ use std::collections::HashSet; use dal::{ diagram::{geometry::Geometry, view::View}, management::{ - prototype::ManagementPrototype, ManagementFuncReturn, ManagementGeometry, - ManagementOperator, + prototype::{ManagementPrototype, ManagementPrototypeExecution}, + ManagementFuncReturn, ManagementGeometry, ManagementOperator, }, - AttributeValue, Component, DalContext, SchemaId, + AttributeValue, Component, ComponentId, DalContext, SchemaId, }; use dal_test::expected::{apply_change_set_to_base, ExpectView}; use dal_test::{ helpers::create_component_for_default_schema_name_in_default_view, test, SCHEMA_ID_SMALL_EVEN_LEGO, }; +use si_id::ViewId; use veritech_client::ManagementFuncStatus; pub mod generator; +async fn exec_mgmt_func( + ctx: &DalContext, + component_id: ComponentId, + prototype_name: &str, + view_id: Option, +) -> (ManagementPrototypeExecution, ManagementFuncReturn) { + let schema_variant_id = Component::schema_variant_id(ctx, component_id) + .await + .expect("get schema variant id"); + + let management_prototype = ManagementPrototype::list_for_variant_id(ctx, schema_variant_id) + .await + .expect("get prototypes") + .into_iter() + .find(|proto| proto.name() == prototype_name) + .expect("could not find prototype"); + + let mut execution_result = management_prototype + .execute(ctx, component_id, 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"); + + (execution_result, result) +} + #[test] async fn update_managed_components_in_view(ctx: &DalContext) { let small_odd_lego = create_component_for_default_schema_name_in_default_view( @@ -47,29 +80,13 @@ async fn update_managed_components_in_view(ctx: &DalContext) { .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"); + let (execution_result, result) = exec_mgmt_func( + ctx, + small_odd_lego.id(), + "Update in View", + Some(new_view_id), + ) + .await; assert_eq!(ManagementFuncStatus::Ok, result.status); assert_eq!(Some(view_name), result.message.as_deref()); @@ -139,29 +156,7 @@ async fn update_managed_components(ctx: &DalContext) { .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") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func(ctx, small_odd_lego.id(), "Update", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -224,29 +219,7 @@ async fn create_component_of_other_schema(ctx: &DalContext) { .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() == "Clone") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func(ctx, small_odd_lego.id(), "Clone", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -313,29 +286,13 @@ async fn create_and_connect_to_self_as_children(ctx: &mut DalContext) { .await .expect("able to update value"); - 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() == "Create and Connect to Self as Children") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func( + ctx, + small_odd_lego.id(), + "Create and Connect to Self as Children", + None, + ) + .await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -446,29 +403,8 @@ async fn create_and_connect_to_self(ctx: &DalContext) { .await .expect("able to update value"); - 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() == "Create and Connect to Self") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = + exec_mgmt_func(ctx, small_odd_lego.id(), "Create and Connect to Self", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -538,29 +474,13 @@ async fn create_and_connect_from_self(ctx: &DalContext) { .await .expect("able to update value"); - 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() == "Create and Connect From Self") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func( + ctx, + small_odd_lego.id(), + "Create and Connect From Self", + None, + ) + .await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -602,30 +522,8 @@ async fn create_component_of_same_schema(ctx: &DalContext) { ) .await .expect("could not create component"); - let variant = small_odd_lego - .schema_variant(ctx) - .await - .expect("get variant"); - - let management_prototype = ManagementPrototype::list_for_variant_id(ctx, variant.id()) - .await - .expect("get prototypes") - .into_iter() - .find(|proto| proto.name() == "Clone") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func(ctx, small_odd_lego.id(), "Clone", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); let operations = result.operations.expect("should have operations"); @@ -669,17 +567,7 @@ async fn create_component_of_same_schema(ctx: &DalContext) { "should have the same manager" ); - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = exec_mgmt_func(ctx, small_odd_lego.id(), "Clone", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -715,10 +603,6 @@ async fn execute_management_func(ctx: &DalContext) { ) .await .expect("could not create component"); - let variant = small_odd_lego - .schema_variant(ctx) - .await - .expect("get variant"); let av_id = Component::attribute_value_for_prop_by_id( ctx, @@ -732,24 +616,8 @@ async fn execute_management_func(ctx: &DalContext) { .await .expect("able to update value"); - let management_prototype = ManagementPrototype::list_for_variant_id(ctx, variant.id()) - .await - .expect("get prototypes") - .into_iter() - .find(|proto| proto.name() == "Import small odd lego") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = + exec_mgmt_func(ctx, small_odd_lego.id(), "Import small odd lego", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); @@ -788,29 +656,9 @@ async fn deeply_nested_children(ctx: &DalContext) { ) .await .expect("could not create component"); - let variant = small_odd_lego - .schema_variant(ctx) - .await - .expect("get variant"); - let management_prototype = ManagementPrototype::list_for_variant_id(ctx, variant.id()) - .await - .expect("get prototypes") - .into_iter() - .find(|proto| proto.name() == "Deeply Nested Children") - .expect("could not find prototype"); - - let mut execution_result = management_prototype - .execute(ctx, small_odd_lego.id(), None) - .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"); + let (execution_result, result) = + exec_mgmt_func(ctx, small_odd_lego.id(), "Deeply Nested Children", None).await; assert_eq!(result.status, ManagementFuncStatus::Ok); let operations = result.operations.expect("should have operations"); @@ -854,3 +702,151 @@ async fn deeply_nested_children(ctx: &DalContext) { component_names ) } + +#[test] +async fn create_in_views(ctx: &DalContext) { + let mut 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 default_view_id = ExpectView::get_id_for_default(ctx).await; + + let manager_x = 50; + let manager_y = 75; + + small_odd_lego + .set_geometry(ctx, default_view_id, manager_x, manager_y, None, None) + .await + .expect("set manager component geometry"); + + let av_id = Component::attribute_value_for_prop_by_id( + ctx, + small_odd_lego.id(), + &["root", "si", "resourceId"], + ) + .await + .expect("av should exist"); + + let view_name = "the black lodge"; + let view = ExpectView::create_with_name(ctx, view_name).await; + + AttributeValue::update(ctx, av_id, Some(serde_json::json!(view_name))) + .await + .expect("able to update value"); + + let (execution_result, result) = + exec_mgmt_func(ctx, small_odd_lego.id(), "Create in Other Views", None).await; + + let operations = result.operations.expect("should have operations"); + + let component_id = + ManagementOperator::new(ctx, small_odd_lego.id(), operations, execution_result, None) + .await + .expect("should create operator") + .operate() + .await + .expect("should operate") + .expect("should return component ids") + .pop() + .expect("should have a component id"); + + let geometries = Geometry::by_view_for_component_id(ctx, component_id) + .await + .expect("get geometries"); + + assert_eq!(2, geometries.len()); + + let black_lodge_geometry = geometries + .get(&view.id()) + .cloned() + .expect("has a geometry in the black lodge"); + + assert_eq!(15, black_lodge_geometry.x() - manager_x); + assert_eq!(15, black_lodge_geometry.y() - manager_y); + + let default_geometry = geometries + .get(&default_view_id) + .cloned() + .expect("has a geometry in the default view"); + + assert_eq!(100, default_geometry.x() - manager_x); + assert_eq!(100, default_geometry.y() - manager_y); +} + +#[test] +async fn create_view_and_in_view(ctx: &DalContext) { + let mut 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 default_view_id = ExpectView::get_id_for_default(ctx).await; + + let manager_x = 50; + let manager_y = 75; + + small_odd_lego + .set_geometry(ctx, default_view_id, manager_x, manager_y, None, None) + .await + .expect("set manager component geometry"); + + let av_id = Component::attribute_value_for_prop_by_id( + ctx, + small_odd_lego.id(), + &["root", "si", "resourceId"], + ) + .await + .expect("av should exist"); + + let view_name = "the red room"; + AttributeValue::update(ctx, av_id, Some(serde_json::json!(view_name))) + .await + .expect("able to update value"); + + let (execution_result, result) = exec_mgmt_func( + ctx, + small_odd_lego.id(), + "Create View and Component in View", + None, + ) + .await; + + let operations = result.operations.expect("should have operations"); + + let component_id = + ManagementOperator::new(ctx, small_odd_lego.id(), operations, execution_result, None) + .await + .expect("should create operator") + .operate() + .await + .expect("should operate") + .expect("should return component ids") + .pop() + .expect("should have a component id"); + + let red_room = View::find_by_name(ctx, view_name) + .await + .expect("find view") + .expect("view exists"); + + let geometries = Geometry::by_view_for_component_id(ctx, component_id) + .await + .expect("get geometries"); + + assert_eq!(1, geometries.len()); + + let red_room_geo = geometries + .get(&red_room.id()) + .cloned() + .expect("has a geometry in the red room"); + + assert_eq!(315, red_room_geo.x() - manager_x); + assert_eq!(315, red_room_geo.y() - manager_y); +} diff --git a/lib/dal/tests/integration_test/management/generator.rs b/lib/dal/tests/integration_test/management/generator.rs index dab3aa3332..7191f0aa23 100644 --- a/lib/dal/tests/integration_test/management/generator.rs +++ b/lib/dal/tests/integration_test/management/generator.rs @@ -62,13 +62,7 @@ async fn calculates_top_and_center(ctx: &DalContext) { .expect("set geo for left lego"); let mut geometries = HashMap::new(); - for geo in [ - center_geo.clone(), - left_geo, - right_geo, - btm_geo.clone(), - top_geo.clone(), - ] { + for geo in [center_geo, left_geo, right_geo, btm_geo, top_geo] { geometries.insert(ComponentId::generate(), geo.into_raw()); }