Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit f7fab90

Browse files
committed
Enable delete_connection API endpoint
Removing an AttributePrototypeArgument will now cause the AttributeValue(s) that use that Prototype to automatically be updated to reflect the new function inputs.
1 parent 8232667 commit f7fab90

5 files changed

Lines changed: 329 additions & 23 deletions

File tree

lib/dal/src/attribute/prototype/argument.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! value. It defines source of the value for the function argument in the
44
//! context of the prototype.
55
6+
use std::collections::HashSet;
7+
68
use serde::{Deserialize, Serialize};
79
use telemetry::prelude::*;
810
use thiserror::Error;
@@ -22,8 +24,8 @@ use crate::{
2224
},
2325
WorkspaceSnapshotError,
2426
},
25-
AttributePrototype, AttributePrototypeId, ComponentId, DalContext, OutputSocketId, PropId,
26-
Timestamp, TransactionsError,
27+
AttributePrototype, AttributePrototypeId, AttributeValue, ComponentId, DalContext,
28+
OutputSocketId, PropId, Timestamp, TransactionsError,
2729
};
2830

2931
use self::{
@@ -45,6 +47,8 @@ pk!(AttributePrototypeArgumentId);
4547
pub enum AttributePrototypeArgumentError {
4648
#[error("attribute prototype error: {0}")]
4749
AttributePrototype(#[from] AttributePrototypeError),
50+
#[error("attribute value error: {0}")]
51+
AttributeValue(String),
4852
#[error("change set error: {0}")]
4953
ChangeSet(#[from] ChangeSetPointerError),
5054
#[error("edge weight error: {0}")]
@@ -59,8 +63,12 @@ pub enum AttributePrototypeArgumentError {
5963
LayerDb(#[from] si_layer_cache::LayerDbError),
6064
#[error("attribute prototype argument {0} has no func argument")]
6165
MissingFuncArgument(AttributePrototypeArgumentId),
66+
#[error("attribute prototype argument {0} has no value source")]
67+
MissingSource(AttributePrototypeArgumentId),
6268
#[error("node weight error: {0}")]
6369
NodeWeight(#[from] NodeWeightError),
70+
#[error("no targets for prototype argument: {0}")]
71+
NoTargets(AttributePrototypeArgumentId),
6472
#[error("serde json error: {0}")]
6573
Serde(#[from] serde_json::Error),
6674
#[error("transactions error: {0}")]
@@ -525,9 +533,37 @@ impl AttributePrototypeArgument {
525533
ctx: &DalContext,
526534
apa_id: AttributePrototypeArgumentId,
527535
) -> AttributePrototypeArgumentResult<()> {
536+
let apa = Self::get_by_id(ctx, apa_id).await?;
537+
let prototype_id = apa.prototype_id(ctx).await?;
538+
// Find all of the "destination" attribute values.
539+
let mut avs_to_update = AttributePrototype::attribute_value_ids(ctx, prototype_id).await?;
540+
// If the argument has targets, then we only care about AVs that are for the same
541+
// destination component.
542+
if let Some(targets) = apa.targets() {
543+
let mut av_ids_to_keep = HashSet::new();
544+
for av_id in &avs_to_update {
545+
let component_id = AttributeValue::component_id(ctx, *av_id)
546+
.await
547+
.map_err(|e| AttributePrototypeArgumentError::AttributeValue(e.to_string()))?;
548+
if component_id == targets.destination_component_id {
549+
av_ids_to_keep.insert(*av_id);
550+
}
551+
}
552+
avs_to_update.retain(|av_id| av_ids_to_keep.contains(av_id));
553+
}
554+
555+
// Remove the argument
528556
ctx.workspace_snapshot()?
529557
.remove_node_by_id(ctx.change_set_pointer()?, apa_id)
530558
.await?;
559+
// Update the destination attribute values
560+
for av_id_to_update in &avs_to_update {
561+
AttributeValue::update_from_prototype_function(ctx, *av_id_to_update)
562+
.await
563+
.map_err(|e| AttributePrototypeArgumentError::AttributeValue(e.to_string()))?;
564+
}
565+
// Enqueue a dependent values update with the destination attribute values
566+
ctx.enqueue_dependent_values_update(avs_to_update).await?;
531567

532568
Ok(())
533569
}

lib/dal/tests/integration_test/connection.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use dal::attribute::prototype::argument::AttributePrototypeArgument;
12
use dal::diagram::Diagram;
23
use dal::{
34
AttributeValue, Component, DalContext, InputSocket, OutputSocket, Schema, SchemaVariant,
@@ -217,3 +218,197 @@ async fn connect_to_one_destination_with_multiple_candidates_of_same_schema_vari
217218
assert_eq!(edge.from_component_id, source.id());
218219
assert_eq!(edge.to_component_id, destination.id());
219220
}
221+
222+
#[test]
223+
async fn remove_connection(ctx: &mut DalContext) {
224+
// Get the source schema variant id.
225+
let docker_image_schema = Schema::find_by_name(ctx, "Docker Image")
226+
.await
227+
.expect("could not perform find by name")
228+
.expect("no schema found");
229+
let mut docker_image_schema_variants =
230+
SchemaVariant::list_for_schema(ctx, docker_image_schema.id())
231+
.await
232+
.expect("could not list schema variants for schema");
233+
let docker_image_schema_variant = docker_image_schema_variants
234+
.pop()
235+
.expect("schema variants are empty");
236+
let docker_image_schema_variant_id = docker_image_schema_variant.id();
237+
238+
// Get the destination schema variant id.
239+
let butane_schema = Schema::find_by_name(ctx, "Butane")
240+
.await
241+
.expect("could not perform find by name")
242+
.expect("no schema found");
243+
let mut butane_schema_variants = SchemaVariant::list_for_schema(ctx, butane_schema.id())
244+
.await
245+
.expect("could not list schema variants for schema");
246+
let butane_schema_variant = butane_schema_variants
247+
.pop()
248+
.expect("schema variants are empty");
249+
let butane_schema_variant_id = butane_schema_variant.id();
250+
251+
// Find the sockets we want to use.
252+
let output_socket =
253+
OutputSocket::find_with_name(ctx, "Container Image", docker_image_schema_variant_id)
254+
.await
255+
.expect("could not perform find output socket")
256+
.expect("output socket not found");
257+
let input_socket =
258+
InputSocket::find_with_name(ctx, "Container Image", butane_schema_variant_id)
259+
.await
260+
.expect("could not perform find input socket")
261+
.expect("input socket not found");
262+
263+
// Create a component for both the source and the destination
264+
let oysters_component =
265+
Component::new(ctx, "oysters in my pocket", docker_image_schema_variant_id)
266+
.await
267+
.expect("could not create component");
268+
269+
ctx.blocking_commit()
270+
.await
271+
.expect("blocking commit after component creation");
272+
273+
ctx.update_snapshot_to_visibility()
274+
.await
275+
.expect("update_snapshot_to_visibility");
276+
277+
// Create a second component for a second source
278+
let lunch_component =
279+
Component::new(ctx, "were saving for lunch", docker_image_schema_variant_id)
280+
.await
281+
.expect("could not create component");
282+
283+
ctx.blocking_commit()
284+
.await
285+
.expect("blocking commit after component 2 creation");
286+
287+
ctx.update_snapshot_to_visibility()
288+
.await
289+
.expect("update_snapshot_to_visibility");
290+
291+
let royel_component = Component::new(ctx, "royel otis", butane_schema_variant_id)
292+
.await
293+
.expect("could not create component");
294+
295+
ctx.blocking_commit()
296+
.await
297+
.expect("blocking commit after butane component creation");
298+
299+
ctx.update_snapshot_to_visibility()
300+
.await
301+
.expect("update_snapshot_to_visibility");
302+
303+
// Connect the components!
304+
let inter_component_attribute_prototype_argument_id = Component::connect(
305+
ctx,
306+
oysters_component.id(),
307+
output_socket.id(),
308+
royel_component.id(),
309+
input_socket.id(),
310+
)
311+
.await
312+
.expect("could not connect components");
313+
314+
ctx.blocking_commit().await.expect("blocking commit failed");
315+
316+
ctx.update_snapshot_to_visibility()
317+
.await
318+
.expect("update_snapshot_to_visibility");
319+
320+
// Connect component 2
321+
let _inter_component_attribute_prototype_argument_id = Component::connect(
322+
ctx,
323+
lunch_component.id(),
324+
output_socket.id(),
325+
royel_component.id(),
326+
input_socket.id(),
327+
)
328+
.await
329+
.expect("could not connect components");
330+
331+
ctx.blocking_commit().await.expect("blocking commit failed");
332+
333+
ctx.update_snapshot_to_visibility()
334+
.await
335+
.expect("update_snapshot_to_visibility");
336+
337+
//dbg!(royel_component.incoming_connections(ctx).await.expect("ok"));
338+
339+
let units_value_id = royel_component
340+
.attribute_values_for_prop(ctx, &["root", "domain", "systemd", "units"])
341+
.await
342+
.expect("able to get values for units")
343+
.first()
344+
.copied()
345+
.expect("has a value");
346+
347+
let materialized_view = AttributeValue::get_by_id(ctx, units_value_id)
348+
.await
349+
.expect("value exists")
350+
.materialized_view(ctx)
351+
.await
352+
.expect("able to get units materialized_view")
353+
.expect("units has a materialized_view");
354+
355+
dbg!(lunch_component
356+
.materialized_view(ctx)
357+
.await
358+
.expect("get docker image materialized_view"));
359+
360+
assert!(matches!(materialized_view, serde_json::Value::Array(_)));
361+
362+
if let serde_json::Value::Array(units_array) = materialized_view {
363+
assert_eq!(2, units_array.len())
364+
}
365+
366+
// Assemble the diagram and check the edges.
367+
let diagram = Diagram::assemble(ctx)
368+
.await
369+
.expect("could not assemble the diagram");
370+
assert_eq!(2, diagram.edges.len());
371+
372+
// Disconnect Component 1
373+
AttributePrototypeArgument::remove(ctx, inter_component_attribute_prototype_argument_id)
374+
.await
375+
.expect("Unable to remove inter component attribute prototype argument");
376+
377+
ctx.blocking_commit().await.expect("blocking commit failed");
378+
ctx.update_snapshot_to_visibility()
379+
.await
380+
.expect("Unable to update snapshot to visibility");
381+
382+
let units_value_id = royel_component
383+
.attribute_values_for_prop(ctx, &["root", "domain", "systemd", "units"])
384+
.await
385+
.expect("able to get values for units")
386+
.first()
387+
.copied()
388+
.expect("has a value");
389+
390+
let materialized_view = AttributeValue::get_by_id(ctx, units_value_id)
391+
.await
392+
.expect("value exists")
393+
.materialized_view(ctx)
394+
.await
395+
.expect("able to get units materialized_view")
396+
.expect("units has a materialized_view");
397+
398+
dbg!(lunch_component
399+
.materialized_view(ctx)
400+
.await
401+
.expect("get docker image materialized_view"));
402+
403+
assert!(matches!(materialized_view, serde_json::Value::Array(_)));
404+
405+
if let serde_json::Value::Array(units_array) = materialized_view {
406+
assert_eq!(1, units_array.len())
407+
}
408+
409+
// Assemble the diagram and check the edges.
410+
let diagram = Diagram::assemble(ctx)
411+
.await
412+
.expect("could not assemble the diagram");
413+
assert_eq!(1, diagram.edges.len());
414+
}

lib/sdf-server/src/server/service/diagram.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ use axum::response::{IntoResponse, Response};
33
use axum::routing::{get, post};
44
use axum::Json;
55
use axum::Router;
6+
use dal::attribute::prototype::argument::AttributePrototypeArgumentError;
67
use dal::attribute::prototype::AttributePrototypeError;
8+
use dal::attribute::value::AttributeValueError;
79
use dal::component::ComponentError;
810
use dal::socket::input::InputSocketError;
11+
use dal::socket::output::OutputSocketError;
912
use dal::workspace_snapshot::WorkspaceSnapshotError;
1013
use dal::WsEventError;
1114
use dal::{
@@ -25,7 +28,7 @@ pub mod set_component_position;
2528

2629
// mod connect_component_to_frame;
2730
pub mod delete_component;
28-
//pub mod delete_connection;
31+
pub mod delete_connection;
2932
// pub mod paste_component;
3033
pub mod remove_delete_intent;
3134
// pub mod restore_connection;
@@ -37,8 +40,12 @@ pub enum DiagramError {
3740
Action(#[from] ActionError),
3841
#[error("action: {0}")]
3942
ActionPrototype(#[from] ActionPrototypeError),
40-
#[error("prototype error: {0}")]
43+
#[error("attribute prototype error: {0}")]
4144
AttributePrototype(#[from] AttributePrototypeError),
45+
#[error("attribute prototype argument error: {0}")]
46+
AttributePrototypeArgument(#[from] AttributePrototypeArgumentError),
47+
#[error("attribute value error: {0}")]
48+
AttributeValue(#[from] AttributeValueError),
4249
#[error("changeset error: {0}")]
4350
ChangeSet(#[from] ChangeSetPointerError),
4451
#[error("change set not found")]
@@ -73,6 +80,8 @@ pub enum DiagramError {
7380
Nats(#[from] si_data_nats::NatsError),
7481
#[error("not authorized")]
7582
NotAuthorized,
83+
#[error("output socket error: {0}")]
84+
OutputSocket(#[from] OutputSocketError),
7685
#[error("paste failed")]
7786
PasteError,
7887
#[error(transparent)]
@@ -112,10 +121,10 @@ impl IntoResponse for DiagramError {
112121

113122
pub fn routes() -> Router<AppState> {
114123
Router::new()
115-
// .route(
116-
// "/delete_connection",
117-
// post(delete_connection::delete_connection),
118-
// )
124+
.route(
125+
"/delete_connection",
126+
post(delete_connection::delete_connection),
127+
)
119128
// .route(
120129
// "/restore_connection",
121130
// post(restore_connection::restore_connection),

0 commit comments

Comments
 (0)