From 475c755cdd1ca966a1dda7736805829218286786 Mon Sep 17 00:00:00 2001 From: Brit Myers Date: Mon, 25 Mar 2024 11:29:38 -0400 Subject: [PATCH] Reimplement attributes view for component debug screen --- app/web/src/components/ComponentDetails.vue | 2 +- .../{ => Debug}/AttributeDebugView.vue | 24 +- .../{ => Debug}/ComponentDebugDetails.vue | 13 +- .../components/{ => Debug}/DebugViewItem.vue | 0 .../src/components/Debug/SocketDebugView.vue | 56 ++ app/web/src/store/components.store.ts | 35 +- lib/dal/src/attribute/prototype.rs | 28 + lib/dal/src/attribute/prototype/debug.rs | 206 +++++++ lib/dal/src/attribute/value.rs | 79 ++- lib/dal/src/attribute/value/debug.rs | 126 +++++ lib/dal/src/component.rs | 1 + lib/dal/src/component/debug.rs | 288 ++++++++++ lib/dal/src/component/view/debug.rs | 505 ------------------ lib/dal/src/func/argument.rs | 13 + lib/dal/src/socket.rs | 1 + lib/dal/src/socket/debug.rs | 144 +++++ lib/dal/src/workspace_snapshot.rs | 43 ++ lib/dal/src/workspace_snapshot/node_weight.rs | 6 + .../node_weight/ordering_node_weight.rs | 10 + lib/dal/tests/integration_test/component.rs | 2 +- .../tests/integration_test/component/debug.rs | 78 +++ .../src/server/service/component.rs | 10 +- .../src/server/service/component/debug.rs | 200 +------ lib/sdf-server/src/server/service/graphviz.rs | 11 +- lib/si-pkg/src/spec/attribute_value.rs | 63 ++- 25 files changed, 1185 insertions(+), 759 deletions(-) rename app/web/src/components/{ => Debug}/AttributeDebugView.vue (57%) rename app/web/src/components/{ => Debug}/ComponentDebugDetails.vue (90%) rename app/web/src/components/{ => Debug}/DebugViewItem.vue (100%) create mode 100644 app/web/src/components/Debug/SocketDebugView.vue create mode 100644 lib/dal/src/attribute/prototype/debug.rs create mode 100644 lib/dal/src/attribute/value/debug.rs create mode 100644 lib/dal/src/component/debug.rs delete mode 100644 lib/dal/src/component/view/debug.rs create mode 100644 lib/dal/src/socket/debug.rs create mode 100644 lib/dal/tests/integration_test/component/debug.rs diff --git a/app/web/src/components/ComponentDetails.vue b/app/web/src/components/ComponentDetails.vue index e103abb97d..7f93681e07 100644 --- a/app/web/src/components/ComponentDetails.vue +++ b/app/web/src/components/ComponentDetails.vue @@ -190,7 +190,7 @@ import { ComponentType } from "@/components/ModelingDiagram/diagram_types"; import ComponentCard from "./ComponentCard.vue"; import DetailsPanelTimestamps from "./DetailsPanelTimestamps.vue"; import ComponentDetailsResource from "./ComponentDetailsResource.vue"; -import ComponentDebugDetails from "./ComponentDebugDetails.vue"; +import ComponentDebugDetails from "./Debug/ComponentDebugDetails.vue"; import AssetQualificationsDetails from "./AssetQualificationsDetails.vue"; import AssetActionsDetails from "./AssetActionsDetails.vue"; import SidebarSubpanelTitle from "./SidebarSubpanelTitle.vue"; diff --git a/app/web/src/components/AttributeDebugView.vue b/app/web/src/components/Debug/AttributeDebugView.vue similarity index 57% rename from app/web/src/components/AttributeDebugView.vue rename to app/web/src/components/Debug/AttributeDebugView.vue index 8f2b919ca5..a8ad25f057 100644 --- a/app/web/src/components/AttributeDebugView.vue +++ b/app/web/src/components/Debug/AttributeDebugView.vue @@ -1,7 +1,7 @@ diff --git a/app/web/src/components/ComponentDebugDetails.vue b/app/web/src/components/Debug/ComponentDebugDetails.vue similarity index 90% rename from app/web/src/components/ComponentDebugDetails.vue rename to app/web/src/components/Debug/ComponentDebugDetails.vue index fad326bf22..418aaf31da 100644 --- a/app/web/src/components/ComponentDebugDetails.vue +++ b/app/web/src/components/Debug/ComponentDebugDetails.vue @@ -30,7 +30,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -102,6 +102,7 @@ import { import { PropType, computed, onMounted } from "vue"; import { ComponentId, useComponentsStore } from "@/store/components.store"; import AttributeDebugView from "./AttributeDebugView.vue"; +import SocketDebugView from "./SocketDebugView.vue"; import DebugViewItem from "./DebugViewItem.vue"; const componentsStore = useComponentsStore(); diff --git a/app/web/src/components/DebugViewItem.vue b/app/web/src/components/Debug/DebugViewItem.vue similarity index 100% rename from app/web/src/components/DebugViewItem.vue rename to app/web/src/components/Debug/DebugViewItem.vue diff --git a/app/web/src/components/Debug/SocketDebugView.vue b/app/web/src/components/Debug/SocketDebugView.vue new file mode 100644 index 0000000000..9404b734bc --- /dev/null +++ b/app/web/src/components/Debug/SocketDebugView.vue @@ -0,0 +1,56 @@ + + + diff --git a/app/web/src/store/components.store.ts b/app/web/src/store/components.store.ts index 55c547c552..e36bc4a91a 100644 --- a/app/web/src/store/components.store.ts +++ b/app/web/src/store/components.store.ts @@ -135,8 +135,10 @@ const qualificationStatusToIconMap: Record< notexists: { icon: "none" }, }; -export interface AttributeDebugData { - valueId: string; +export interface AttributeDebugView { + path: string; + name: string; + attributeValueId: string; proxyFor?: string | null; funcName: string; funcId: string; @@ -148,37 +150,20 @@ export interface AttributeDebugData { }; value: object | string | number | boolean | null; prototypeId: string; - prototypeContext: { - prop_id: string; - internal_provider_id: string; - external_provider_id: string; - component_id: string; - }; kind: string; - prototypeInChangeSet: boolean; - valueInChangeSet: boolean; - implicitValue?: object | string | number | boolean | null; - implicitValueContext?: { - prop_id: string; - internal_provider_id: string; - external_provider_id: string; - component_id: string; - }; - implicitFuncName?: string; + materializedView?: string; } - -export interface AttributeDebugView { - path: string; - name: string; - debugData: AttributeDebugData; +export interface SocketDebugView extends AttributeDebugView { + socketId: string; + connectionAnnotations: string[]; } export interface ComponentDebugView { name: string; schemaVariantId: string; attributes: AttributeDebugView[]; - inputSockets: AttributeDebugView[]; - outputSockets: AttributeDebugView[]; + inputSockets: SocketDebugView[]; + outputSockets: SocketDebugView[]; } type EventBusEvents = { diff --git a/lib/dal/src/attribute/prototype.rs b/lib/dal/src/attribute/prototype.rs index cd0be5e882..4d1e140acb 100644 --- a/lib/dal/src/attribute/prototype.rs +++ b/lib/dal/src/attribute/prototype.rs @@ -37,6 +37,7 @@ use crate::{ }; pub mod argument; +pub mod debug; #[remain::sorted] #[derive(Error, Debug)] @@ -224,6 +225,33 @@ impl AttributePrototype { Ok(None) } + pub async fn find_for_input_socket( + ctx: &DalContext, + input_socket_id: InputSocketId, + ) -> AttributePrototypeResult> { + let workspace_snapshot = ctx.workspace_snapshot()?; + + if let Some(prototype_idx) = workspace_snapshot + .edges_directed(input_socket_id, Direction::Outgoing) + .await? + .iter() + .find(|(edge_weight, _, _)| { + EdgeWeightKindDiscriminants::Prototype == edge_weight.kind().into() + }) + .map(|(_, _, target_idx)| target_idx) + { + let node_weight = workspace_snapshot.get_node_weight(*prototype_idx).await?; + + if matches!( + node_weight.content_address_discriminants(), + Some(ContentAddressDiscriminants::AttributePrototype) + ) { + return Ok(Some(node_weight.id().into())); + } + } + + Ok(None) + } pub async fn get_by_id( ctx: &DalContext, diff --git a/lib/dal/src/attribute/prototype/debug.rs b/lib/dal/src/attribute/prototype/debug.rs new file mode 100644 index 0000000000..c9a21d7f0c --- /dev/null +++ b/lib/dal/src/attribute/prototype/debug.rs @@ -0,0 +1,206 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use telemetry::prelude::*; +use thiserror::Error; + +use crate::attribute::prototype::argument::{ + static_value::StaticArgumentValue, + value_source::{ValueSource, ValueSourceError}, + AttributePrototypeArgument, AttributePrototypeArgumentError, +}; +use crate::attribute::prototype::AttributePrototypeError; +use crate::attribute::value::{AttributeValueError, ValueIsFor}; +use crate::func::argument::FuncArgument; +use crate::func::argument::FuncArgumentError; +use crate::func::execution::{FuncExecution, FuncExecutionError}; +use crate::prop::{PropError, PropPath}; +use crate::socket::input::InputSocketError; +use crate::socket::output::OutputSocketError; +use crate::workspace_snapshot::node_weight::NodeWeightError; +use crate::workspace_snapshot::WorkspaceSnapshotError; +use crate::{ + AttributePrototype, AttributePrototypeId, AttributeValue, AttributeValueId, Component, + ComponentError, DalContext, Func, FuncError, FuncId, InputSocket, Prop, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AttributePrototypeDebugView { + pub func_id: FuncId, + pub func_execution: Option, + pub id: AttributePrototypeId, + pub func_name: String, + pub func_args: HashMap>, + pub arg_sources: HashMap>, + pub attribute_values: Vec, +} + +type AttributePrototypeDebugViewResult = Result; + +#[remain::sorted] +#[derive(Error, Debug)] +pub enum AttributePrototypeDebugViewError { + #[error("attribute prototype argument Error: {0}")] + AttributePrototypeArgumentError(#[from] AttributePrototypeArgumentError), + #[error("attribute prototype error: {0}")] + AttributePrototypeError(#[from] AttributePrototypeError), + #[error("attribute value error: {0}")] + AttributeValue(#[from] AttributeValueError), + #[error("component error: {0}")] + ComponentError(#[from] ComponentError), + #[error("func error: {0}")] + Func(#[from] FuncError), + #[error("func argument error: {0}")] + FuncArgumentError(#[from] FuncArgumentError), + #[error("func execution error: {0}")] + FuncExecution(#[from] FuncExecutionError), + #[error("input socket error: {0}")] + InputSocketError(#[from] InputSocketError), + #[error("node weight error: {0}")] + NodeWeightError(#[from] NodeWeightError), + #[error("output socket error: {0}")] + OutputSocketError(#[from] OutputSocketError), + #[error("prop error: {0}")] + PropError(#[from] PropError), + #[error("value source error: {0}")] + ValueSourceError(#[from] ValueSourceError), + #[error("workspace snapshot error: {0}")] + WorkspaceSnapshotError(#[from] WorkspaceSnapshotError), +} + +impl AttributePrototypeDebugView { + #[instrument(level = "debug", skip_all)] + pub async fn assemble( + ctx: &DalContext, + attribute_value_id: AttributeValueId, + ) -> AttributePrototypeDebugViewResult { + let prototype_id = AttributeValue::prototype_id(ctx, attribute_value_id).await?; + + let destination_component_id = + AttributeValue::component_id(ctx, attribute_value_id).await?; + + let mut func_binding_args: HashMap> = HashMap::new(); + let mut arg_sources: HashMap> = HashMap::new(); + + let attribute_prototype_ids = + AttributePrototypeArgument::list_ids_for_prototype(ctx, prototype_id).await?; + for attribute_prototype_id in attribute_prototype_ids { + let attribute_prototype_argument = + AttributePrototypeArgument::get_by_id(ctx, attribute_prototype_id).await?; + let expected_source_component_id = attribute_prototype_argument + .targets() + .map(|targets| targets.source_component_id) + .unwrap_or(destination_component_id); + + if attribute_prototype_argument + .targets() + .map_or(true, |targets| { + targets.destination_component_id == destination_component_id + }) + { + // If the "source" Component is marked for deletion, and we (the destination) are + // *NOT*, then we should ignore the argument as data should not flow from things + // that are marked for deletion to ones that are not. + let destination_component = Component::get_by_id(ctx, destination_component_id) + .await + .map_err(|e| AttributeValueError::Component(e.to_string()))?; + + let source_component = Component::get_by_id(ctx, expected_source_component_id) + .await + .map_err(|e| AttributeValueError::Component(e.to_string()))?; + + if source_component.to_delete() && !destination_component.to_delete() { + continue; + } + + let func_arg_id = + AttributePrototypeArgument::func_argument_id_by_id(ctx, attribute_prototype_id) + .await?; + let func_arg_name = FuncArgument::get_name_by_id(ctx, func_arg_id).await?; + + let values_for_arg = match AttributePrototypeArgument::value_source_by_id( + ctx, + attribute_prototype_id, + ) + .await? + .ok_or( + AttributeValueError::AttributePrototypeArgumentMissingValueSource( + attribute_prototype_id, + ), + )? { + ValueSource::StaticArgumentValue(static_argument_value_id) => { + let val = StaticArgumentValue::get_by_id(ctx, static_argument_value_id) + .await? + .value; + arg_sources.insert( + func_arg_name.clone(), + Some(PropPath::new(["Static Value"]).to_string()), + ); + vec![val] + } + other_source => { + let mut values = vec![]; + + for attribute_value_id in other_source + .attribute_values_for_component_id(ctx, expected_source_component_id) + .await? + { + let attribute_value = + AttributeValue::get_by_id(ctx, attribute_value_id).await?; + + let attribute_value_name = + AttributeValue::is_for(ctx, attribute_value_id).await?; + let prop_path: PropPath = match attribute_value_name { + ValueIsFor::InputSocket(id) => { + let inputsock = InputSocket::get_by_id(ctx, id).await?; + PropPath::new(["Input Socket", inputsock.name()]) + } + ValueIsFor::Prop(_) => { + let prop_id = + AttributeValue::prop_id_for_id(ctx, attribute_value.id()) + .await?; + Prop::path_by_id(ctx, prop_id).await? + } + ValueIsFor::OutputSocket(_) => continue, + }; + arg_sources.insert( + func_arg_name.clone(), + Some(prop_path.with_replaced_sep("/")), + ); + values.push( + attribute_value + .materialized_view(ctx) + .await? + .unwrap_or(Value::Null), + ); + } + + values + } + }; + + func_binding_args + .entry(func_arg_name) + .and_modify(|values| values.extend(values_for_arg.clone())) + .or_insert(values_for_arg); + } + } + let attribute_values = AttributePrototype::attribute_value_ids(ctx, prototype_id).await?; + let func_id = AttributePrototype::func_id(ctx, prototype_id).await?; + + let func_execution = + Some(FuncExecution::get_latest_execution_by_func_id(ctx, &func_id).await?); + let func_name = Func::get_by_id(ctx, func_id).await?.name; + + Ok(AttributePrototypeDebugView { + func_args: func_binding_args, + func_id, + func_name, + func_execution, + arg_sources, + id: prototype_id, + attribute_values, + }) + } +} diff --git a/lib/dal/src/attribute/value.rs b/lib/dal/src/attribute/value.rs index 0299f8c691..adec6cee01 100644 --- a/lib/dal/src/attribute/value.rs +++ b/lib/dal/src/attribute/value.rs @@ -44,6 +44,7 @@ use std::sync::Arc; use petgraph::prelude::*; use serde::{Deserialize, Serialize}; use serde_json::Value; +use si_pkg::{AttributeValuePath, KeyOrIndex}; use telemetry::prelude::*; use thiserror::Error; use tokio::sync::{RwLock, TryLockError}; @@ -61,6 +62,7 @@ use crate::func::intrinsics::IntrinsicFunc; use crate::func::FuncError; use crate::prop::PropError; use crate::socket::input::InputSocketError; +use crate::socket::output::OutputSocketError; use crate::workspace_snapshot::content_address::{ContentAddress, ContentAddressDiscriminants}; use crate::workspace_snapshot::edge_weight::{ EdgeWeight, EdgeWeightError, EdgeWeightKind, EdgeWeightKindDiscriminants, @@ -71,7 +73,8 @@ use crate::workspace_snapshot::node_weight::{ use crate::workspace_snapshot::{serde_value_to_string_type, WorkspaceSnapshotError}; use crate::{ pk, AttributePrototype, AttributePrototypeId, Component, ComponentId, DalContext, Func, FuncId, - InputSocketId, OutputSocketId, Prop, PropId, PropKind, TransactionsError, + InputSocket, InputSocketId, OutputSocket, OutputSocketId, Prop, PropId, PropKind, + TransactionsError, }; use super::prototype::argument::static_value::StaticArgumentValue; @@ -81,6 +84,7 @@ use super::prototype::argument::{ AttributePrototypeArgumentId, }; +pub mod debug; pub mod dependent_value_graph; pub mod view; @@ -161,6 +165,10 @@ pub enum AttributeValueError { NotFoundForComponentAndInputSocket(ComponentId, InputSocketId), #[error("attribute value {0} has no outgoing edge to a prop or socket")] OrphanedAttributeValue(AttributeValueId), + #[error("output socket error: {0}")] + OutputSocketError(#[from] OutputSocketError), + #[error("parent prop of map or array not found: {0}")] + ParentAttributeValueMissing(AttributeValueId), #[error("prop error: {0}")] Prop(#[from] PropError), #[error("array or map prop missing element prop: {0}")] @@ -2129,4 +2137,73 @@ impl AttributeValue { let prototype_id = Self::prototype_id(ctx, av_id).await?; Ok(AttributePrototype::list_input_socket_sources_for_id(ctx, prototype_id).await?) } + + /// Get the morale equivalent of the [`PropPath`]for a given [`AttributeValueId`]. + /// This includes the key/index in the path, unlike the [`PropPath`] which doesn't + /// include the key/index + #[instrument(level = "info", skip_all)] + pub async fn get_path_for_id( + ctx: &DalContext, + attribute_value_id: AttributeValueId, + ) -> AttributeValueResult> { + let mut parts = VecDeque::new(); + let mut work_queue = VecDeque::from([attribute_value_id]); + + while let Some(mut attribute_value_id) = work_queue.pop_front() { + let attribute_path = match Self::is_for(ctx, attribute_value_id).await? { + ValueIsFor::Prop(prop_id) => { + let prop_name = Prop::get_by_id(ctx, prop_id).await?.name; + let attribute_path = AttributeValuePath::Prop { + path: prop_name, + key_or_index: None, + }; + // check the parent of this attribute value + // if the parent is an array or map, we need to add the key/index to the attribute value path + if let Some(parent_attribute_value_id) = + Self::parent_attribute_value_id(ctx, attribute_value_id).await? + { + let key_or_index = + Self::get_index_or_key_of_child_entry(ctx, attribute_value_id).await?; + attribute_path.set_index_or_key(key_or_index); + attribute_value_id = parent_attribute_value_id; + work_queue.push_back(attribute_value_id); + } + attribute_path + } + ValueIsFor::InputSocket(input_socket_id) => { + let input_socket_name = InputSocket::get_by_id(ctx, input_socket_id) + .await? + .name() + .to_string(); + AttributeValuePath::InputSocket(input_socket_name) + } + ValueIsFor::OutputSocket(output_socket_id) => { + let output_socket_name = OutputSocket::get_by_id(ctx, output_socket_id) + .await? + .name() + .to_string(); + AttributeValuePath::OutputSocket(output_socket_name) + } + }; + parts.push_front(attribute_path); + } + if !parts.is_empty() { + Ok(Some( + AttributeValuePath::assemble_from_parts_with_separator(parts, Some("/")), + )) + } else { + Ok(None) + } + } + + pub async fn get_index_or_key_of_child_entry( + ctx: &DalContext, + id: AttributeValueId, + ) -> AttributeValueResult> { + let maybe_key = ctx + .workspace_snapshot()? + .index_or_key_of_child_entry(id) + .await?; + Ok(maybe_key) + } } diff --git a/lib/dal/src/attribute/value/debug.rs b/lib/dal/src/attribute/value/debug.rs new file mode 100644 index 0000000000..9941d38617 --- /dev/null +++ b/lib/dal/src/attribute/value/debug.rs @@ -0,0 +1,126 @@ +use super::ValueIsFor; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use telemetry::prelude::*; +use thiserror::Error; + +use crate::attribute::prototype::debug::{ + AttributePrototypeDebugView, AttributePrototypeDebugViewError, +}; +use crate::attribute::prototype::{ + argument::{value_source::ValueSourceError, AttributePrototypeArgumentError}, + AttributePrototypeError, +}; +use crate::attribute::value::AttributeValueError; +use crate::func::execution::{FuncExecution, FuncExecutionError}; +use crate::prop::PropError; +use crate::socket::input::InputSocketError; +use crate::socket::output::OutputSocketError; +use crate::workspace_snapshot::node_weight::NodeWeightError; +use crate::workspace_snapshot::WorkspaceSnapshotError; +use crate::ComponentError; +use crate::{ + AttributePrototypeId, AttributeValue, AttributeValueId, DalContext, FuncError, FuncId, Prop, + PropKind, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AttributeDebugView { + pub path: String, + pub parent_id: Option, + pub attribute_value_id: AttributeValueId, + pub func_id: FuncId, + pub func_execution: Option, + pub value_is_for: ValueIsFor, + pub prop: Option, + pub prototype_id: Option, + pub key: Option, + pub func_name: String, + pub func_args: HashMap>, + pub arg_sources: HashMap>, + pub value: Option, + pub prop_kind: Option, + pub materialized_view: Option, +} + +type AttributeDebugViewResult = Result; + +#[remain::sorted] +#[derive(Error, Debug)] +pub enum AttributeDebugViewError { + #[error("attribute prototype argument error: {0}")] + AttributePrototypeArgumentError(#[from] AttributePrototypeArgumentError), + #[error("attribute debug view error: {0}")] + AttributePrototypeDebugViewError(#[from] AttributePrototypeDebugViewError), + #[error("attribute prototype error: {0}")] + AttributePrototypeError(#[from] AttributePrototypeError), + #[error("attribute value error: {0}")] + AttributeValue(#[from] AttributeValueError), + #[error("component error: {0}")] + ComponentError(#[from] ComponentError), + #[error("func error: {0}")] + Func(#[from] FuncError), + #[error("func execution error: {0}")] + FuncExecution(#[from] FuncExecutionError), + #[error("input socket error: {0}")] + InputSocketError(#[from] InputSocketError), + #[error("node weight error: {0}")] + NodeWeightError(#[from] NodeWeightError), + #[error("output socket error: {0}")] + OutputSocketError(#[from] OutputSocketError), + #[error("prop error: {0}")] + PropError(#[from] PropError), + #[error("value source error: {0}")] + ValueSourceError(#[from] ValueSourceError), + #[error("workspace snapshot error: {0}")] + WorkspaceSnapshotError(#[from] WorkspaceSnapshotError), +} +impl AttributeDebugView { + #[instrument(level = "info", skip_all)] + pub async fn new( + ctx: &DalContext, + attribute_value_id: AttributeValueId, + key: Option, + parent_id: Option, + ) -> AttributeDebugViewResult { + let attribute_value = AttributeValue::get_by_id(ctx, attribute_value_id).await?; + let prototype_id = AttributeValue::prototype_id(ctx, attribute_value_id).await?; + let value_is_for = AttributeValue::is_for(ctx, attribute_value_id).await?; + + let prop_id = AttributeValue::prop_id_for_id(ctx, attribute_value_id).await?; + + let prop = Prop::get_by_id(ctx, prop_id).await?; + let path = match AttributeValue::get_path_for_id(ctx, attribute_value_id).await? { + Some(path) => path, + None => String::new(), + }; + let prop_opt: Option = Some(prop); + let attribute_prototype_debug_view = + AttributePrototypeDebugView::assemble(ctx, attribute_value_id).await?; + let materialized_view = attribute_value.materialized_view(ctx).await?; + let prop_kind = prop_opt.clone().map(|prop| prop.kind); + let value = match attribute_value.unprocessed_value(ctx).await? { + Some(value) => Some(value), + None => attribute_value.value(ctx).await?, + }; + let view = AttributeDebugView { + path, + parent_id, + attribute_value_id, + func_id: attribute_prototype_debug_view.func_id, + key, + func_execution: attribute_prototype_debug_view.func_execution, + prop: prop_opt, + prototype_id: Some(prototype_id), + value_is_for, + func_name: attribute_prototype_debug_view.func_name, + func_args: attribute_prototype_debug_view.func_args, + arg_sources: attribute_prototype_debug_view.arg_sources, + value, + prop_kind, + materialized_view, + }; + Ok(view) + } +} diff --git a/lib/dal/src/component.rs b/lib/dal/src/component.rs index 5cdfd34a6e..c0a46626d4 100644 --- a/lib/dal/src/component.rs +++ b/lib/dal/src/component.rs @@ -46,6 +46,7 @@ use crate::{ }; pub mod code; +pub mod debug; pub mod diff; pub mod frame; pub mod properties; diff --git a/lib/dal/src/component/debug.rs b/lib/dal/src/component/debug.rs new file mode 100644 index 0000000000..5a97f10281 --- /dev/null +++ b/lib/dal/src/component/debug.rs @@ -0,0 +1,288 @@ +use petgraph::graph::NodeIndex; +use petgraph::Direction; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; +use telemetry::prelude::*; +use thiserror::Error; + +use crate::attribute::value::AttributeValueError; + +use crate::attribute::value::debug::{AttributeDebugView, AttributeDebugViewError}; +use crate::prop::PropError; +use crate::socket::debug::{SocketDebugView, SocketDebugViewError}; +use crate::socket::input::InputSocketError; +use crate::socket::output::OutputSocketError; +use crate::workspace_snapshot::edge_weight::EdgeWeightKind; +use crate::workspace_snapshot::node_weight::NodeWeightError; +use crate::workspace_snapshot::WorkspaceSnapshotError; +use crate::{ + func::execution::FuncExecutionError, AttributeValue, AttributeValueId, Component, ComponentId, + DalContext, PropId, SchemaVariantId, SecretError, SecretId, +}; +use crate::{ + ComponentError, FuncError, InputSocket, InputSocketId, OutputSocket, OutputSocketId, + SchemaVariantError, +}; + +type ComponentDebugViewResult = Result; + +/// A generated view for an [`Component`](crate::Component) that contains metadata about each of +/// the components attributes. Used for constructing a debug view of the component and also for +/// cloning a component +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComponentDebugView { + pub name: String, + pub schema_variant_id: SchemaVariantId, + pub attributes: Vec, + pub input_sockets: Vec, + pub output_sockets: Vec, +} +/// A generated view for an [`Component`](crate::Component) that contains metadata about each of +/// the components attributes. Used for constructing a debug view of the component and also for +/// cloning a component +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComponentDebugData { + pub name: String, + pub schema_variant_id: SchemaVariantId, + pub attribute_tree: HashMap>, + pub input_sockets: HashMap>, + pub output_sockets: HashMap>, +} + +#[remain::sorted] +#[derive(Error, Debug)] +pub enum ComponentDebugViewError { + #[error("attribute debug view error: {0}")] + AttributeDebugViewError(#[from] AttributeDebugViewError), + #[error("attribute value error: {0}")] + AttributeValue(#[from] AttributeValueError), + #[error("Attribute Value tree badly constructed with root prop of {0}")] + AttributeValueTreeBad(AttributeValueId), + #[error("component error: {0}")] + Component(String), + #[error("component error: {0}")] + ComponentError(#[from] ComponentError), + #[error("func error: {0}")] + Func(#[from] FuncError), + #[error("func execution error: {0}")] + FuncExecution(#[from] FuncExecutionError), + #[error("input socket error: {0}")] + InputSocketError(#[from] InputSocketError), + #[error("json pointer not found: {1:?} at {0}")] + JSONPointerNotFound(serde_json::Value, String), + #[error("node weight error: {0}")] + NodeWeightError(#[from] NodeWeightError), + #[error("no internal provider for prop {0}")] + NoInternalProvider(PropId), + #[error("no root prop found for schema variant {0}")] + NoRootProp(SchemaVariantId), + #[error("schema variant not found for component {0}")] + NoSchemaVariant(ComponentId), + #[error("component not found {0}")] + NotFound(ComponentId), + #[error("output socket error: {0}")] + OutputSocketError(#[from] OutputSocketError), + #[error("prop error: {0}")] + Prop(#[from] PropError), + #[error("schema variant error: {0}")] + SchemaVariant(#[from] SchemaVariantError), + #[error("secret error: {0}")] + Secret(#[from] SecretError), + #[error("secret not found: {0}")] + SecretNotFound(SecretId), + #[error("serde json error: {0}")] + SerdeJson(#[from] serde_json::Error), + #[error("socket debug view error: {0}")] + SocketDebugViewError(#[from] SocketDebugViewError), + #[error("workspace snapshot error: {0}")] + WorkspaceSnapshotError(#[from] WorkspaceSnapshotError), +} + +impl ComponentDebugView { + #[instrument(level = "info", skip_all)] + pub async fn new( + ctx: &DalContext, + component_id: ComponentId, + ) -> ComponentDebugViewResult { + // get ComponentDebugData + let component = Component::get_by_id(ctx, component_id).await?; + + let component_debug_data = ComponentDebugData::new(ctx, &component).await?; + let mut attributes = vec![]; + let mut input_sockets = vec![]; + let mut output_sockets = vec![]; + + let mut cache: Vec = vec![]; + //construct attribute value debug views from the debug data + for (av, children) in component_debug_data.attribute_tree { + if !cache.contains(&av) { + let avd = AttributeDebugView::new(ctx, av, None, None).await?; + attributes.push(avd); + cache.push(av); + } + + for child_av in children { + if !cache.contains(&child_av) { + let child_avd = AttributeDebugView::new(ctx, child_av, None, Some(av)).await?; + attributes.push(child_avd); + cache.push(child_av); + } + } + } + //sort alphabetically by path for the view + attributes.sort_by_key(|view| view.path.to_lowercase()); + + for (input_socket, _) in component_debug_data.input_sockets { + let avd = SocketDebugView::new_for_input_socket(ctx, input_socket).await?; + input_sockets.push(avd); + } + for (output_socket, _) in component_debug_data.output_sockets { + let avd = SocketDebugView::new_for_output_socket(ctx, output_socket).await?; + output_sockets.push(avd); + } + + let debug_view = ComponentDebugView { + name: component_debug_data.name, + schema_variant_id: component_debug_data.schema_variant_id, + attributes, + input_sockets, + output_sockets, + }; + + Ok(debug_view) + } +} + +impl ComponentDebugData { + #[instrument(level = "info", skip_all)] + pub async fn new(ctx: &DalContext, component: &Component) -> ComponentDebugViewResult { + let schema_variant_id = Component::schema_variant_id(ctx, component.id()).await?; + + let attribute_tree = + Self::get_attribute_value_tree_for_component(ctx, component.id()).await?; + let input_sockets = Self::get_input_sockets_for_component(ctx, schema_variant_id).await?; + let output_sockets = Self::get_output_sockets_for_component(ctx, schema_variant_id).await?; + let name = component + .name(ctx) + .await + .map_err(|e| ComponentDebugViewError::Component(format!("get name error: {}", e)))?; + + let debug_view = ComponentDebugData { + name, + schema_variant_id, + attribute_tree, + input_sockets, + output_sockets, + }; + + Ok(debug_view) + } + + #[instrument(level = "info", skip_all)] + pub async fn get_input_sockets_for_component( + ctx: &DalContext, + schema_variant_id: SchemaVariantId, + ) -> ComponentDebugViewResult>> { + let mut input_sockets = HashMap::new(); + let sockets = InputSocket::list_ids_for_schema_variant(ctx, schema_variant_id).await?; + for socket in sockets { + let attribute_values_for_socket = + InputSocket::attribute_values_for_input_socket_id(ctx, socket).await?; + input_sockets.insert(socket, attribute_values_for_socket); + } + Ok(input_sockets) + } + + #[instrument(level = "info", skip_all)] + pub async fn get_output_sockets_for_component( + ctx: &DalContext, + schema_variant_id: SchemaVariantId, + ) -> ComponentDebugViewResult>> { + let mut output_sockets = HashMap::new(); + let sockets = OutputSocket::list_ids_for_schema_variant(ctx, schema_variant_id).await?; + for socket in sockets { + let attribute_values_for_socket = + OutputSocket::attribute_values_for_output_socket_id(ctx, socket).await?; + output_sockets.insert(socket, attribute_values_for_socket); + } + Ok(output_sockets) + } + + #[instrument(level = "info", skip_all)] + pub async fn get_attribute_value_tree_for_component( + ctx: &DalContext, + component_id: ComponentId, + ) -> ComponentDebugViewResult>> { + let mut child_values = HashMap::new(); + // Get the root attribute value and load it into the work queue. + let root_attribute_value_id = Component::root_attribute_value_id(ctx, component_id).await?; + let workspace_snapshot = ctx.workspace_snapshot()?; + + let mut work_queue = VecDeque::from([root_attribute_value_id]); + while let Some(attribute_value_id) = work_queue.pop_front() { + // Collect all child attribute values. + let mut cache: Vec<(AttributeValueId, Option)> = Vec::new(); + { + let mut child_attribute_values_with_keys_by_id: HashMap< + AttributeValueId, + (NodeIndex, Option), + > = HashMap::new(); + + for (edge_weight, _, target_idx) in workspace_snapshot + .edges_directed(attribute_value_id, Direction::Outgoing) + .await? + { + if let EdgeWeightKind::Contain(key) = edge_weight.kind() { + let child_id = workspace_snapshot + .get_node_weight(target_idx) + .await? + .id() + .into(); + child_attribute_values_with_keys_by_id + .insert(child_id, (target_idx, key.to_owned())); + } + } + + let maybe_ordering = + AttributeValue::get_child_av_ids_for_ordered_parent(ctx, attribute_value_id) + .await + .ok(); + // Ideally every attribute value with children is connected via an ordering node + // We don't error out on ordering not existing here because we don't have that + // guarantee. If that becomes a certainty we should fail on maybe_ordering==None. + for av_id in maybe_ordering.unwrap_or_else(|| { + child_attribute_values_with_keys_by_id + .keys() + .cloned() + .collect() + }) { + let (child_attribute_value_node_index, key) = + &child_attribute_values_with_keys_by_id[&av_id]; + let child_attribute_value_node_weight = workspace_snapshot + .get_node_weight(*child_attribute_value_node_index) + .await?; + let content = + child_attribute_value_node_weight.get_attribute_value_node_weight()?; + cache.push((content.id().into(), key.clone())); + } + } + + // Now that we have the child props, prepare debug views and load the work queue. + let mut child_attribute_value_ids = Vec::new(); + for (child_attribute_value_id, _key) in cache { + let child_attribute_value = + AttributeValue::get_by_id(ctx, child_attribute_value_id).await?; + + // Load the work queue with the child attribute value. + work_queue.push_back(child_attribute_value_id); + + // Cache the prop values to eventually insert into the child property editor values map. + child_attribute_value_ids.push(child_attribute_value.id()); + } + child_values.insert(attribute_value_id, child_attribute_value_ids); + } + Ok(child_values) + } +} diff --git a/lib/dal/src/component/view/debug.rs b/lib/dal/src/component/view/debug.rs deleted file mode 100644 index eee929c550..0000000000 --- a/lib/dal/src/component/view/debug.rs +++ /dev/null @@ -1,505 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, VecDeque}; -use std::time::Instant; -use thiserror::Error; - -use crate::{ - func::execution::{FuncExecution, FuncExecutionError}, - socket::SocketEdgeKind, - AttributePrototype, AttributeValue, AttributeValueId, AttributeValuePayload, Component, - ComponentId, DalContext, ExternalProvider, ExternalProviderId, Func, FuncBinding, - FuncBindingError, FuncBindingReturnValue, FuncBindingReturnValueError, InternalProvider, - InternalProviderId, Prop, PropId, PropKind, SchemaVariantId, SecretError, SecretId, Socket, - SocketId, StandardModel, StandardModelError, -}; - -type ComponentDebugViewResult = Result; - -/// A generated view for an [`Component`](crate::Component) that contains metadata about each of -/// the components attributes. Used for constructing a debug view of the component and also for -/// cloning a component -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ComponentDebugView { - pub name: String, - pub schema_variant_id: SchemaVariantId, - pub attributes: Vec, - pub input_sockets: Vec, - pub output_sockets: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AttributeDebugView { - pub path: String, - pub parent_info: Option, - pub attribute_value: AttributeValue, - pub func: Func, - pub func_binding: FuncBinding, - pub func_binding_return_value: FuncBindingReturnValue, - pub func_execution: FuncExecution, - pub prop: Option, - pub internal_provider: Option, - pub external_provider: Option, - pub prototype: AttributePrototype, - pub array_index: Option, - pub implicit_attribute_value: Option, -} - -#[remain::sorted] -#[derive(Error, Debug)] -pub enum ComponentDebugViewError { - #[error(transparent)] - AttributeValue(#[from] AttributeValueError), - #[error("Attribute Value tree badly constructed with root prop of {0}")] - AttributeValueTreeBad(AttributeValueId), - #[error("component error: {0}")] - Component(String), - #[error("external provider not found for output socket: {0}")] - ExternalProviderNotFoundForInputSocket(SocketId), - #[error(transparent)] - Func(#[from] FuncError), - #[error(transparent)] - FuncBinding(#[from] FuncBindingError), - #[error(transparent)] - FuncBindingReturnValue(#[from] FuncBindingReturnValueError), - #[error(transparent)] - FuncExecution(#[from] FuncExecutionError), - #[error(transparent)] - InternalProvider(#[from] InternalProviderError), - #[error("internal provider not found for input socket: {0}")] - InternalProviderNotFoundForInputSocket(SocketId), - #[error("json pointer not found: {1:?} at {0}")] - JSONPointerNotFound(serde_json::Value, String), - #[error("no attribute value found for context {0:?}")] - NoAttributeValue(AttributeReadContext), - #[error("no internal provider for prop {0}")] - NoInternalProvider(PropId), - #[error("no root prop found for schema variant {0}")] - NoRootProp(SchemaVariantId), - #[error("schema variant not found for component {0}")] - NoSchemaVariant(ComponentId), - #[error("component not found {0}")] - NotFound(ComponentId), - #[error(transparent)] - Prop(#[from] PropError), - #[error(transparent)] - SchemaVariant(#[from] SchemaVariantError), - #[error(transparent)] - Secret(#[from] SecretError), - #[error("secret not found: {0}")] - SecretNotFound(SecretId), - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), - #[error(transparent)] - Socket(#[from] SocketError), - #[error(transparent)] - StandardModel(#[from] StandardModelError), - #[error(transparent)] - UlidDecode(#[from] ulid::DecodeError), -} - -pub enum AttributeDebugInput<'a> { - ComponentSocket((Socket, ComponentId)), - AttributeValuePayload { - payload: &'a AttributeValuePayload, - implicit_attribute_value: Option, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ParentInfo { - pub value: AttributeValue, - pub kind: PropKind, - pub path: String, - pub key: Option, - pub array_index: Option, -} - -impl ComponentDebugView { - pub async fn new(ctx: &DalContext, component: &Component) -> ComponentDebugViewResult { - let debug_view_start = Instant::now(); - - let schema_variant = component - .schema_variant(ctx) - .await - .map_err(|e| ComponentDebugViewError::Component(e.to_string()))? - .ok_or(ComponentError::NoSchemaVariant(*component.id())) - .map_err(|e| ComponentDebugViewError::Component(e.to_string()))?; - - let root_prop_id = schema_variant - .root_prop_id() - .ok_or(ComponentDebugViewError::NoRootProp(*schema_variant.id()))?; - - let root_av_context = AttributeReadContext { - prop_id: Some(*root_prop_id), - internal_provider_id: Some(InternalProviderId::NONE), - external_provider_id: Some(ExternalProviderId::NONE), - component_id: Some(*component.id()), - }; - - let root_prop_av = AttributeValue::find_for_context(ctx, root_av_context) - .await? - .ok_or(AttributeValueError::NotFoundForReadContext(root_av_context))?; - - let view_context = AttributeReadContext { - prop_id: None, - internal_provider_id: Some(InternalProviderId::NONE), - external_provider_id: Some(ExternalProviderId::NONE), - component_id: Some(*component.id()), - }; - - let mut initial_work = AttributeValue::list_payload_for_read_context_and_root( - ctx, - *root_prop_av.id(), - view_context, - ) - .await?; - - // We sort the work queue according to the order of every nested IndexMap. This ensures that - // when we reconstruct the final shape, we don't have to worry about the order that things - // appear in. - let attribute_value_order: Vec = initial_work - .iter() - .filter_map(|avp| avp.attribute_value.index_map()) - .flat_map(|index_map| index_map.order()) - .copied() - .collect(); - initial_work.sort_by_cached_key(|avp| { - attribute_value_order - .iter() - .position(|attribute_value_id| attribute_value_id == avp.attribute_value.id()) - .unwrap_or(0) - }); - - let mut index_map: HashMap = HashMap::new(); - let mut work_queue = VecDeque::from(initial_work); - let mut parent_queue: VecDeque> = VecDeque::from([None]); - let mut attributes = vec![]; - - while !work_queue.is_empty() { - let mut unprocessed = vec![]; - - if let Some(parent_info) = parent_queue.pop_front() { - let mut current_parent = parent_info; - - while let Some(payload) = work_queue.pop_front() { - if current_parent.as_ref().map(|parent| *parent.value.id()) - == payload.parent_attribute_value_id - { - let current_prop_name = payload.prop.name(); - let current_parent_path = current_parent - .as_ref() - .map(|p| p.path.as_str()) - .unwrap_or(""); - - let prop_full_path = match current_parent.as_ref().map(|p| p.kind) { - Some(PropKind::Array) => { - let array_index = *index_map.get(payload.prop.id()).unwrap_or(&0); - let path = format!( - "{}/{}/{}", - current_parent_path, current_prop_name, array_index - ); - index_map.insert(*payload.prop.id(), array_index + 1); - path - } - Some(PropKind::Map) => { - if let Some(key) = payload.attribute_value.key() { - format!("{}/{}/{}", current_parent_path, current_prop_name, key) - } else { - // This should be an error - format!("{}/{}", current_parent_path, current_prop_name) - } - } - _ => format!("{}/{}", current_parent_path, current_prop_name), - }; - - let current_index = index_map.get(payload.prop.id()).map(|index| index - 1); - - let implicit_attribute_value = if let Some(internal_provider) = - InternalProvider::find_for_prop(ctx, *payload.prop.id()).await? - { - let implicit_attribute_value_context = AttributeReadContext { - internal_provider_id: Some(*internal_provider.id()), - component_id: Some(*component.id()), - ..Default::default() - }; - let implicit_attribute_value = AttributeValue::find_for_context( - ctx, - implicit_attribute_value_context, - ) - .await?; - - if let Some(implicit_attribute_value) = implicit_attribute_value { - if implicit_attribute_value.context.component_id().is_none() { - None - } else { - Some(implicit_attribute_value) - } - } else { - None - } - } else { - None - }; - - attributes.push( - Self::get_attribute_debug_view( - ctx, - AttributeDebugInput::AttributeValuePayload { - payload: &payload, - implicit_attribute_value, - }, - current_parent.to_owned(), - Some(prop_full_path.to_owned()), - current_index, - ) - .await?, - ); - - match payload.prop.kind() { - PropKind::Object | PropKind::Array | PropKind::Map => { - // The current parent is pushed back onto the queue and swapped - // with this value, which transforms this into a depth-first search - // (but preserves index-map ordering above) - parent_queue.push_front(current_parent); - // too much cloning! - // - let (key, array_index) = match ¤t_index { - Some(index) => (None, Some(*index)), - None => (payload.attribute_value.key.to_owned(), None), - }; - - current_parent = Some(ParentInfo { - value: payload.attribute_value.to_owned(), - kind: *payload.prop.kind(), - path: prop_full_path, - key, - array_index, - }); - - // Since we've changed parents we need to reprocess the unprocessed - // in case they are children of this parent but were skipped - for unprocessed_payload in unprocessed { - work_queue.push_front(unprocessed_payload); - } - unprocessed = vec![]; - - continue; - } - _ => {} - } - } else { - unprocessed.push(payload); - } - } - work_queue = VecDeque::from(unprocessed); - - // If we are out of roots but we have work left to process, something went wrong - // with the attribute value structure in the database (we had an orphaned child - // with no parent) - if parent_queue.is_empty() && !work_queue.is_empty() { - return Err(ComponentDebugViewError::AttributeValueTreeBad( - *root_prop_av.id(), - )); - } - } - } - - let attributes_duration = debug_view_start.elapsed(); - let sockets_start = Instant::now(); - - let mut input_sockets = vec![]; - let mut output_sockets = vec![]; - - for socket in Socket::list_for_component(ctx, *component.id()).await? { - let socket_debug_data = Self::get_attribute_debug_view( - ctx, - AttributeDebugInput::ComponentSocket((socket, *component.id())), - None, - None, - None, - ) - .await?; - - if socket_debug_data.internal_provider.is_some() { - input_sockets.push(socket_debug_data); - } else { - output_sockets.push(socket_debug_data) - } - } - - let sockets_duration = sockets_start.elapsed(); - - dbg!(attributes_duration, sockets_duration); - - let name = component - .name(ctx) - .await - .map_err(|e| ComponentDebugViewError::Component(format!("get name error: {}", e)))?; - - let debug_view = ComponentDebugView { - name, - schema_variant_id: *schema_variant.id(), - attributes, - input_sockets, - output_sockets, - }; - - Ok(debug_view) - } - - pub async fn get_attribute_debug_view( - ctx: &DalContext, - payload: AttributeDebugInput<'_>, - parent_info: Option, - path: Option, - array_index: Option, - ) -> ComponentDebugViewResult { - let ( - attribute_value, - prop, - internal_provider, - external_provider, - func_binding_return_value, - path, - implicit_attribute_value, - ) = match payload { - AttributeDebugInput::AttributeValuePayload { - payload, - implicit_attribute_value, - } => { - let func_binding_return_value = match &payload.func_binding_return_value { - Some(fbrv) => fbrv.to_owned(), - None => FuncBindingReturnValue::get_by_id( - ctx, - &payload.attribute_value.func_binding_return_value_id(), - ) - .await? - .ok_or(FuncBindingReturnValueError::NotFound( - payload.attribute_value.func_binding_return_value_id(), - ))?, - }; - - let path = path.unwrap_or(payload.prop.name().into()); - - ( - payload.attribute_value.to_owned(), - Some(payload.prop.to_owned()), - None, - None, - func_binding_return_value, - path, - implicit_attribute_value, - ) - } - AttributeDebugInput::ComponentSocket((socket, component_id)) => { - let (attribute_value, internal_provider, external_provider, path) = - match socket.edge_kind() { - SocketEdgeKind::ConfigurationInput => { - let internal_provider = socket.internal_provider(ctx).await?.ok_or( - ComponentDebugViewError::InternalProviderNotFoundForInputSocket( - *socket.id(), - ), - )?; - - let input_socket_value_context = AttributeReadContext { - prop_id: Some(PropId::NONE), - internal_provider_id: Some(*internal_provider.id()), - external_provider_id: Some(ExternalProviderId::NONE), - component_id: Some(component_id), - }; - let attribute_value = - AttributeValue::find_for_context(ctx, input_socket_value_context) - .await? - .ok_or(AttributeValueError::NotFoundForReadContext( - input_socket_value_context, - ))?; - - let name = internal_provider.name().to_owned(); - (attribute_value, Some(internal_provider), None, name) - } - SocketEdgeKind::ConfigurationOutput => { - let external_provider = socket.external_provider(ctx).await?.ok_or( - ComponentDebugViewError::ExternalProviderNotFoundForInputSocket( - *socket.id(), - ), - )?; - - let input_socket_value_context = AttributeReadContext { - prop_id: Some(PropId::NONE), - internal_provider_id: Some(InternalProviderId::NONE), - external_provider_id: Some(*external_provider.id()), - component_id: Some(component_id), - }; - let attribute_value = - AttributeValue::find_for_context(ctx, input_socket_value_context) - .await? - .ok_or(AttributeValueError::NotFoundForReadContext( - input_socket_value_context, - ))?; - - let name = external_provider.name().to_owned(); - (attribute_value, None, Some(external_provider), name) - } - }; - - let func_binding_return_value = FuncBindingReturnValue::get_by_id( - ctx, - &attribute_value.func_binding_return_value_id(), - ) - .await? - .ok_or(FuncBindingReturnValueError::NotFound( - attribute_value.func_binding_return_value_id(), - ))?; - - ( - attribute_value, - None, - internal_provider, - external_provider, - func_binding_return_value, - path, - None, - ) - } - }; - - let prototype = attribute_value.attribute_prototype(ctx).await?.ok_or( - AttributeValueError::AttributePrototypeNotFound( - *attribute_value.id(), - *ctx.visibility(), - ), - )?; - - let func = Func::get_by_id(ctx, &prototype.func_id()) - .await? - .ok_or(FuncError::NotFound(prototype.func_id()))?; - - let func_binding = FuncBinding::get_by_id(ctx, &attribute_value.func_binding_id()) - .await? - .ok_or(FuncBindingError::NotFound( - attribute_value.func_binding_id(), - ))?; - - let func_execution = - FuncExecution::get_by_pk(ctx, &func_binding_return_value.func_execution_pk()).await?; - - Ok(AttributeDebugView { - path, - parent_info, - attribute_value, - func, - func_binding, - func_binding_return_value, - func_execution, - prop, - internal_provider, - external_provider, - prototype, - array_index, - implicit_attribute_value, - }) - } -} diff --git a/lib/dal/src/func/argument.rs b/lib/dal/src/func/argument.rs index a589262c0c..b58fe04789 100644 --- a/lib/dal/src/func/argument.rs +++ b/lib/dal/src/func/argument.rs @@ -225,6 +225,19 @@ impl FuncArgument { Ok(FuncArgument::assemble(&arg_node_weight, &inner)) } + pub async fn get_name_by_id( + ctx: &DalContext, + func_arg_id: FuncArgumentId, + ) -> FuncArgumentResult { + let node_weight = ctx + .workspace_snapshot()? + .get_node_weight_by_id(func_arg_id) + .await?; + let func_arg_node_weight = node_weight.get_func_argument_node_weight()?; + let name = func_arg_node_weight.name().to_string(); + Ok(name) + } + pub async fn list_ids_for_func( ctx: &DalContext, func_id: FuncId, diff --git a/lib/dal/src/socket.rs b/lib/dal/src/socket.rs index 242ff4cd5a..8b58972584 100644 --- a/lib/dal/src/socket.rs +++ b/lib/dal/src/socket.rs @@ -5,6 +5,7 @@ use si_pkg::SocketSpecArity; use strum::{AsRefStr, Display, EnumIter, EnumString}; pub mod connection_annotation; +pub mod debug; pub mod input; pub mod output; diff --git a/lib/dal/src/socket/debug.rs b/lib/dal/src/socket/debug.rs new file mode 100644 index 0000000000..d750c1a643 --- /dev/null +++ b/lib/dal/src/socket/debug.rs @@ -0,0 +1,144 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use telemetry::prelude::*; +use thiserror::Error; +use ulid::Ulid; + +use super::{input::InputSocketError, output::OutputSocketError}; +use crate::{ + attribute::{ + prototype::{ + debug::{AttributePrototypeDebugView, AttributePrototypeDebugViewError}, + AttributePrototypeError, + }, + value::AttributeValueError, + }, + func::execution::FuncExecution, + AttributePrototype, AttributePrototypeId, AttributeValue, AttributeValueId, DalContext, FuncId, + InputSocket, InputSocketId, OutputSocket, OutputSocketId, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SocketDebugView { + pub path: String, + pub socket_id: Ulid, + pub attribute_value_id: AttributeValueId, + pub func_id: FuncId, + pub func_execution: Option, + pub prototype_id: Option, + pub connection_annotations: Vec, + pub func_name: String, + pub func_args: HashMap>, + pub arg_sources: HashMap>, + pub value: Option, + pub materialized_view: Option, + pub name: String, +} +type SocketDebugViewResult = Result; + +#[remain::sorted] +#[derive(Error, Debug)] +pub enum SocketDebugViewError { + #[error("attribute prototype debug view error: {0}")] + AttributePrototypeDebugViewError(#[from] AttributePrototypeDebugViewError), + #[error("attribute prototype error: {0}")] + AttributePrototypeError(#[from] AttributePrototypeError), + #[error("attribute value error: {0}")] + AttributeValue(#[from] AttributeValueError), + #[error("input socket error: {0}")] + InputSocketError(#[from] InputSocketError), + #[error("output socket error: {0}")] + OutputSocketError(#[from] OutputSocketError), +} + +impl SocketDebugView { + #[instrument(level = "info", skip_all)] + pub async fn new_for_output_socket( + ctx: &DalContext, + output_socket_id: OutputSocketId, + ) -> SocketDebugViewResult { + let prototype_id = + AttributePrototype::find_for_output_socket(ctx, output_socket_id).await?; + + let attribute_value_id = + OutputSocket::attribute_values_for_output_socket_id(ctx, output_socket_id) + .await? + .pop() + .expect("should have attribute value id"); + + let prototype_debug_view = + AttributePrototypeDebugView::assemble(ctx, attribute_value_id).await?; + let attribute_value = AttributeValue::get_by_id(ctx, attribute_value_id).await?; + let output_socket = OutputSocket::get_by_id(ctx, output_socket_id).await?; + let connection_annotations = output_socket + .connection_annotations() + .into_iter() + .map(|f| f.to_string()) + .collect(); + let path = match AttributeValue::get_path_for_id(ctx, attribute_value_id).await? { + Some(path) => path, + None => String::new(), + }; + + let materialized_view = attribute_value.materialized_view(ctx).await?; + + Ok(SocketDebugView { + prototype_id, + func_name: prototype_debug_view.func_name, + func_args: prototype_debug_view.func_args, + arg_sources: prototype_debug_view.arg_sources, + attribute_value_id, + socket_id: output_socket_id.into(), + func_id: prototype_debug_view.func_id, + func_execution: prototype_debug_view.func_execution, + connection_annotations, + value: attribute_value.unprocessed_value(ctx).await?, + path, + materialized_view, + name: output_socket.name().to_string(), + }) + } + #[instrument(level = "info", skip_all)] + pub async fn new_for_input_socket( + ctx: &DalContext, + input_socket_id: InputSocketId, + ) -> SocketDebugViewResult { + let prototype_id = AttributePrototype::find_for_input_socket(ctx, input_socket_id).await?; + let attribute_value_id = + InputSocket::attribute_values_for_input_socket_id(ctx, input_socket_id) + .await? + .pop() + .expect("should have attribute value id"); + let prototype_debug_view = + AttributePrototypeDebugView::assemble(ctx, attribute_value_id).await?; + let attribute_value = AttributeValue::get_by_id(ctx, attribute_value_id).await?; + let input_socket = InputSocket::get_by_id(ctx, input_socket_id).await?; + let connection_annotations = input_socket + .connection_annotations() + .into_iter() + .map(|f| f.to_string()) + .collect(); + let path = match AttributeValue::get_path_for_id(ctx, attribute_value_id).await? { + Some(path) => path, + None => String::new(), + }; + let materialized_view = attribute_value.materialized_view(ctx).await?; + + Ok(SocketDebugView { + prototype_id, + func_name: prototype_debug_view.func_name, + func_args: prototype_debug_view.func_args, + arg_sources: prototype_debug_view.arg_sources, + attribute_value_id, + socket_id: input_socket_id.into(), + func_id: prototype_debug_view.func_id, + func_execution: prototype_debug_view.func_execution, + connection_annotations, + value: attribute_value.unprocessed_value(ctx).await?, + path, + materialized_view, + name: input_socket.name().to_string(), + }) + } +} diff --git a/lib/dal/src/workspace_snapshot.rs b/lib/dal/src/workspace_snapshot.rs index b035f0c2b4..61ae1395aa 100644 --- a/lib/dal/src/workspace_snapshot.rs +++ b/lib/dal/src/workspace_snapshot.rs @@ -31,6 +31,7 @@ pub mod update; pub mod vector_clock; use si_layer_cache::persister::PersistStatus; +use si_pkg::KeyOrIndex; use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -922,4 +923,46 @@ impl WorkspaceSnapshot { }, ) } + #[instrument(level = "debug", skip_all)] + pub async fn index_or_key_of_child_entry( + &self, + id: impl Into, + ) -> WorkspaceSnapshotResult> { + // First, let's see if this node has an incoming, ordinal edge which means + // it has an index, in this case, it's an element in an Array + let maybe_id = id.into(); + let maybe_ordering_node = self + .incoming_sources_for_edge_weight_kind(maybe_id, EdgeWeightKindDiscriminants::Ordinal) + .await + .map_or(None, |node| node.first().cloned()); + + if let Some(maybe_ordering_node) = maybe_ordering_node { + // there's an ordering node, so let's grab the edgeweight which includes a + // a vec with the ids for all of the ordered children. + let order_node_weight = self + .get_node_weight(maybe_ordering_node) + .await? + .get_ordering_node_weight()?; + let index = order_node_weight.get_index_for_id(maybe_id)?; + + return Ok(Some(KeyOrIndex::Index(index))); + } + + // now let's see if we have a child entry for a Map + let maybe_containing_node = self + .edges_directed_for_edge_weight_kind( + maybe_id, + Direction::Incoming, + EdgeWeightKindDiscriminants::Contain, + ) + .await + .map_or(None, |node| node.first().cloned()); + if let Some((edge_weight, _, _)) = maybe_containing_node { + // grab the key from the edge weight + if let EdgeWeightKind::Contain(Some(contain_key)) = edge_weight.kind() { + return Ok(Some(KeyOrIndex::Key(contain_key.to_string()))); + } + } + Ok(None) + } } diff --git a/lib/dal/src/workspace_snapshot/node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight.rs index ef2cb4c3e4..db4afe980e 100644 --- a/lib/dal/src/workspace_snapshot/node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight.rs @@ -1,3 +1,5 @@ +use std::num::TryFromIntError; + use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use si_events::ContentHash; @@ -54,6 +56,10 @@ pub enum NodeWeightError { IncompatibleNodeWeightVariants, #[error("Invalid ContentAddress variant ({0}) for NodeWeight variant ({1})")] InvalidContentAddressForWeightKind(String, String), + #[error("Missing Key for Child Entry {0}")] + MissingKeytForChildEntry(Ulid), + #[error("try from int error: {0}")] + TryFromIntError(#[from] TryFromIntError), #[error("Unexpected content address variant: {1} expected {0}")] UnexpectedContentAddressVariant(ContentAddressDiscriminants, ContentAddressDiscriminants), #[error("Unexpected node weight variant. Got {1} but expected {0}")] diff --git a/lib/dal/src/workspace_snapshot/node_weight/ordering_node_weight.rs b/lib/dal/src/workspace_snapshot/node_weight/ordering_node_weight.rs index a2fecad166..135db17e3a 100644 --- a/lib/dal/src/workspace_snapshot/node_weight/ordering_node_weight.rs +++ b/lib/dal/src/workspace_snapshot/node_weight/ordering_node_weight.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use si_events::ContentHash; use ulid::Ulid; +use super::NodeWeightError; use crate::change_set::ChangeSet; use crate::workspace_snapshot::vector_clock::VectorClockId; use crate::workspace_snapshot::{node_weight::NodeWeightResult, vector_clock::VectorClock}; @@ -173,6 +174,15 @@ impl OrderingNodeWeight { Ok(false) } } + pub fn get_index_for_id(&self, id: Ulid) -> NodeWeightResult { + let order = self.order.to_owned(); + let index = order + .iter() + .position(|&key| key == id) + .ok_or(NodeWeightError::MissingKeytForChildEntry(id))?; + let ret: i64 = index.try_into().map_err(NodeWeightError::TryFromIntError)?; + Ok(ret) + } } impl std::fmt::Debug for OrderingNodeWeight { diff --git a/lib/dal/tests/integration_test/component.rs b/lib/dal/tests/integration_test/component.rs index ed59e53fd0..84682326f3 100644 --- a/lib/dal/tests/integration_test/component.rs +++ b/lib/dal/tests/integration_test/component.rs @@ -8,10 +8,10 @@ use dal::{Component, DalContext, Schema, SchemaVariant}; use dal_test::test; use dal_test::test_harness::create_component_for_schema_name; +mod debug; mod get_code; mod get_diff; mod set_type; - #[test] async fn update_and_insert_and_update(ctx: &mut DalContext) { let component = create_component_for_schema_name(ctx, "Docker Image", "a tulip in a cup").await; diff --git a/lib/dal/tests/integration_test/component/debug.rs b/lib/dal/tests/integration_test/component/debug.rs new file mode 100644 index 0000000000..e193093728 --- /dev/null +++ b/lib/dal/tests/integration_test/component/debug.rs @@ -0,0 +1,78 @@ +use dal::component::debug::ComponentDebugView; +use dal::prop::PropPath; +use dal::{AttributeValue, Component, DalContext, Prop}; +use dal_test::test; +use dal_test::test_harness::{commit_and_update_snapshot, create_component_for_schema_name}; +use pretty_assertions_sorted::assert_eq; + +#[test] +async fn get_debug_view(ctx: &mut DalContext) { + //create a new component for starfield schema + let component: Component = + create_component_for_schema_name(ctx, "starfield", "new component").await; + commit_and_update_snapshot(ctx).await; + //get the debug view + let component_debug_view = ComponentDebugView::new(ctx, component.id()) + .await + .expect("couldn't get component debug"); + + //make sure the debug view matches the SchemaVariant ID and component name + let sv_id = component + .schema_variant(ctx) + .await + .expect("couldn't get schema variant"); + + assert_eq!(component_debug_view.schema_variant_id, sv_id.id()); + assert_eq!(component_debug_view.name, "new component"); + + // get attribute values for the root prop for the component and make sure the paths match + let maybe_root_avs = component + .attribute_values_for_prop(ctx, &["root"]) + .await + .expect("couldn't get root prop"); + assert_eq!(maybe_root_avs.len(), 1); + + let root_av = Component::root_attribute_value_id(ctx, component.id()) + .await + .expect("couldn't get root av"); + let maybe_root_av = maybe_root_avs + .first() + .copied() + .expect("able to get the root av"); + assert_eq!(root_av, maybe_root_av); + + let prop_path = AttributeValue::get_path_for_id(ctx, root_av) + .await + .expect("can't get path"); + assert_eq!(prop_path, Some("root".to_string())); + + // get a more deeply nested prop/attribute value and + let rigid_prop_path = PropPath::new([ + "root", + "domain", + "possible_world_a", + "wormhole_1", + "wormhole_2", + "wormhole_3", + "rigid_designator", + ]); + let rigid_designator_prop_id = Prop::find_prop_id_by_path(ctx, sv_id.id(), &rigid_prop_path) + .await + .expect("able to find 'rigid_designator' prop"); + let rigid_designator_values = Prop::attribute_values_for_prop_id(ctx, rigid_designator_prop_id) + .await + .expect("able to get attribute value for rigid_designator prop"); + + let rigid_designator_value_id = rigid_designator_values + .first() + .copied() + .expect("get first value id"); + + let attribute_path = AttributeValue::get_path_for_id(ctx, rigid_designator_value_id) + .await + .expect("can't get the path"); + assert_eq!( + attribute_path, + Some(rigid_prop_path.with_replaced_sep("/").to_string()) + ); +} diff --git a/lib/sdf-server/src/server/service/component.rs b/lib/sdf-server/src/server/service/component.rs index 02b9caf0df..19e1a18f26 100644 --- a/lib/sdf-server/src/server/service/component.rs +++ b/lib/sdf-server/src/server/service/component.rs @@ -4,10 +4,10 @@ use axum::{ routing::{get, post}, Json, Router, }; -use dal::attribute::value::AttributeValueError; -use dal::component::ComponentId; use dal::property_editor::PropertyEditorError; use dal::validation::resolver::ValidationResolverError; +use dal::{attribute::value::debug::AttributeDebugViewError, component::ComponentId}; +use dal::{attribute::value::AttributeValueError, component::debug::ComponentDebugViewError}; use dal::{ActionPrototypeError, ComponentError as DalComponentError, StandardModelError}; use dal::{ChangeSetError, TransactionsError}; use thiserror::Error; @@ -46,6 +46,8 @@ pub enum ComponentError { // AttributePrototypeArgument(#[from] AttributePrototypeArgumentError), // #[error("attribute prototype not found")] // AttributePrototypeNotFound, + #[error("attribute debug view error: {0}")] + AttributeDebugViewError(#[from] AttributeDebugViewError), #[error("attribute value error: {0}")] AttributeValue(#[from] AttributeValueError), // #[error("attribute value not found")] @@ -56,8 +58,8 @@ pub enum ComponentError { // ChangeStatus(#[from] ChangeStatusError), // #[error("component debug view error: {0}")] // ComponentDebug(String), - // #[error("component debug view error: {0}")] - // ComponentDebugView(#[from] ComponentDebugViewError), + #[error("component debug view error: {0}")] + ComponentDebugView(#[from] ComponentDebugViewError), // #[error("component name not found")] // ComponentNameNotFound, // #[error("component view error: {0}")] diff --git a/lib/sdf-server/src/server/service/component/debug.rs b/lib/sdf-server/src/server/service/component/debug.rs index f54a2ada5f..92bafd951b 100644 --- a/lib/sdf-server/src/server/service/component/debug.rs +++ b/lib/sdf-server/src/server/service/component/debug.rs @@ -1,52 +1,11 @@ use axum::extract::Query; use axum::Json; - use serde::{Deserialize, Serialize}; +use telemetry::prelude::*; use super::ComponentResult; use crate::server::extract::{AccessBuilder, HandlerContext}; -use dal::{Component, ComponentId, SchemaVariantId, Visibility}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FrontendComponentDebugView { - name: String, - schema_variant_id: SchemaVariantId, - attributes: Vec, - input_sockets: Vec, - output_sockets: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FrontendAttributeDebugView { - name: String, - path: String, - // debug_data: AttributeMetadataView, -} - -// #[derive(Clone, Debug, Serialize, Deserialize)] -// #[serde(rename_all = "camelCase")] -// pub struct AttributeMetadataView { -// pub value_id: AttributeValueId, -// pub func_name: String, -// pub func_id: FuncId, -// pub func_args: serde_json::Value, -// pub arg_sources: HashMap>, -// pub visibility: Visibility, -// pub value: Option, -// #[serde(skip_serializing_if = "Option::is_none")] -// pub implicit_value: Option, -// #[serde(skip_serializing_if = "Option::is_none")] -// pub implicit_value_context: Option, -// #[serde(skip_serializing_if = "Option::is_none")] -// pub implicit_func_name: Option, -// pub prototype_id: AttributePrototypeId, -// pub prototype_context: AttributeContext, -// pub prototype_in_change_set: bool, -// pub value_in_change_set: bool, -// pub kind: Option, -// } +use dal::{component::debug::ComponentDebugView, ComponentId, Visibility}; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -56,160 +15,13 @@ pub struct DebugComponentRequest { pub visibility: Visibility, } -type DebugComponentResponse = FrontendComponentDebugView; - +#[instrument(level = "debug", skip_all)] pub async fn debug_component( HandlerContext(builder): HandlerContext, AccessBuilder(request_ctx): AccessBuilder, Query(request): Query, -) -> ComponentResult> { +) -> ComponentResult> { let ctx = builder.build(request_ctx.build(request.visibility)).await?; - - let component = Component::get_by_id(&ctx, request.component_id).await?; - let schema_variant_id = Component::schema_variant_id(&ctx, component.id()).await?; - - // let debug_view = ComponentDebugView::new(&ctx, &component).await?; - - let attributes = vec![]; - let input_sockets = vec![]; - let output_sockets = vec![]; - - // let transform_start = Instant::now(); - // - // for attribute_debug in debug_view.attributes { - // attributes.push(FrontendAttributeDebugView { - // name: attribute_debug - // .prop - // .as_ref() - // .map(|p| p.name()) - // .unwrap_or("") - // .into(), - // path: attribute_debug.path.to_owned(), - // debug_data: get_attribute_metadata(&ctx, attribute_debug).await?, - // }); - // } - // - // for attribute_debug in debug_view.input_sockets { - // input_sockets.push(FrontendAttributeDebugView { - // name: attribute_debug.path.to_owned(), - // path: "Input Socket".into(), - // debug_data: get_attribute_metadata(&ctx, attribute_debug).await?, - // }); - // } - // - // for attribute_debug in debug_view.output_sockets { - // output_sockets.push(FrontendAttributeDebugView { - // name: attribute_debug.path.to_owned(), - // path: "Output Socket".into(), - // debug_data: get_attribute_metadata(&ctx, attribute_debug).await?, - // }); - // } - // - // dbg!(transform_start.elapsed()); - - let component_view = DebugComponentResponse { - name: component.name(&ctx).await?, - schema_variant_id, - attributes, - input_sockets, - output_sockets, - }; - - Ok(Json(component_view)) + let debug_view = ComponentDebugView::new(&ctx, request.component_id).await?; + Ok(Json(debug_view)) } - -// async fn get_attribute_metadata( -// ctx: &DalContext, -// debug_view: AttributeDebugView, -// ) -> ComponentResult { -// let func_args = debug_view.func_binding.args().to_owned(); -// -// let mut arg_sources = HashMap::new(); -// -// for apa in -// AttributePrototypeArgument::list_for_attribute_prototype(ctx, *debug_view.prototype.id()) -// .await? -// { -// let arg = FuncArgument::get_by_id(ctx, &apa.func_argument_id()) -// .await? -// .ok_or(ComponentError::ComponentDebug(format!( -// "could not find func argument {}", -// apa.func_argument_id() -// )))?; -// -// let internal_provider_id = apa.internal_provider_id(); -// let input_ip_name = if internal_provider_id.is_some() { -// let ip = InternalProvider::get_by_id(ctx, &internal_provider_id) -// .await? -// .ok_or(ComponentError::ComponentDebug(format!( -// "could not find internal provider for input: {}", -// internal_provider_id -// )))?; -// -// let prop_id = *ip.prop_id(); -// let path = if prop_id == PropId::NONE { -// format!("Input Socket: {}", ip.name()) -// } else { -// let prop = -// Prop::get_by_id(ctx, &prop_id) -// .await? -// .ok_or(ComponentError::ComponentDebug(format!( -// "could not find prop {} for provider for input {}", -// prop_id, internal_provider_id -// )))?; -// -// format!("Prop: /{}", prop.path().with_replaced_sep("/")) -// }; -// -// Some(path) -// } else { -// None -// }; -// -// arg_sources.insert(arg.name().into(), input_ip_name); -// } -// -// let (implicit_value, implicit_value_context, implicit_func_name) = -// match debug_view.implicit_attribute_value { -// Some(implicit_attribute_value) => { -// let prototype = implicit_attribute_value -// .attribute_prototype(ctx) -// .await? -// .ok_or(AttributeValueError::AttributePrototypeNotFound( -// *implicit_attribute_value.id(), -// ctx.visibility().to_owned(), -// ))?; -// let func = Func::get_by_id(ctx, &prototype.func_id()) -// .await? -// .ok_or(FuncError::NotFound(prototype.func_id()))?; -// -// ( -// implicit_attribute_value.get_value(ctx).await?, -// Some(implicit_attribute_value.context), -// Some(func.name().to_owned()), -// ) -// } -// None => (None, None, None), -// }; -// -// Ok(AttributeMetadataView { -// value_id: *debug_view.attribute_value.id(), -// func_name: debug_view.func.name().into(), -// func_id: debug_view.func.id().to_owned(), -// func_args, -// arg_sources, -// visibility: debug_view.attribute_value.visibility().to_owned(), -// value: debug_view -// .attribute_value -// .get_unprocessed_value(ctx) -// .await?, -// prototype_id: *debug_view.prototype.id(), -// prototype_context: debug_view.prototype.context, -// kind: debug_view.prop.map(|prop| *prop.kind()), -// prototype_in_change_set: debug_view.prototype.visibility().in_change_set(), -// value_in_change_set: debug_view.attribute_value.visibility().in_change_set(), -// implicit_value, -// implicit_value_context, -// implicit_func_name, -// }) -// } diff --git a/lib/sdf-server/src/server/service/graphviz.rs b/lib/sdf-server/src/server/service/graphviz.rs index 7500b059bf..21b74423ec 100644 --- a/lib/sdf-server/src/server/service/graphviz.rs +++ b/lib/sdf-server/src/server/service/graphviz.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use axum::{extract::Query, response::Response, routing::get, Json, Router}; use dal::{ + attribute::value::AttributeValueError, schema::variant::SchemaVariantError, workspace_snapshot::{ content_address::ContentAddressDiscriminants, @@ -9,7 +10,8 @@ use dal::{ node_weight::{NodeWeight, NodeWeightDiscriminants}, WorkspaceSnapshotError, }, - Component, ComponentError, SchemaVariant, SchemaVariantId, TransactionsError, Visibility, + AttributeValue, Component, ComponentError, SchemaVariant, SchemaVariantId, TransactionsError, + Visibility, }; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -24,6 +26,8 @@ use crate::server::{ #[remain::sorted] #[derive(Error, Debug)] pub enum GraphVizError { + #[error(transparent)] + AttributeValueError(#[from] AttributeValueError), #[error(transparent)] Component(#[from] ComponentError), #[error(transparent)] @@ -32,6 +36,8 @@ pub enum GraphVizError { NoRootNode, #[error(transparent)] SchemaVariant(#[from] SchemaVariantError), + #[error("serde json: {0}")] + SerdeJson(#[from] serde_json::Error), #[error("could not acquire lock: {0}")] TryLock(#[from] tokio::sync::TryLockError), #[error("workspace snapshot error")] @@ -358,6 +364,9 @@ pub async fn components( .name(&ctx) .await?, ), + NodeWeight::AttributeValue(inner) => { + AttributeValue::get_path_for_id(&ctx, inner.id().into()).await? + } _ => None, }; diff --git a/lib/si-pkg/src/spec/attribute_value.rs b/lib/si-pkg/src/spec/attribute_value.rs index 96b3a68e40..6769979f9e 100644 --- a/lib/si-pkg/src/spec/attribute_value.rs +++ b/lib/si-pkg/src/spec/attribute_value.rs @@ -1,3 +1,5 @@ +use core::fmt; + use derive_builder::Builder; use serde::{Deserialize, Serialize}; @@ -11,13 +13,48 @@ use super::{ pub enum AttributeValuePath { Prop { path: String, - key: Option, - index: Option, + key_or_index: Option, }, InputSocket(String), OutputSocket(String), } +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum KeyOrIndex { + Key(String), + Index(i64), +} +impl fmt::Display for KeyOrIndex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let _ = match self { + KeyOrIndex::Key(key) => write!(f, "[{}]", key), + KeyOrIndex::Index(index) => write!(f, "[{}]", index), + }; + Ok(()) + } +} +impl fmt::Display for AttributeValuePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttributeValuePath::Prop { path, key_or_index } => { + if let Some(attribute_value_index_or_key) = key_or_index { + write!(f, "{}{}", path, attribute_value_index_or_key)? + } else { + write!(f, "{}", path)? + } + } + AttributeValuePath::InputSocket(path) => write!(f, "{}", path)?, + AttributeValuePath::OutputSocket(path) => write!(f, "{}", path)?, + }; + Ok(()) + } +} + +/// This is the separator used for the "path" column. It is a vertical tab character, which should +/// not (we'll see) be able to be provided by our users in [`Prop`] names. +pub const ATTRIBUTE_VALUE_PATH_SEPARATOR: &str = "\x0B"; + impl AttributeValuePath { pub fn path(&self) -> &str { match self { @@ -26,6 +63,28 @@ impl AttributeValuePath { Self::OutputSocket(path) => path, } } + + pub fn set_index_or_key(&self, key_or_index: Option) -> AttributeValuePath { + Self::Prop { + path: self.path().to_string(), + key_or_index, + } + } + pub fn assemble_from_parts_with_separator( + parts: impl IntoIterator, + separator: Option<&str>, + ) -> String { + // use default separator unless one is passed in + let separator = match separator { + Some(sep) => sep, + None => ATTRIBUTE_VALUE_PATH_SEPARATOR, + }; + parts + .into_iter() + .map(|part| part.to_string()) + .collect::>() + .join(separator) + } } #[derive(Builder, Clone, Debug, Deserialize, Serialize)]