From a4be003a75f31c465ed1c704fb72255d30de25a9 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 21 Oct 2024 15:32:07 -0700 Subject: [PATCH] Orphan all children before removing old frame version on schema version upgrade While attempting to (re)connect the children of a frame to the newly upgraded version, we try to find the `OutputSocket` `AttributeValue`s from the old version, but this has already been removed. Rather than creating specialized (re)connect logic for the schema version upgrade scenario, we can have the existing logic treat it as a brand new parent/child relationship by orphaning the children before we remove the old version of the parent frame. --- lib/dal/src/component.rs | 5 + .../integration_test/component/upgrade.rs | 211 +++++++++++++++++- 2 files changed, 214 insertions(+), 2 deletions(-) diff --git a/lib/dal/src/component.rs b/lib/dal/src/component.rs index 0bd26a5b1a..fecefd3d84 100644 --- a/lib/dal/src/component.rs +++ b/lib/dal/src/component.rs @@ -3092,6 +3092,11 @@ impl Component { // ======================================== // Delete original component // ======================================== + // Remove all children from the "old" frame before we delete it. We'll add them all to the + // new frame after we've deleted the old one. + for &child in &original_children { + Frame::orphan_child(ctx, child).await.map_err(Box::new)?; + } // Remove the original resource so that we don't queue a delete action original_component.clear_resource(ctx).await?; diff --git a/lib/dal/tests/integration_test/component/upgrade.rs b/lib/dal/tests/integration_test/component/upgrade.rs index e07162f2f3..6ad173c7f7 100644 --- a/lib/dal/tests/integration_test/component/upgrade.rs +++ b/lib/dal/tests/integration_test/component/upgrade.rs @@ -6,10 +6,12 @@ use dal::prop::PropPath; use dal::schema::variant::authoring::VariantAuthoringClient; use dal::{AttributeValue, Component, ComponentType, DalContext, Prop, SchemaVariant}; use dal_test::expected::{ExpectComponent, ExpectSchema, ExpectSchemaVariant}; -use dal_test::helpers::{create_component_for_default_schema_name, PropEditorTestView}; +use dal_test::helpers::{ + create_component_for_default_schema_name, ChangeSetTestHelpers, PropEditorTestView, +}; use dal_test::test; use itertools::Itertools; -use pretty_assertions_sorted::assert_eq; +use pretty_assertions_sorted::{assert_eq, assert_ne}; use serde_json::json; use std::collections::VecDeque; // TODO test that validates that components that exist on locked variants aren't auto upgraded, but can be upgraded manually @@ -667,6 +669,211 @@ async fn upgrade_array_of_objects(ctx: &mut DalContext) { } } +#[test] +async fn upgrade_frame_with_child(ctx: &mut DalContext) { + let frame_original_code_definition = r#" + function main() { + const theProp = new PropBuilder() + .setName("The Prop") + .setKind("string") + .setWidget(new PropWidgetDefinitionBuilder().setKind("text").build()) + .build(); + const outputSocket = new SocketDefinitionBuilder() + .setName("Socket Data") + .setArity("one") + .setValueFrom( + new ValueFromBuilder() + .setKind("prop") + .setPropPath(["root", "domain", "The Prop"]) + .build() + ) + .build(); + + return new AssetBuilder() + .addProp(theProp) + .addOutputSocket(outputSocket) + .build() + } + "#; + let original_frame_variant = ExpectSchemaVariant( + VariantAuthoringClient::create_schema_and_variant_from_code( + ctx, + "Initial Variant", + None, + None, + "Category name", + "#0000ff", + frame_original_code_definition, + ) + .await + .expect("Unable to create frame schema and variant") + .id, + ); + update_schema_variant_component_type( + ctx, + original_frame_variant, + ComponentType::ConfigurationFrameDown, + ) + .await; + + let component_code_definition = r#" + function main() { + const theProp = new PropBuilder() + .setName("The Prop") + .setKind("string") + .setWidget(new PropWidgetDefinitionBuilder().setKind("text").build()) + .setValueFrom( + new ValueFromBuilder() + .setKind("inputSocket") + .setSocketName("Socket Data") + .build() + ) + .build(); + + const inputSocket = new SocketDefinitionBuilder() + .setName("Socket Data") + .setArity("one") + .build(); + + return new AssetBuilder() + .addProp(theProp) + .addInputSocket(inputSocket) + .build(); + } + "#; + + let child_variant = ExpectSchemaVariant( + VariantAuthoringClient::create_schema_and_variant_from_code( + ctx, + "Child Variant", + None, + None, + "Another Category", + "#0077cc", + component_code_definition, + ) + .await + .expect("Unable to create child component schema and variant") + .id, + ); + + let frame_component = original_frame_variant.create_component(ctx).await; + let child_component = child_variant.create_component(ctx).await; + child_component.upsert_parent(ctx, frame_component).await; + + let inferred_connections = child_component + .component(ctx) + .await + .inferred_incoming_connections(ctx) + .await + .expect("Unable to get inferred incoming connections for child component."); + + assert_eq!(1, inferred_connections.len()); + let inferred_connection = inferred_connections + .first() + .expect("Unable to get first element of a single element Vec."); + assert_eq!(frame_component.id(), inferred_connection.from_component_id,); + + ChangeSetTestHelpers::apply_change_set_to_base(ctx) + .await + .expect("Unable to apply change set"); + let change_set = ChangeSetTestHelpers::fork_from_head_change_set(ctx) + .await + .expect("Unable to fork change set"); + ctx.update_visibility_and_snapshot_to_visibility(change_set.id) + .await + .expect("Unable to update ctx"); + + let updated_frame_variant = original_frame_variant.create_unlocked_copy(ctx).await; + let updated_frame_code_definition = r#" + function main() { + const theProp = new PropBuilder() + .setName("The Prop") + .setKind("string") + .setWidget(new PropWidgetDefinitionBuilder().setKind("text").build()) + .build(); + const outputSocket = new SocketDefinitionBuilder() + .setName("Socket Data") + .setArity("one") + .setValueFrom( + new ValueFromBuilder() + .setKind("prop") + .setPropPath(["root", "domain", "The Prop"]) + .build() + ) + .build(); + + const anotherProp = new PropBuilder() + .setName("Another Prop") + .setKind("string") + .setWidget(new PropWidgetDefinitionBuilder().setKind("text").build()) + .build(); + + return new AssetBuilder() + .addProp(theProp) + .addProp(anotherProp) + .addOutputSocket(outputSocket) + .build() + } + + "#; + let frame_variant = original_frame_variant.schema_variant(ctx).await; + VariantAuthoringClient::save_variant_content( + ctx, + updated_frame_variant.id(), + frame_variant + .schema(ctx) + .await + .expect("Unable to get frame schema.") + .name + .clone(), + frame_variant.display_name(), + frame_variant.category(), + frame_variant.description(), + frame_variant.link(), + frame_variant + .get_color(ctx) + .await + .expect("Unable to get schema variant color."), + frame_variant.component_type(), + Some(updated_frame_code_definition), + ) + .await + .expect("Unable to update variant."); + + let regenerated_frame_variant = ExpectSchemaVariant( + VariantAuthoringClient::regenerate_variant(ctx, updated_frame_variant.id()) + .await + .expect("Unable to regenerate variant."), + ); + + assert_ne!(original_frame_variant.id(), regenerated_frame_variant.id()); + assert_eq!(updated_frame_variant.id(), regenerated_frame_variant.id()); + + let upgraded_frame_component = frame_component + .component(ctx) + .await + .upgrade_to_new_variant(ctx, regenerated_frame_variant.id()) + .await + .expect("Unable to upgrade frame component to new variant."); + + let inferred_connections = child_component + .component(ctx) + .await + .inferred_incoming_connections(ctx) + .await + .expect("Unable to get inferred incoming connections for child component."); + + assert_eq!(1, inferred_connections.len()); + let inferred_connection = inferred_connections + .first() + .expect("Unable to get first element of a single element Vec."); + assert_eq!( + upgraded_frame_component.id(), + inferred_connection.from_component_id + ); +} + async fn update_schema_variant_component_type( ctx: &mut DalContext, variant: ExpectSchemaVariant,