Skip to content

Commit

Permalink
Merge pull request #5215 from systeminit/zack/mgmt-fns-can-delete-com…
Browse files Browse the repository at this point in the history
…ponents-and-views

feat: delete components from management functions
  • Loading branch information
zacharyhamm authored Jan 9, 2025
2 parents db3c531 + aec717c commit db9bbba
Show file tree
Hide file tree
Showing 9 changed files with 606 additions and 180 deletions.
37 changes: 37 additions & 0 deletions lib/dal-test/src/test_exclusive_schemas/legos/small.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use crate::test_exclusive_schemas::{
SCHEMA_ID_SMALL_EVEN_LEGO,
};

/// The "small odd lego" has a special importance for our tests. It is a
/// repository of example management functions used for management function
/// integration tests
pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego(
ctx: &DalContext,
schema_id: SchemaId,
Expand Down Expand Up @@ -388,6 +391,33 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego(
"test:createViewAndComponentInView",
)?;

let delete_and_erase_components_code = r#"
async function main({ thisComponent, currentView }: Input): Promise<Output> {
const components = thisComponent.properties?.si?.resourceId?.split(",");
console.log(components);
const deleteComponent = components[0];
const deleteComponentWithResource = components[1];
const deleteComponentStillOnHead = components[2];
const eraseComponent = components[3];
return {
status: "ok",
ops: {
delete: [
deleteComponent,
deleteComponentWithResource,
deleteComponentStillOnHead
],
erase: [eraseComponent],
}
};
}
"#;
let delete_and_erase_components = build_management_func(
delete_and_erase_components_code,
"test:deleteAndEraseComponents",
)?;

let fn_name = "test:deleteActionSmallLego";
let delete_action_func = build_action_func(delete_action_code, fn_name)?;

Expand Down Expand Up @@ -533,6 +563,12 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego(
.func_unique_id(&create_view_and_component_in_view.unique_id)
.build()?,
)
.management_func(
ManagementFuncSpec::builder()
.name("Delete and Erase")
.func_unique_id(&delete_and_erase_components.unique_id)
.build()?,
)
.build()?,
)
.build()?;
Expand All @@ -555,6 +591,7 @@ pub(crate) async fn migrate_test_exclusive_schema_small_odd_lego(
.func(deeply_nested_children)
.func(create_component_in_other_views)
.func(create_view_and_component_in_view)
.func(delete_and_erase_components)
.schema(small_lego_schema)
.build()?;

Expand Down
5 changes: 3 additions & 2 deletions lib/dal/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ use crate::{

pub mod code;
pub mod debug;
pub mod delete;
pub mod diff;
pub mod frame;
pub mod inferred_connection_graph;
Expand Down Expand Up @@ -3341,11 +3342,11 @@ impl Component {

pub async fn exists_on_head(
ctx: &DalContext,
component_ids: Vec<ComponentId>,
component_ids: &[ComponentId],
) -> ComponentResult<HashSet<ComponentId>> {
let mut components = HashSet::new();
let base_change_set_ctx = ctx.clone_with_base().await?;
for component_id in component_ids {
for &component_id in component_ids {
let maybe_component =
Component::try_get_by_id(&base_change_set_ctx, component_id).await?;
if maybe_component.is_some() {
Expand Down
160 changes: 160 additions & 0 deletions lib/dal/src/component/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::collections::{HashMap, HashSet};

use si_events::audit_log::AuditLogKind;
use si_id::ComponentId;

use crate::{change_status::ChangeStatus, diagram::SummaryDiagramEdge, DalContext, WsEvent};

use super::{Component, ComponentResult};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ComponentDeletionStatus {
/// The component has a resource but will be deleted by the destroy action
/// after applying to head
MarkedForDeletion,
/// The component was deleted in this changeset but still exists on head
StillExistsOnHead,
/// The component was deleted (either because it was safe to delete without
/// an action, or becuase it was force erased)
Deleted,
}

pub async fn delete_components(
ctx: &DalContext,
component_ids: &[ComponentId],
force_erase: bool,
) -> ComponentResult<HashMap<ComponentId, ComponentDeletionStatus>> {
let head_components: HashSet<ComponentId> =
Component::exists_on_head(ctx, component_ids).await?;
let mut result = HashMap::new();

let mut socket_map = HashMap::new();
let mut socket_map_head = HashMap::new();
let base_change_set_ctx = ctx.clone_with_base().await?;

for &component_id in component_ids {
let component = Component::get_by_id(ctx, component_id).await?;

let incoming_connections = component.incoming_connections(ctx).await?;
let outgoing_connections = component.outgoing_connections(ctx).await?;

let status = delete_component(ctx, &component, force_erase, &head_components).await?;

for incoming_connection in incoming_connections {
let payload = SummaryDiagramEdge {
from_component_id: incoming_connection.from_component_id,
from_socket_id: incoming_connection.from_output_socket_id,
to_component_id: incoming_connection.to_component_id,
to_socket_id: incoming_connection.to_input_socket_id,
change_status: ChangeStatus::Deleted,
created_info: serde_json::to_value(incoming_connection.created_info)?,
deleted_info: serde_json::to_value(incoming_connection.deleted_info)?,
to_delete: true,
from_base_change_set: false,
};
WsEvent::connection_upserted(ctx, payload.into())
.await?
.publish_on_commit(ctx)
.await?;
}

for outgoing_connection in outgoing_connections {
let payload = SummaryDiagramEdge {
from_component_id: outgoing_connection.from_component_id,
from_socket_id: outgoing_connection.from_output_socket_id,
to_component_id: outgoing_connection.to_component_id,
to_socket_id: outgoing_connection.to_input_socket_id,
change_status: ChangeStatus::Deleted,
created_info: serde_json::to_value(outgoing_connection.created_info)?,
deleted_info: serde_json::to_value(outgoing_connection.deleted_info)?,
to_delete: true,
from_base_change_set: false,
};
WsEvent::connection_upserted(ctx, payload.into())
.await?
.publish_on_commit(ctx)
.await?;
}

match status {
ComponentDeletionStatus::MarkedForDeletion => {
let payload = component
.into_frontend_type(ctx, None, ChangeStatus::Deleted, &mut socket_map)
.await?;
WsEvent::component_updated(ctx, payload)
.await?
.publish_on_commit(ctx)
.await?;
}
ComponentDeletionStatus::StillExistsOnHead => {
let component: Component =
Component::get_by_id(&base_change_set_ctx, component_id).await?;
let payload = component
.into_frontend_type(
&base_change_set_ctx,
None,
ChangeStatus::Deleted,
&mut socket_map_head,
)
.await?;
WsEvent::component_updated(ctx, payload)
.await?
.publish_on_commit(ctx)
.await?;
}
ComponentDeletionStatus::Deleted => {
WsEvent::component_deleted(ctx, component_id)
.await?
.publish_on_commit(ctx)
.await?;
}
}

result.insert(component_id, status);
}

Ok(result)
}

async fn delete_component(
ctx: &DalContext,
component: &Component,
force_erase: bool,
head_components: &HashSet<ComponentId>,
) -> ComponentResult<ComponentDeletionStatus> {
let component_id = component.id();
let component_name = component.name(ctx).await?;
let component_schema_variant = component.schema_variant(ctx).await?;

let still_exists_on_head = head_components.contains(&component_id);

let mut status = if force_erase {
Component::remove(ctx, component_id).await?;
ComponentDeletionStatus::Deleted
} else {
// the move semantics here feel strange
match component.clone().delete(ctx).await? {
Some(_) => ComponentDeletionStatus::MarkedForDeletion,
None => ComponentDeletionStatus::Deleted,
}
};

if matches!(status, ComponentDeletionStatus::Deleted) && still_exists_on_head {
status = ComponentDeletionStatus::StillExistsOnHead;
}

ctx.write_audit_log(
AuditLogKind::DeleteComponent {
component_id,
name: component_name.to_owned(),
schema_variant_id: component_schema_variant.id(),
schema_variant_name: component_schema_variant.display_name().to_string(),
},
component_name,
)
.await?;

ctx.workspace_snapshot()?.cleanup().await?;

Ok(status)
}
4 changes: 3 additions & 1 deletion lib/dal/src/func/binding/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl ManagementBinding {
let component_create_type = format!(
r#"
{{
kind: "{name}",
kind?: "{name}",
properties?: {sv_type},
geometry?: Geometry | {{ [key: string]: Geometry }},
connect?: {{
Expand Down Expand Up @@ -166,6 +166,8 @@ type Output = {{
}},
parent?: string,
}} }},
delete?: string[],
erase?: string[],
actions?: {{ [key: string]: {{
add?: ("create" | "update" | "refresh" | "delete" | string)[];
remove?: ("create" | "update" | "refresh" | "delete" | string)[];
Expand Down
Loading

0 comments on commit db9bbba

Please sign in to comment.