From f8edfb6718807152b8d49432cdb03dca47378aa1 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 20 May 2026 21:08:33 +0200 Subject: [PATCH 01/13] feat: added sub flows and sub flow settings --- ...120000_add_tucana_shared_flow_sub_flows.rb | 38 ++++++++++ db/structure.sql | 75 +++++++++++++++++-- 2 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb diff --git a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb new file mode 100644 index 00000000..777f1c09 --- /dev/null +++ b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class AddTucanaSharedFlowSubFlows < Code0::ZeroTrack::Database::Migration[1.0] + def change + add_column :flow_settings, :cast, :text + + add_column :node_parameters, :cast, :text + + create_table :sub_flows do |t| + t.references :node_parameter, null: false, foreign_key: { to_table: :node_parameters, on_delete: :cascade } + t.references :starting_node, null: true, foreign_key: { to_table: :node_functions, on_delete: :restrict } + t.text :function_identifier + t.text :signature, null: false + t.index :node_parameter_id, unique: true + + t.check_constraint 'num_nonnulls(starting_node_id, function_identifier) = 1', + name: check_constraint_name(:sub_flows, :execution_reference, :one_of) + + t.timestamps_with_timezone + end + + create_table :sub_flow_settings do |t| + t.references :sub_flow, null: false, foreign_key: { to_table: :sub_flows, on_delete: :cascade } + t.text :identifier, null: false + t.jsonb :default_value + # rubocop:disable Rails/ThreeStateBooleanColumn -- mirrors proto optional bool presence + t.boolean :optional + t.boolean :hidden + # rubocop:enable Rails/ThreeStateBooleanColumn + + t.timestamps_with_timezone + end + + remove_reference :node_functions, :value_of_node_parameter, + null: true, + foreign_key: { to_table: :node_parameters, on_delete: :cascade } + end +end diff --git a/db/structure.sql b/db/structure.sql index 0375537b..7d70b740 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -204,6 +204,7 @@ CREATE TABLE flow_settings ( flow_id bigint NOT NULL, flow_setting_id text NOT NULL, object jsonb NOT NULL, + "cast" text, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL ); @@ -614,8 +615,7 @@ CREATE TABLE node_functions ( created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, flow_id bigint NOT NULL, - function_definition_id bigint NOT NULL, - value_of_node_parameter_id bigint + function_definition_id bigint NOT NULL ); CREATE SEQUENCE node_functions_id_seq @@ -631,6 +631,7 @@ CREATE TABLE node_parameters ( id bigint NOT NULL, node_function_id bigint NOT NULL, literal_value jsonb, + "cast" text, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, parameter_definition_id bigint NOT NULL @@ -721,6 +722,46 @@ CREATE SEQUENCE reference_values_id_seq ALTER SEQUENCE reference_values_id_seq OWNED BY reference_values.id; +CREATE TABLE sub_flow_settings ( + id bigint NOT NULL, + sub_flow_id bigint NOT NULL, + identifier text NOT NULL, + default_value jsonb, + optional boolean, + hidden boolean, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE sub_flow_settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE sub_flow_settings_id_seq OWNED BY sub_flow_settings.id; + +CREATE TABLE sub_flows ( + id bigint NOT NULL, + node_parameter_id bigint NOT NULL, + starting_node_id bigint, + function_identifier text, + signature text NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + CONSTRAINT check_sub_flows_execution_reference_one_of CHECK ((num_nonnulls(starting_node_id, function_identifier) = 1)) +); + +CREATE SEQUENCE sub_flows_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE sub_flows_id_seq OWNED BY sub_flows.id; + CREATE TABLE runtime_flow_type_data_type_links ( id bigint NOT NULL, runtime_flow_type_id bigint NOT NULL, @@ -1096,6 +1137,10 @@ ALTER TABLE ONLY reference_paths ALTER COLUMN id SET DEFAULT nextval('reference_ ALTER TABLE ONLY reference_values ALTER COLUMN id SET DEFAULT nextval('reference_values_id_seq'::regclass); +ALTER TABLE ONLY sub_flow_settings ALTER COLUMN id SET DEFAULT nextval('sub_flow_settings_id_seq'::regclass); + +ALTER TABLE ONLY sub_flows ALTER COLUMN id SET DEFAULT nextval('sub_flows_id_seq'::regclass); + ALTER TABLE ONLY runtime_flow_type_data_type_links ALTER COLUMN id SET DEFAULT nextval('runtime_flow_type_data_type_links_id_seq'::regclass); ALTER TABLE ONLY runtime_flow_type_settings ALTER COLUMN id SET DEFAULT nextval('runtime_flow_type_settings_id_seq'::regclass); @@ -1241,6 +1286,12 @@ ALTER TABLE ONLY reference_paths ALTER TABLE ONLY reference_values ADD CONSTRAINT reference_values_pkey PRIMARY KEY (id); +ALTER TABLE ONLY sub_flow_settings + ADD CONSTRAINT sub_flow_settings_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT sub_flows_pkey PRIMARY KEY (id); + ALTER TABLE ONLY runtime_flow_type_data_type_links ADD CONSTRAINT runtime_flow_type_data_type_links_pkey PRIMARY KEY (id); @@ -1442,8 +1493,6 @@ CREATE INDEX index_node_functions_on_function_definition_id ON node_functions US CREATE INDEX index_node_functions_on_next_node_id ON node_functions USING btree (next_node_id); -CREATE INDEX index_node_functions_on_value_of_node_parameter_id ON node_functions USING btree (value_of_node_parameter_id); - CREATE INDEX index_node_parameters_on_node_function_id ON node_parameters USING btree (node_function_id); CREATE INDEX index_node_parameters_on_parameter_definition_id ON node_parameters USING btree (parameter_definition_id); @@ -1460,6 +1509,12 @@ CREATE INDEX index_reference_values_on_node_function_id ON reference_values USIN CREATE INDEX index_reference_values_on_node_parameter_id ON reference_values USING btree (node_parameter_id); +CREATE INDEX index_sub_flow_settings_on_sub_flow_id ON sub_flow_settings USING btree (sub_flow_id); + +CREATE UNIQUE INDEX index_sub_flows_on_node_parameter_id ON sub_flows USING btree (node_parameter_id); + +CREATE INDEX index_sub_flows_on_starting_node_id ON sub_flows USING btree (starting_node_id); + CREATE UNIQUE INDEX index_runtime_flow_types_on_runtime_id_and_identifier ON runtime_flow_types USING btree (runtime_id, identifier); CREATE INDEX index_runtime_status_configurations_on_runtime_status_id ON runtime_status_configurations USING btree (runtime_status_id); @@ -1614,6 +1669,15 @@ ALTER TABLE ONLY reference_values ALTER TABLE ONLY reference_values ADD CONSTRAINT fk_rails_8c916f07f1 FOREIGN KEY (node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT fk_sub_flows_node_parameter FOREIGN KEY (node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; + +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT fk_sub_flows_starting_node FOREIGN KEY (starting_node_id) REFERENCES node_functions(id) ON DELETE RESTRICT; + +ALTER TABLE ONLY sub_flow_settings + ADD CONSTRAINT fk_sub_flow_settings_sub_flow FOREIGN KEY (sub_flow_id) REFERENCES sub_flows(id) ON DELETE CASCADE; + ALTER TABLE ONLY data_type_data_type_links ADD CONSTRAINT fk_rails_90fbf0d8ef FOREIGN KEY (data_type_id) REFERENCES data_types(id) ON DELETE CASCADE; @@ -1674,9 +1738,6 @@ ALTER TABLE ONLY runtimes ALTER TABLE ONLY flow_data_type_links ADD CONSTRAINT fk_rails_f4202724d3 FOREIGN KEY (flow_id) REFERENCES flows(id) ON DELETE CASCADE; -ALTER TABLE ONLY node_functions - ADD CONSTRAINT fk_rails_f5d1a9d316 FOREIGN KEY (value_of_node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; - ALTER TABLE ONLY audit_events ADD CONSTRAINT fk_rails_f64374fc56 FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL; From 3e57aa44cfccfa725feac0058f5081d42fbf0d8a Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 20 May 2026 21:11:11 +0200 Subject: [PATCH 02/13] feat: adjusted service layers to new sub flow --- app/graphql/types/flow_setting_type.rb | 4 + .../types/flow_sub_flow_setting_type.rb | 20 +++++ app/graphql/types/flow_sub_flow_type.rb | 25 ++++++ .../types/input/flow_setting_input_type.rb | 3 + .../types/input/flow_sub_flow_input_type.rb | 24 ++++++ .../input/flow_sub_flow_setting_input_type.rb | 22 ++++++ .../types/input/node_parameter_input_type.rb | 3 + .../input/node_parameter_value_input_type.rb | 6 +- .../types/node_function_id_wrapper_type.rb | 9 --- app/graphql/types/node_parameter_type.rb | 5 +- .../types/node_parameter_value_type.rb | 8 +- app/models/flow_setting.rb | 3 +- app/models/node_function.rb | 5 -- app/models/node_parameter.rb | 13 ++-- app/models/sub_flow.rb | 27 +++++++ app/models/sub_flow_setting.rb | 16 ++++ app/services/error_code.rb | 1 - .../projects/flows/update_service.rb | 78 ++++++++++++++----- docs/graphql/enum/errorcodeenum.md | 1 - docs/graphql/input_object/flowsettinginput.md | 1 + docs/graphql/input_object/flowsubflowinput.md | 14 ++++ .../input_object/flowsubflowsettinginput.md | 14 ++++ .../input_object/nodeparameterinput.md | 1 + .../input_object/nodeparametervalueinput.md | 2 +- docs/graphql/object/flowsetting.md | 1 + docs/graphql/object/flowsubflow.md | 14 ++++ docs/graphql/object/flowsubflowsetting.md | 14 ++++ docs/graphql/object/nodefunctionidwrapper.md | 11 --- docs/graphql/object/nodeparameter.md | 1 + docs/graphql/union/nodeparametervalue.md | 2 +- spec/factories/node_functions.rb | 1 - spec/factories/node_parameters.rb | 1 - spec/factories/sub_flow_settings.rb | 11 +++ spec/factories/sub_flows.rb | 10 +++ spec/models/node_function_spec.rb | 7 -- spec/models/node_parameter_spec.rb | 15 +--- spec/models/sub_flow_setting_spec.rb | 15 ++++ spec/models/sub_flow_spec.rb | 22 ++++++ .../projects/flows/create_mutation_spec.rb | 16 +++- .../projects/flows/update_mutation_spec.rb | 30 ++++--- 40 files changed, 381 insertions(+), 95 deletions(-) create mode 100644 app/graphql/types/flow_sub_flow_setting_type.rb create mode 100644 app/graphql/types/flow_sub_flow_type.rb create mode 100644 app/graphql/types/input/flow_sub_flow_input_type.rb create mode 100644 app/graphql/types/input/flow_sub_flow_setting_input_type.rb delete mode 100644 app/graphql/types/node_function_id_wrapper_type.rb create mode 100644 app/models/sub_flow.rb create mode 100644 app/models/sub_flow_setting.rb create mode 100644 docs/graphql/input_object/flowsubflowinput.md create mode 100644 docs/graphql/input_object/flowsubflowsettinginput.md create mode 100644 docs/graphql/object/flowsubflow.md create mode 100644 docs/graphql/object/flowsubflowsetting.md delete mode 100644 docs/graphql/object/nodefunctionidwrapper.md create mode 100644 spec/factories/sub_flow_settings.rb create mode 100644 spec/factories/sub_flows.rb create mode 100644 spec/models/sub_flow_setting_spec.rb create mode 100644 spec/models/sub_flow_spec.rb diff --git a/app/graphql/types/flow_setting_type.rb b/app/graphql/types/flow_setting_type.rb index 24c9c83c..2ec38f88 100644 --- a/app/graphql/types/flow_setting_type.rb +++ b/app/graphql/types/flow_setting_type.rb @@ -6,6 +6,10 @@ class FlowSettingType < Types::BaseObject authorize :read_flow + field :cast, String, + null: true, + description: 'The cast applied to the flow setting' + field :flow_setting_identifier, String, null: false, method: :flow_setting_id, diff --git a/app/graphql/types/flow_sub_flow_setting_type.rb b/app/graphql/types/flow_sub_flow_setting_type.rb new file mode 100644 index 00000000..bc80d8f9 --- /dev/null +++ b/app/graphql/types/flow_sub_flow_setting_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + class FlowSubFlowSettingType < Types::BaseObject + description 'Represents a sub-flow setting.' + + field :default_value, GraphQL::Types::JSON, + null: true, + description: 'The default value of the sub-flow setting.' + field :hidden, Boolean, + null: true, + description: 'Whether the sub-flow setting is hidden.' + field :identifier, String, + null: false, + description: 'The identifier of the sub-flow setting.' + field :optional, Boolean, + null: true, + description: 'Whether the sub-flow setting is optional.' + end +end diff --git a/app/graphql/types/flow_sub_flow_type.rb b/app/graphql/types/flow_sub_flow_type.rb new file mode 100644 index 00000000..ec743081 --- /dev/null +++ b/app/graphql/types/flow_sub_flow_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + class FlowSubFlowType < Types::BaseObject + description 'Represents a sub-flow parameter value.' + + field :function_identifier, String, + null: true, + description: 'The function identifier to execute.' + field :settings, [Types::FlowSubFlowSettingType], + method: :sub_flow_settings, + null: false, + description: 'The sub-flow settings.' + field :signature, String, + null: false, + description: 'The sub-flow signature.' + field :starting_node_id, GlobalIdType[::NodeFunction], + null: true, + description: 'The starting node to execute.' + + def starting_node_id + object.starting_node&.to_global_id + end + end +end diff --git a/app/graphql/types/input/flow_setting_input_type.rb b/app/graphql/types/input/flow_setting_input_type.rb index 259fc0d0..8f743df1 100644 --- a/app/graphql/types/input/flow_setting_input_type.rb +++ b/app/graphql/types/input/flow_setting_input_type.rb @@ -5,6 +5,9 @@ module Input class FlowSettingInputType < Types::BaseInputObject description 'Input type for flow settings' + argument :cast, String, + required: false, + description: 'The cast applied to the flow setting' argument :value, GraphQL::Types::JSON, required: true, description: 'The value of the flow setting' end diff --git a/app/graphql/types/input/flow_sub_flow_input_type.rb b/app/graphql/types/input/flow_sub_flow_input_type.rb new file mode 100644 index 00000000..43a82670 --- /dev/null +++ b/app/graphql/types/input/flow_sub_flow_input_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Input + class FlowSubFlowInputType < Types::BaseInputObject + description 'Input type for sub-flow parameter values' + + argument :function_identifier, String, + required: false, + description: 'The function identifier to execute' + argument :settings, [Types::Input::FlowSubFlowSettingInputType], + required: false, + description: 'The sub-flow settings' + argument :signature, String, + required: true, + description: 'The sub-flow signature' + argument :starting_node_id, Types::GlobalIdType[::NodeFunction], + required: false, + description: 'The starting node to execute' + + require_one_of %i[starting_node_id function_identifier] + end + end +end diff --git a/app/graphql/types/input/flow_sub_flow_setting_input_type.rb b/app/graphql/types/input/flow_sub_flow_setting_input_type.rb new file mode 100644 index 00000000..14cbe64d --- /dev/null +++ b/app/graphql/types/input/flow_sub_flow_setting_input_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Input + class FlowSubFlowSettingInputType < Types::BaseInputObject + description 'Input type for sub-flow settings' + + argument :default_value, GraphQL::Types::JSON, + required: false, + description: 'The default value of the sub-flow setting' + argument :hidden, Boolean, + required: false, + description: 'Whether the sub-flow setting is hidden' + argument :identifier, String, + required: true, + description: 'The identifier of the sub-flow setting' + argument :optional, Boolean, + required: false, + description: 'Whether the sub-flow setting is optional' + end + end +end diff --git a/app/graphql/types/input/node_parameter_input_type.rb b/app/graphql/types/input/node_parameter_input_type.rb index 003b24b6..f12ed698 100644 --- a/app/graphql/types/input/node_parameter_input_type.rb +++ b/app/graphql/types/input/node_parameter_input_type.rb @@ -5,6 +5,9 @@ module Input class NodeParameterInputType < Types::BaseInputObject description 'Input type for Node parameter' + argument :cast, String, + required: false, + description: 'The cast applied to the parameter' argument :value, Types::Input::NodeParameterValueInputType, required: true, description: 'The value of the parameter' end diff --git a/app/graphql/types/input/node_parameter_value_input_type.rb b/app/graphql/types/input/node_parameter_value_input_type.rb index 027f5f1d..59cd1157 100644 --- a/app/graphql/types/input/node_parameter_value_input_type.rb +++ b/app/graphql/types/input/node_parameter_value_input_type.rb @@ -7,12 +7,12 @@ class NodeParameterValueInputType < Types::BaseInputObject argument :literal_value, GraphQL::Types::JSON, required: false, description: 'The literal value of the parameter' - argument :node_function_id, Types::GlobalIdType[::NodeFunction], - required: false, description: 'The function value of the parameter as an id' argument :reference_value, Types::Input::ReferenceValueInputType, required: false, description: 'The reference value of the parameter' + argument :sub_flow, Types::Input::FlowSubFlowInputType, + required: false, description: 'The sub-flow value of the parameter' - require_one_of %i[node_function_id literal_value reference_value] + require_one_of %i[literal_value reference_value sub_flow] end end end diff --git a/app/graphql/types/node_function_id_wrapper_type.rb b/app/graphql/types/node_function_id_wrapper_type.rb deleted file mode 100644 index 5db7a54a..00000000 --- a/app/graphql/types/node_function_id_wrapper_type.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module Types - class NodeFunctionIdWrapperType < Types::BaseObject - description 'Represents a Node Function id wrapper.' - - id_field NodeFunction - end -end diff --git a/app/graphql/types/node_parameter_type.rb b/app/graphql/types/node_parameter_type.rb index 8159daf4..fb22e3ee 100644 --- a/app/graphql/types/node_parameter_type.rb +++ b/app/graphql/types/node_parameter_type.rb @@ -6,6 +6,7 @@ class NodeParameterType < Types::BaseObject authorize :read_flow + field :cast, String, null: true, description: 'The cast applied to the parameter' field :parameter_definition, Types::ParameterDefinitionType, null: false, description: 'The definition of the parameter' field :value, Types::NodeParameterValueType, null: true, description: 'The value of the parameter' @@ -13,8 +14,8 @@ class NodeParameterType < Types::BaseObject def value if object.reference_value.present? object.reference_value - elsif object.function_value.present? - object.function_value + elsif object.sub_flow.present? + object.sub_flow else object.literal_value end diff --git a/app/graphql/types/node_parameter_value_type.rb b/app/graphql/types/node_parameter_value_type.rb index 33b28cb2..aa6f83da 100644 --- a/app/graphql/types/node_parameter_value_type.rb +++ b/app/graphql/types/node_parameter_value_type.rb @@ -4,15 +4,15 @@ module Types class NodeParameterValueType < Types::BaseUnion description 'Represents a parameter value for a node.' - possible_types Types::LiteralValueType, Types::ReferenceValueType, Types::NodeFunctionIdWrapperType, - description: 'The value can be a literal, a reference, or a node function id.' + possible_types Types::FlowSubFlowType, Types::LiteralValueType, Types::ReferenceValueType, + description: 'The value can be a literal, a reference, or a sub-flow.' def self.resolve_type(object, _context) case object when ReferenceValue Types::ReferenceValueType - when NodeFunction - Types::NodeFunctionIdWrapperType + when SubFlow + Types::FlowSubFlowType else Types::LiteralValueType end diff --git a/app/models/flow_setting.rb b/app/models/flow_setting.rb index e543fb1a..2c9556d7 100644 --- a/app/models/flow_setting.rb +++ b/app/models/flow_setting.rb @@ -9,7 +9,8 @@ def to_grpc Tucana::Shared::FlowSetting.new( database_id: id, flow_setting_id: flow_setting_id, - value: Tucana::Shared::Value.from_ruby(object) + value: Tucana::Shared::Value.from_ruby(object), + cast: cast ) end end diff --git a/app/models/node_function.rb b/app/models/node_function.rb index 4b2ccda8..01452ff0 100644 --- a/app/models/node_function.rb +++ b/app/models/node_function.rb @@ -5,11 +5,6 @@ class NodeFunction < ApplicationRecord belongs_to :next_node, class_name: 'NodeFunction', optional: true belongs_to :flow, class_name: 'Flow' - belongs_to :value_of_node_parameter, - class_name: 'NodeParameter', - inverse_of: :function_value, - optional: true - has_one :previous_node, class_name: 'NodeFunction', foreign_key: :next_node_id, diff --git a/app/models/node_parameter.rb b/app/models/node_parameter.rb index 48a873a0..d807e332 100644 --- a/app/models/node_parameter.rb +++ b/app/models/node_parameter.rb @@ -5,22 +5,23 @@ class NodeParameter < ApplicationRecord belongs_to :node_function, class_name: 'NodeFunction', inverse_of: :node_parameters has_one :reference_value, autosave: true - has_one :function_value, class_name: 'NodeFunction', inverse_of: :value_of_node_parameter + has_one :sub_flow, autosave: true, dependent: :destroy validate :only_one_value_present def to_grpc param = Tucana::Shared::NodeParameter.new( database_id: id, - runtime_parameter_id: parameter_definition.runtime_parameter_definition.runtime_name + runtime_parameter_id: parameter_definition.runtime_parameter_definition.runtime_name, + cast: cast ) param.value = Tucana::Shared::NodeValue.new(literal_value: Tucana::Shared::Value.from_ruby({})) if reference_value.present? param.value.reference_value = reference_value.to_grpc - elsif function_value.present? - param.value.node_function_id = function_value.id + elsif sub_flow.present? + param.value.sub_flow = sub_flow.to_grpc else param.value.literal_value = Tucana::Shared::Value.from_ruby(literal_value) end @@ -31,9 +32,9 @@ def to_grpc private def only_one_value_present - values = [!literal_value.nil?, reference_value.present?, function_value.present?] + values = [!literal_value.nil?, reference_value.present?, sub_flow.present?] return if values.count(true) <= 1 - errors.add(:value, 'Only one of literal_value, reference_value, or function_value must be present') + errors.add(:value, 'Only one of literal_value, reference_value, or sub_flow must be present') end end diff --git a/app/models/sub_flow.rb b/app/models/sub_flow.rb new file mode 100644 index 00000000..ac1bc072 --- /dev/null +++ b/app/models/sub_flow.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class SubFlow < ApplicationRecord + belongs_to :node_parameter, inverse_of: :sub_flow + belongs_to :starting_node, class_name: 'NodeFunction', optional: true + + has_many :sub_flow_settings, inverse_of: :sub_flow, autosave: true, dependent: :destroy + + validate :validate_execution_reference + + def to_grpc + Tucana::Shared::SubFlow.new( + starting_node_id: starting_node_id, + function_identifier: function_identifier, + signature: signature, + settings: sub_flow_settings.map(&:to_grpc) + ) + end + + private + + def validate_execution_reference + return if [starting_node_id.present?, function_identifier.present?].count(true) == 1 + + errors.add(:base, 'Exactly one of starting_node or function_identifier must be present') + end +end diff --git a/app/models/sub_flow_setting.rb b/app/models/sub_flow_setting.rb new file mode 100644 index 00000000..c3545e47 --- /dev/null +++ b/app/models/sub_flow_setting.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class SubFlowSetting < ApplicationRecord + belongs_to :sub_flow, inverse_of: :sub_flow_settings + + validates :identifier, presence: true + + def to_grpc + Tucana::Shared::SubFlowSetting.new( + identifier: identifier, + default_value: default_value.nil? ? nil : Tucana::Shared::Value.from_ruby(default_value), + optional: optional, + hidden: hidden + ) + end +end diff --git a/app/services/error_code.rb b/app/services/error_code.rb index c94cd5db..d86a0987 100644 --- a/app/services/error_code.rb +++ b/app/services/error_code.rb @@ -88,7 +88,6 @@ def self.error_codes cyclic_data_type_reference: { description: 'A data type dependency cycle was detected' }, invalid_data_type_link: { description: 'The data type link is invalid because of active model errors' }, node_not_found: { description: 'The node with this id does not exist' }, - function_value_not_found: { description: 'The id for the function value node does not exist' }, invalid_node_parameter: { description: 'The node parameter is invalid' }, invalid_node_function: { description: 'The node function is invalid' }, invalid_runtime_status: { description: 'The runtime status is invalid because of active model errors' }, diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index 0c1f021c..8bfe3f21 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -60,6 +60,7 @@ def update_settings(t) db_settings[index] ||= flow.flow_settings.build db_settings[index].flow_setting_id = flow_type_settings[index]&.identifier db_settings[index].object = setting.value + db_settings[index].cast = optional_input(setting, :cast) next if db_settings[index].save @@ -85,6 +86,14 @@ def update_nodes(t) current_node = all_nodes[node_index] || NodeFunction.new(flow: flow) update_node(t, current_node, node_input) + unless current_node.save + t.rollback_and_return! ServiceResponse.error( + message: 'Invalid node', + error_code: :invalid_node_function, + details: current_node.errors + ) + end + updated_nodes << { node: current_node, input: node_input } node_index += 1 @@ -181,22 +190,15 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) end db_parameters[index].parameter_definition = parameter_definition + db_parameters[index].cast = optional_input(parameter, :cast) db_parameters[index].literal_value = parameter.value.literal_value - if parameter.value.node_function_id.present? - node = all_nodes.find { |n| n[:input].id == parameter.value.node_function_id } - - if node.nil? - t.rollback_and_return! ServiceResponse.error( - message: 'Invalid function value for parameter', - error_code: :function_value_not_found - ) - end - - db_parameters[index].function_value = node[:node] + if optional_input(parameter.value, :sub_flow).present? + update_sub_flow(t, db_parameters[index], parameter.value.sub_flow, all_nodes) else - db_parameters[index].function_value = nil + db_parameters[index].sub_flow&.destroy + db_parameters[index].sub_flow = nil end if parameter.value.reference_value.present? @@ -246,13 +248,6 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) ) end - removed_parameters = current_node.node_parameters - db_parameters - # rubocop:disable Rails/SkipsModelValidations -- must nullify FK before parameter destruction to prevent cascade - flow.node_functions - .where(value_of_node_parameter: removed_parameters) - .update_all(value_of_node_parameter_id: nil) - # rubocop:enable Rails/SkipsModelValidations - current_node.node_parameters = db_parameters end @@ -267,6 +262,51 @@ def create_audit_event } ) end + + def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) + starting_node_id = nil + + if optional_input(sub_flow_input, :starting_node_id).present? + starting_node = all_nodes.find { |n| n[:input].id == sub_flow_input.starting_node_id } + + if starting_node.nil? + t.rollback_and_return! ServiceResponse.error( + message: 'Sub-flow starting node not found', + error_code: :node_not_found + ) + end + + starting_node_id = starting_node[:node].id + end + + sub_flow = node_parameter.sub_flow || node_parameter.build_sub_flow + sub_flow.assign_attributes( + starting_node_id: starting_node_id, + function_identifier: optional_input(sub_flow_input, :function_identifier), + signature: sub_flow_input.signature + ) + + sub_flow_settings_input = Array(optional_input(sub_flow_input, :settings)) + sub_flow_settings = sub_flow.sub_flow_settings.first(sub_flow_settings_input.length) + + sub_flow_settings_input.each_with_index do |setting, index| + sub_flow_settings[index] ||= sub_flow.sub_flow_settings.build + sub_flow_settings[index].assign_attributes( + identifier: setting.identifier, + default_value: optional_input(setting, :default_value), + optional: optional_input(setting, :optional), + hidden: optional_input(setting, :hidden) + ) + end + + (sub_flow.sub_flow_settings - sub_flow_settings).each(&:destroy) + end + + def optional_input(input, attribute) + return unless input.respond_to?(attribute) + + input.public_send(attribute) + end end end end diff --git a/docs/graphql/enum/errorcodeenum.md b/docs/graphql/enum/errorcodeenum.md index 6aa35ecd..1bd36423 100644 --- a/docs/graphql/enum/errorcodeenum.md +++ b/docs/graphql/enum/errorcodeenum.md @@ -20,7 +20,6 @@ Represents the available error responses | `FAILED_TO_SAVE_VALID_BACKUP_CODE` | The new backup codes could not be saved | | `FLOW_NOT_FOUND` | The flow with the given identifier was not found | | `FLOW_TYPE_NOT_FOUND` | The flow type with the given identifier was not found | -| `FUNCTION_VALUE_NOT_FOUND` | The id for the function value node does not exist | | `GENERIC_KEY_NOT_FOUND` | The given key was not found in the data type | | `IDENTITY_NOT_FOUND` | The external identity with the given identifier was not found | | `IDENTITY_VALIDATION_FAILED` | Failed to validate the external identity | diff --git a/docs/graphql/input_object/flowsettinginput.md b/docs/graphql/input_object/flowsettinginput.md index 403e6ee7..92e3a625 100644 --- a/docs/graphql/input_object/flowsettinginput.md +++ b/docs/graphql/input_object/flowsettinginput.md @@ -8,4 +8,5 @@ Input type for flow settings | Name | Type | Description | |------|------|-------------| +| `cast` | [`String`](../scalar/string.md) | The cast applied to the flow setting | | `value` | [`JSON!`](../scalar/json.md) | The value of the flow setting | diff --git a/docs/graphql/input_object/flowsubflowinput.md b/docs/graphql/input_object/flowsubflowinput.md new file mode 100644 index 00000000..c853ee22 --- /dev/null +++ b/docs/graphql/input_object/flowsubflowinput.md @@ -0,0 +1,14 @@ +--- +title: FlowSubFlowInput +--- + +Input type for sub-flow parameter values + +## Fields + +| Name | Type | Description | +|------|------|-------------| +| `functionIdentifier` | [`String`](../scalar/string.md) | The function identifier to execute | +| `settings` | [`[FlowSubFlowSettingInput!]`](../input_object/flowsubflowsettinginput.md) | The sub-flow settings | +| `signature` | [`String!`](../scalar/string.md) | The sub-flow signature | +| `startingNodeId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The starting node to execute | diff --git a/docs/graphql/input_object/flowsubflowsettinginput.md b/docs/graphql/input_object/flowsubflowsettinginput.md new file mode 100644 index 00000000..521c0c89 --- /dev/null +++ b/docs/graphql/input_object/flowsubflowsettinginput.md @@ -0,0 +1,14 @@ +--- +title: FlowSubFlowSettingInput +--- + +Input type for sub-flow settings + +## Fields + +| Name | Type | Description | +|------|------|-------------| +| `defaultValue` | [`JSON`](../scalar/json.md) | The default value of the sub-flow setting | +| `hidden` | [`Boolean`](../scalar/boolean.md) | Whether the sub-flow setting is hidden | +| `identifier` | [`String!`](../scalar/string.md) | The identifier of the sub-flow setting | +| `optional` | [`Boolean`](../scalar/boolean.md) | Whether the sub-flow setting is optional | diff --git a/docs/graphql/input_object/nodeparameterinput.md b/docs/graphql/input_object/nodeparameterinput.md index 04ec1c62..f04da24f 100644 --- a/docs/graphql/input_object/nodeparameterinput.md +++ b/docs/graphql/input_object/nodeparameterinput.md @@ -8,4 +8,5 @@ Input type for Node parameter | Name | Type | Description | |------|------|-------------| +| `cast` | [`String`](../scalar/string.md) | The cast applied to the parameter | | `value` | [`NodeParameterValueInput!`](../input_object/nodeparametervalueinput.md) | The value of the parameter | diff --git a/docs/graphql/input_object/nodeparametervalueinput.md b/docs/graphql/input_object/nodeparametervalueinput.md index 96e3f693..2ccb1ee0 100644 --- a/docs/graphql/input_object/nodeparametervalueinput.md +++ b/docs/graphql/input_object/nodeparametervalueinput.md @@ -9,5 +9,5 @@ Input type for parameter value | Name | Type | Description | |------|------|-------------| | `literalValue` | [`JSON`](../scalar/json.md) | The literal value of the parameter | -| `nodeFunctionId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The function value of the parameter as an id | | `referenceValue` | [`ReferenceValueInput`](../input_object/referencevalueinput.md) | The reference value of the parameter | +| `subFlow` | [`FlowSubFlowInput`](../input_object/flowsubflowinput.md) | The sub-flow value of the parameter | diff --git a/docs/graphql/object/flowsetting.md b/docs/graphql/object/flowsetting.md index 460bd694..cf517f4c 100644 --- a/docs/graphql/object/flowsetting.md +++ b/docs/graphql/object/flowsetting.md @@ -8,6 +8,7 @@ Represents a flow setting | Name | Type | Description | |------|------|-------------| +| `cast` | [`String`](../scalar/string.md) | The cast applied to the flow setting | | `createdAt` | [`Time!`](../scalar/time.md) | Time when this FlowSetting was created | | `flowSettingIdentifier` | [`String!`](../scalar/string.md) | The identifier of the flow setting | | `id` | [`FlowSettingID!`](../scalar/flowsettingid.md) | Global ID of this FlowSetting | diff --git a/docs/graphql/object/flowsubflow.md b/docs/graphql/object/flowsubflow.md new file mode 100644 index 00000000..9bff668c --- /dev/null +++ b/docs/graphql/object/flowsubflow.md @@ -0,0 +1,14 @@ +--- +title: FlowSubFlow +--- + +Represents a sub-flow parameter value. + +## Fields without arguments + +| Name | Type | Description | +|------|------|-------------| +| `functionIdentifier` | [`String`](../scalar/string.md) | The function identifier to execute. | +| `settings` | [`[FlowSubFlowSetting!]!`](../object/flowsubflowsetting.md) | The sub-flow settings. | +| `signature` | [`String!`](../scalar/string.md) | The sub-flow signature. | +| `startingNodeId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The starting node to execute. | diff --git a/docs/graphql/object/flowsubflowsetting.md b/docs/graphql/object/flowsubflowsetting.md new file mode 100644 index 00000000..2fc41613 --- /dev/null +++ b/docs/graphql/object/flowsubflowsetting.md @@ -0,0 +1,14 @@ +--- +title: FlowSubFlowSetting +--- + +Represents a sub-flow setting. + +## Fields without arguments + +| Name | Type | Description | +|------|------|-------------| +| `defaultValue` | [`JSON`](../scalar/json.md) | The default value of the sub-flow setting. | +| `hidden` | [`Boolean`](../scalar/boolean.md) | Whether the sub-flow setting is hidden. | +| `identifier` | [`String!`](../scalar/string.md) | The identifier of the sub-flow setting. | +| `optional` | [`Boolean`](../scalar/boolean.md) | Whether the sub-flow setting is optional. | diff --git a/docs/graphql/object/nodefunctionidwrapper.md b/docs/graphql/object/nodefunctionidwrapper.md deleted file mode 100644 index a5d139fd..00000000 --- a/docs/graphql/object/nodefunctionidwrapper.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: NodeFunctionIdWrapper ---- - -Represents a Node Function id wrapper. - -## Fields without arguments - -| Name | Type | Description | -|------|------|-------------| -| `id` | [`NodeFunctionID!`](../scalar/nodefunctionid.md) | Global ID of this NodeFunctionIdWrapper | diff --git a/docs/graphql/object/nodeparameter.md b/docs/graphql/object/nodeparameter.md index e08ad1ae..77569f5f 100644 --- a/docs/graphql/object/nodeparameter.md +++ b/docs/graphql/object/nodeparameter.md @@ -8,6 +8,7 @@ Represents a Node parameter | Name | Type | Description | |------|------|-------------| +| `cast` | [`String`](../scalar/string.md) | The cast applied to the parameter | | `createdAt` | [`Time!`](../scalar/time.md) | Time when this NodeParameter was created | | `id` | [`NodeParameterID!`](../scalar/nodeparameterid.md) | Global ID of this NodeParameter | | `parameterDefinition` | [`ParameterDefinition!`](../object/parameterdefinition.md) | The definition of the parameter | diff --git a/docs/graphql/union/nodeparametervalue.md b/docs/graphql/union/nodeparametervalue.md index 1c267c84..97435410 100644 --- a/docs/graphql/union/nodeparametervalue.md +++ b/docs/graphql/union/nodeparametervalue.md @@ -6,6 +6,6 @@ Represents a parameter value for a node. ## Possible types +- [`FlowSubFlow`](../object/flowsubflow.md) - [`LiteralValue`](../object/literalvalue.md) -- [`NodeFunctionIdWrapper`](../object/nodefunctionidwrapper.md) - [`ReferenceValue`](../object/referencevalue.md) diff --git a/spec/factories/node_functions.rb b/spec/factories/node_functions.rb index 9cd825aa..f12be9f1 100644 --- a/spec/factories/node_functions.rb +++ b/spec/factories/node_functions.rb @@ -6,6 +6,5 @@ next_node { nil } node_parameters { [] } flow - value_of_node_parameter { nil } end end diff --git a/spec/factories/node_parameters.rb b/spec/factories/node_parameters.rb index 8d0c86df..eb0831c2 100644 --- a/spec/factories/node_parameters.rb +++ b/spec/factories/node_parameters.rb @@ -6,6 +6,5 @@ node_function literal_value { 'value' } reference_value { nil } - function_value { nil } end end diff --git a/spec/factories/sub_flow_settings.rb b/spec/factories/sub_flow_settings.rb new file mode 100644 index 00000000..7071da42 --- /dev/null +++ b/spec/factories/sub_flow_settings.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :sub_flow_setting do + sub_flow + identifier { 'setting' } + default_value { nil } + optional { nil } + hidden { nil } + end +end diff --git a/spec/factories/sub_flows.rb b/spec/factories/sub_flows.rb new file mode 100644 index 00000000..4793a9bc --- /dev/null +++ b/spec/factories/sub_flows.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :sub_flow do + node_parameter + starting_node factory: :node_function + function_identifier { nil } + signature { '(): VOID' } + end +end diff --git a/spec/models/node_function_spec.rb b/spec/models/node_function_spec.rb index 5aa9c20b..f8ef7194 100644 --- a/spec/models/node_function_spec.rb +++ b/spec/models/node_function_spec.rb @@ -10,13 +10,6 @@ it { is_expected.to belong_to(:next_node).class_name('NodeFunction').optional } it { is_expected.to belong_to(:flow).class_name('Flow') } - it do - is_expected.to belong_to(:value_of_node_parameter) - .class_name('NodeParameter') - .inverse_of(:function_value) - .optional - end - it { is_expected.to have_many(:node_parameters).inverse_of(:node_function) } end diff --git a/spec/models/node_parameter_spec.rb b/spec/models/node_parameter_spec.rb index bb328be1..3e34791d 100644 --- a/spec/models/node_parameter_spec.rb +++ b/spec/models/node_parameter_spec.rb @@ -10,12 +10,7 @@ describe 'associations' do it { is_expected.to belong_to(:parameter_definition).class_name('ParameterDefinition') } it { is_expected.to have_one(:reference_value) } - - it do - is_expected.to have_one(:function_value) - .class_name('NodeFunction') - .inverse_of(:value_of_node_parameter) - end + it { is_expected.to have_one(:sub_flow) } it { is_expected.to belong_to(:node_function).class_name('NodeFunction').inverse_of(:node_parameters) } end @@ -25,20 +20,18 @@ param = build( :node_parameter, literal_value: 1, - reference_value: create(:reference_value), - function_value: nil + reference_value: create(:reference_value) ) expect(param).not_to be_valid expect(param.errors[:value]) - .to include('Only one of literal_value, reference_value, or function_value must be present') + .to include('Only one of literal_value, reference_value, or sub_flow must be present') end it 'allows all values to be empty' do param = build( :node_parameter, literal_value: nil, - reference_value: nil, - function_value: nil + reference_value: nil ) expect(param).to be_valid end diff --git a/spec/models/sub_flow_setting_spec.rb b/spec/models/sub_flow_setting_spec.rb new file mode 100644 index 00000000..75e0370f --- /dev/null +++ b/spec/models/sub_flow_setting_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SubFlowSetting do + subject { create(:sub_flow_setting) } + + describe 'associations' do + it { is_expected.to belong_to(:sub_flow).inverse_of(:sub_flow_settings) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:identifier) } + end +end diff --git a/spec/models/sub_flow_spec.rb b/spec/models/sub_flow_spec.rb new file mode 100644 index 00000000..abe68344 --- /dev/null +++ b/spec/models/sub_flow_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SubFlow do + subject { create(:sub_flow) } + + describe 'associations' do + it { is_expected.to belong_to(:node_parameter).inverse_of(:sub_flow) } + it { is_expected.to belong_to(:starting_node).class_name('NodeFunction').optional } + it { is_expected.to have_many(:sub_flow_settings).inverse_of(:sub_flow) } + end + + describe 'validations' do + it 'requires exactly one execution reference' do + sub_flow = build(:sub_flow, starting_node: nil, function_identifier: nil) + + expect(sub_flow).not_to be_valid + expect(sub_flow.errors[:base]).to include('Exactly one of starting_node or function_identifier must be present') + end + end +end diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb index 5b927b3c..4fb845b7 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb @@ -29,6 +29,10 @@ ...on LiteralValue { value } + ...on FlowSubFlow { + signature + startingNodeId + } ...on ReferenceValue { createdAt id @@ -104,7 +108,10 @@ parameters: [ { value: { - nodeFunctionId: 'gid://sagittarius/NodeFunction/2000', + subFlow: { + startingNodeId: 'gid://sagittarius/NodeFunction/2000', + signature: '(input: INPUT): OUTPUT', + }, }, } ], @@ -201,6 +208,13 @@ :value ) expect(parameter_values).to include(a_hash_including('value' => 100)) + expect(parameter_values).to include( + a_hash_including( + '__typename' => 'FlowSubFlow', + 'signature' => '(input: INPUT): OUTPUT', + 'startingNodeId' => a_string_matching(%r{gid://sagittarius/NodeFunction/\d+}) + ) + ) expect(parameter_values).to include( a_hash_including('referencePath' => [a_hash_including('arrayIndex' => 0, 'path' => 'some.path')]) ) diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb index 5538f286..626e4e9d 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb @@ -29,6 +29,10 @@ ...on LiteralValue { value } + ...on FlowSubFlow { + signature + startingNodeId + } ...on ReferenceValue { createdAt id @@ -110,7 +114,10 @@ parameters: [ { value: { - nodeFunctionId: 'gid://sagittarius/NodeFunction/2000', + subFlow: { + startingNodeId: 'gid://sagittarius/NodeFunction/2000', + signature: '(input: INPUT): OUTPUT', + }, }, } ], @@ -213,6 +220,13 @@ 'value' => 100 ) ) + expect(parameter_values).to include( + a_hash_including( + '__typename' => 'FlowSubFlow', + 'signature' => '(input: INPUT): OUTPUT', + 'startingNodeId' => a_string_matching(%r{gid://sagittarius/NodeFunction/\d+}) + ) + ) expect(parameter_values).to include( a_hash_including( '__typename' => 'ReferenceValue', @@ -309,10 +323,8 @@ node_function: node1, parameter_definition: function_definition.parameter_definitions.first, literal_value: nil) - create(:node_function, - flow: f, - function_definition: function_definition, - value_of_node_parameter: parameter) + node2 = create(:node_function, flow: f, function_definition: function_definition) + create(:sub_flow, node_parameter: parameter, starting_node: node2, signature: '(input: INPUT): OUTPUT') f.starting_node = node1 node1.save! f.save! @@ -360,7 +372,7 @@ end end - context 'when clearing function_value on a reused node' do + context 'when clearing sub_flow on a reused node' do before do stub_allowed_ability(NamespaceProjectPolicy, :update_flow, user: current_user, subject: project) stub_allowed_ability(NamespaceProjectPolicy, :read_namespace_project, user: current_user, subject: project) @@ -373,10 +385,8 @@ node_function: node1, parameter_definition: function_definition.parameter_definitions.first, literal_value: nil) - create(:node_function, - flow: f, - function_definition: function_definition, - value_of_node_parameter: parameter) + node2 = create(:node_function, flow: f, function_definition: function_definition) + create(:sub_flow, node_parameter: parameter, starting_node: node2, signature: '(input: INPUT): OUTPUT') f.starting_node = node1 node1.save! f.save! From c79b48e19ac6858fef4ec53618a5f6a158679b95 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 21 May 2026 21:05:08 +0200 Subject: [PATCH 03/13] fix: corrected migrations --- ...120000_add_tucana_shared_flow_sub_flows.rb | 4 +- db/schema_migrations/20260520120000 | 1 + db/structure.sql | 138 +++++++++--------- 3 files changed, 72 insertions(+), 71 deletions(-) create mode 100644 db/schema_migrations/20260520120000 diff --git a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb index 777f1c09..f3cff265 100644 --- a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb +++ b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb @@ -7,11 +7,11 @@ def change add_column :node_parameters, :cast, :text create_table :sub_flows do |t| - t.references :node_parameter, null: false, foreign_key: { to_table: :node_parameters, on_delete: :cascade } + t.references :node_parameter, null: false, index: { unique: true }, + foreign_key: { to_table: :node_parameters, on_delete: :cascade } t.references :starting_node, null: true, foreign_key: { to_table: :node_functions, on_delete: :restrict } t.text :function_identifier t.text :signature, null: false - t.index :node_parameter_id, unique: true t.check_constraint 'num_nonnulls(starting_node_id, function_identifier) = 1', name: check_constraint_name(:sub_flows, :execution_reference, :one_of) diff --git a/db/schema_migrations/20260520120000 b/db/schema_migrations/20260520120000 new file mode 100644 index 00000000..2c8b40a7 --- /dev/null +++ b/db/schema_migrations/20260520120000 @@ -0,0 +1 @@ +e8ff4064cc4b6989a987836826ce7686d62a62059e30f7fddc8466d3ec893d8f \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 7d70b740..16068bcd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -204,9 +204,9 @@ CREATE TABLE flow_settings ( flow_id bigint NOT NULL, flow_setting_id text NOT NULL, object jsonb NOT NULL, - "cast" text, created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL + updated_at timestamp with time zone NOT NULL, + "cast" text ); CREATE SEQUENCE flow_settings_id_seq @@ -631,10 +631,10 @@ CREATE TABLE node_parameters ( id bigint NOT NULL, node_function_id bigint NOT NULL, literal_value jsonb, - "cast" text, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - parameter_definition_id bigint NOT NULL + parameter_definition_id bigint NOT NULL, + "cast" text ); CREATE SEQUENCE node_parameters_id_seq @@ -722,46 +722,6 @@ CREATE SEQUENCE reference_values_id_seq ALTER SEQUENCE reference_values_id_seq OWNED BY reference_values.id; -CREATE TABLE sub_flow_settings ( - id bigint NOT NULL, - sub_flow_id bigint NOT NULL, - identifier text NOT NULL, - default_value jsonb, - optional boolean, - hidden boolean, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL -); - -CREATE SEQUENCE sub_flow_settings_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - -ALTER SEQUENCE sub_flow_settings_id_seq OWNED BY sub_flow_settings.id; - -CREATE TABLE sub_flows ( - id bigint NOT NULL, - node_parameter_id bigint NOT NULL, - starting_node_id bigint, - function_identifier text, - signature text NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - CONSTRAINT check_sub_flows_execution_reference_one_of CHECK ((num_nonnulls(starting_node_id, function_identifier) = 1)) -); - -CREATE SEQUENCE sub_flows_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - -ALTER SEQUENCE sub_flows_id_seq OWNED BY sub_flows.id; - CREATE TABLE runtime_flow_type_data_type_links ( id bigint NOT NULL, runtime_flow_type_id bigint NOT NULL, @@ -986,6 +946,46 @@ CREATE TABLE schema_migrations ( version character varying NOT NULL ); +CREATE TABLE sub_flow_settings ( + id bigint NOT NULL, + sub_flow_id bigint NOT NULL, + identifier text NOT NULL, + default_value jsonb, + optional boolean, + hidden boolean, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE sub_flow_settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE sub_flow_settings_id_seq OWNED BY sub_flow_settings.id; + +CREATE TABLE sub_flows ( + id bigint NOT NULL, + node_parameter_id bigint NOT NULL, + starting_node_id bigint, + function_identifier text, + signature text NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + CONSTRAINT check_53a99b1dd3 CHECK ((num_nonnulls(starting_node_id, function_identifier) = 1)) +); + +CREATE SEQUENCE sub_flows_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE sub_flows_id_seq OWNED BY sub_flows.id; + CREATE TABLE translations ( id bigint NOT NULL, code text NOT NULL, @@ -1137,10 +1137,6 @@ ALTER TABLE ONLY reference_paths ALTER COLUMN id SET DEFAULT nextval('reference_ ALTER TABLE ONLY reference_values ALTER COLUMN id SET DEFAULT nextval('reference_values_id_seq'::regclass); -ALTER TABLE ONLY sub_flow_settings ALTER COLUMN id SET DEFAULT nextval('sub_flow_settings_id_seq'::regclass); - -ALTER TABLE ONLY sub_flows ALTER COLUMN id SET DEFAULT nextval('sub_flows_id_seq'::regclass); - ALTER TABLE ONLY runtime_flow_type_data_type_links ALTER COLUMN id SET DEFAULT nextval('runtime_flow_type_data_type_links_id_seq'::regclass); ALTER TABLE ONLY runtime_flow_type_settings ALTER COLUMN id SET DEFAULT nextval('runtime_flow_type_settings_id_seq'::regclass); @@ -1161,6 +1157,10 @@ ALTER TABLE ONLY runtime_statuses ALTER COLUMN id SET DEFAULT nextval('runtime_s ALTER TABLE ONLY runtimes ALTER COLUMN id SET DEFAULT nextval('runtimes_id_seq'::regclass); +ALTER TABLE ONLY sub_flow_settings ALTER COLUMN id SET DEFAULT nextval('sub_flow_settings_id_seq'::regclass); + +ALTER TABLE ONLY sub_flows ALTER COLUMN id SET DEFAULT nextval('sub_flows_id_seq'::regclass); + ALTER TABLE ONLY translations ALTER COLUMN id SET DEFAULT nextval('translations_id_seq'::regclass); ALTER TABLE ONLY user_identities ALTER COLUMN id SET DEFAULT nextval('user_identities_id_seq'::regclass); @@ -1286,12 +1286,6 @@ ALTER TABLE ONLY reference_paths ALTER TABLE ONLY reference_values ADD CONSTRAINT reference_values_pkey PRIMARY KEY (id); -ALTER TABLE ONLY sub_flow_settings - ADD CONSTRAINT sub_flow_settings_pkey PRIMARY KEY (id); - -ALTER TABLE ONLY sub_flows - ADD CONSTRAINT sub_flows_pkey PRIMARY KEY (id); - ALTER TABLE ONLY runtime_flow_type_data_type_links ADD CONSTRAINT runtime_flow_type_data_type_links_pkey PRIMARY KEY (id); @@ -1325,6 +1319,12 @@ ALTER TABLE ONLY runtimes ALTER TABLE ONLY schema_migrations ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); +ALTER TABLE ONLY sub_flow_settings + ADD CONSTRAINT sub_flow_settings_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT sub_flows_pkey PRIMARY KEY (id); + ALTER TABLE ONLY translations ADD CONSTRAINT translations_pkey PRIMARY KEY (id); @@ -1509,12 +1509,6 @@ CREATE INDEX index_reference_values_on_node_function_id ON reference_values USIN CREATE INDEX index_reference_values_on_node_parameter_id ON reference_values USING btree (node_parameter_id); -CREATE INDEX index_sub_flow_settings_on_sub_flow_id ON sub_flow_settings USING btree (sub_flow_id); - -CREATE UNIQUE INDEX index_sub_flows_on_node_parameter_id ON sub_flows USING btree (node_parameter_id); - -CREATE INDEX index_sub_flows_on_starting_node_id ON sub_flows USING btree (starting_node_id); - CREATE UNIQUE INDEX index_runtime_flow_types_on_runtime_id_and_identifier ON runtime_flow_types USING btree (runtime_id, identifier); CREATE INDEX index_runtime_status_configurations_on_runtime_status_id ON runtime_status_configurations USING btree (runtime_status_id); @@ -1525,6 +1519,12 @@ CREATE INDEX index_runtimes_on_namespace_id ON runtimes USING btree (namespace_i CREATE UNIQUE INDEX index_runtimes_on_token ON runtimes USING btree (token); +CREATE INDEX index_sub_flow_settings_on_sub_flow_id ON sub_flow_settings USING btree (sub_flow_id); + +CREATE UNIQUE INDEX index_sub_flows_on_node_parameter_id ON sub_flows USING btree (node_parameter_id); + +CREATE INDEX index_sub_flows_on_starting_node_id ON sub_flows USING btree (starting_node_id); + CREATE INDEX index_translations_on_owner ON translations USING btree (owner_type, owner_id); CREATE UNIQUE INDEX index_user_identities_on_provider_id_and_identifier ON user_identities USING btree (provider_id, identifier); @@ -1567,6 +1567,9 @@ ALTER TABLE ONLY function_definitions ALTER TABLE ONLY node_parameters ADD CONSTRAINT fk_rails_2ed7c53167 FOREIGN KEY (parameter_definition_id) REFERENCES parameter_definitions(id) ON DELETE RESTRICT; +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT fk_rails_32ab48790a FOREIGN KEY (node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; + ALTER TABLE ONLY runtime_flow_types ADD CONSTRAINT fk_rails_3675f29c4e FOREIGN KEY (runtime_id) REFERENCES runtimes(id) ON DELETE CASCADE; @@ -1609,6 +1612,9 @@ ALTER TABLE ONLY node_functions ALTER TABLE ONLY backup_codes ADD CONSTRAINT fk_rails_556c1feac3 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY sub_flow_settings + ADD CONSTRAINT fk_rails_55f76c79cc FOREIGN KEY (sub_flow_id) REFERENCES sub_flows(id) ON DELETE CASCADE; + ALTER TABLE ONLY namespace_members ADD CONSTRAINT fk_rails_567f152a62 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; @@ -1669,15 +1675,6 @@ ALTER TABLE ONLY reference_values ALTER TABLE ONLY reference_values ADD CONSTRAINT fk_rails_8c916f07f1 FOREIGN KEY (node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; -ALTER TABLE ONLY sub_flows - ADD CONSTRAINT fk_sub_flows_node_parameter FOREIGN KEY (node_parameter_id) REFERENCES node_parameters(id) ON DELETE CASCADE; - -ALTER TABLE ONLY sub_flows - ADD CONSTRAINT fk_sub_flows_starting_node FOREIGN KEY (starting_node_id) REFERENCES node_functions(id) ON DELETE RESTRICT; - -ALTER TABLE ONLY sub_flow_settings - ADD CONSTRAINT fk_sub_flow_settings_sub_flow FOREIGN KEY (sub_flow_id) REFERENCES sub_flows(id) ON DELETE CASCADE; - ALTER TABLE ONLY data_type_data_type_links ADD CONSTRAINT fk_rails_90fbf0d8ef FOREIGN KEY (data_type_id) REFERENCES data_types(id) ON DELETE CASCADE; @@ -1726,6 +1723,9 @@ ALTER TABLE ONLY flows ALTER TABLE ONLY flow_settings ADD CONSTRAINT fk_rails_da3b2fb3c5 FOREIGN KEY (flow_id) REFERENCES flows(id) ON DELETE CASCADE; +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT fk_rails_e27dd4d82a FOREIGN KEY (starting_node_id) REFERENCES node_functions(id) ON DELETE RESTRICT; + ALTER TABLE ONLY runtime_flow_types ADD CONSTRAINT fk_rails_e729dc57e7 FOREIGN KEY (runtime_module_id) REFERENCES runtime_modules(id) ON DELETE CASCADE; From 9dade74812bada48124a532b59a9d1c40833d950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 24 May 2026 10:24:32 +0200 Subject: [PATCH 04/13] Update app/models/node_parameter.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niklas van Schrick Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- app/models/node_parameter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/node_parameter.rb b/app/models/node_parameter.rb index d807e332..2ea75c13 100644 --- a/app/models/node_parameter.rb +++ b/app/models/node_parameter.rb @@ -5,7 +5,7 @@ class NodeParameter < ApplicationRecord belongs_to :node_function, class_name: 'NodeFunction', inverse_of: :node_parameters has_one :reference_value, autosave: true - has_one :sub_flow, autosave: true, dependent: :destroy + has_one :sub_flow, autosave: true validate :only_one_value_present From 9aec8ee308dd6c3b5346caa4b35aa1e78222fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 24 May 2026 10:24:48 +0200 Subject: [PATCH 05/13] Update app/models/sub_flow.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niklas van Schrick Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- app/models/sub_flow.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/sub_flow.rb b/app/models/sub_flow.rb index ac1bc072..2b9bdbee 100644 --- a/app/models/sub_flow.rb +++ b/app/models/sub_flow.rb @@ -4,7 +4,7 @@ class SubFlow < ApplicationRecord belongs_to :node_parameter, inverse_of: :sub_flow belongs_to :starting_node, class_name: 'NodeFunction', optional: true - has_many :sub_flow_settings, inverse_of: :sub_flow, autosave: true, dependent: :destroy + has_many :sub_flow_settings, inverse_of: :sub_flow, autosave: true validate :validate_execution_reference From c280de181ac9d5b2b753f870cb59e12cf1a272c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 24 May 2026 10:27:12 +0200 Subject: [PATCH 06/13] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niklas van Schrick Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- .../20260520120000_add_tucana_shared_flow_sub_flows.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb index f3cff265..d9d75628 100644 --- a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb +++ b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb @@ -2,9 +2,9 @@ class AddTucanaSharedFlowSubFlows < Code0::ZeroTrack::Database::Migration[1.0] def change - add_column :flow_settings, :cast, :text + add_column :flow_settings, :cast, :text, limit: 500 - add_column :node_parameters, :cast, :text + add_column :node_parameters, :cast, :text, limit: 500 create_table :sub_flows do |t| t.references :node_parameter, null: false, index: { unique: true }, @@ -23,10 +23,8 @@ def change t.references :sub_flow, null: false, foreign_key: { to_table: :sub_flows, on_delete: :cascade } t.text :identifier, null: false t.jsonb :default_value - # rubocop:disable Rails/ThreeStateBooleanColumn -- mirrors proto optional bool presence - t.boolean :optional - t.boolean :hidden - # rubocop:enable Rails/ThreeStateBooleanColumn + t.boolean :optional, null: false, default: false + t.boolean :hidden, null: false, default: false t.timestamps_with_timezone end From 2b3def66595c2ad9ab2f0b8c59addb6ff0be7dbe Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 24 May 2026 10:55:41 +0200 Subject: [PATCH 07/13] feat: added direct reference to function definition --- app/models/sub_flow.rb | 9 ++- .../projects/flows/update_service.rb | 66 ++++++++++-------- ...120000_add_tucana_shared_flow_sub_flows.rb | 4 +- db/structure.sql | 19 +++-- spec/factories/sub_flow_settings.rb | 4 +- spec/factories/sub_flows.rb | 2 +- spec/models/sub_flow_setting_spec.rb | 2 + spec/models/sub_flow_spec.rb | 5 +- .../projects/flows/update_mutation_spec.rb | 69 +++++++++++++++++++ 9 files changed, 138 insertions(+), 42 deletions(-) diff --git a/app/models/sub_flow.rb b/app/models/sub_flow.rb index 2b9bdbee..48dea395 100644 --- a/app/models/sub_flow.rb +++ b/app/models/sub_flow.rb @@ -3,11 +3,16 @@ class SubFlow < ApplicationRecord belongs_to :node_parameter, inverse_of: :sub_flow belongs_to :starting_node, class_name: 'NodeFunction', optional: true + belongs_to :function_definition, optional: true has_many :sub_flow_settings, inverse_of: :sub_flow, autosave: true validate :validate_execution_reference + def function_identifier + function_definition&.identifier + end + def to_grpc Tucana::Shared::SubFlow.new( starting_node_id: starting_node_id, @@ -20,8 +25,8 @@ def to_grpc private def validate_execution_reference - return if [starting_node_id.present?, function_identifier.present?].count(true) == 1 + return if [starting_node_id.present?, function_definition_id.present?].count(true) == 1 - errors.add(:base, 'Exactly one of starting_node or function_identifier must be present') + errors.add(:base, 'Exactly one of starting_node or function_definition must be present') end end diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index 8bfe3f21..497258b7 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -53,14 +53,15 @@ def update_flow_attributes end def update_settings(t) - db_settings = flow.flow_settings.first(flow_input.settings.length) + settings_input = Array(flow_input.settings) + db_settings = flow.flow_settings.first(settings_input.length) flow_type_settings = flow.flow_type.flow_type_settings - flow_input.settings.each_with_index do |setting, index| + settings_input.each_with_index do |setting, index| db_settings[index] ||= flow.flow_settings.build db_settings[index].flow_setting_id = flow_type_settings[index]&.identifier db_settings[index].object = setting.value - db_settings[index].cast = optional_input(setting, :cast) + db_settings[index].cast = setting.try(:cast) next if db_settings[index].save @@ -76,8 +77,6 @@ def update_settings(t) def update_nodes(t) all_nodes = flow.node_functions - - flow_input.starting_node_id node_index = 0 updated_nodes = [] @@ -86,19 +85,13 @@ def update_nodes(t) current_node = all_nodes[node_index] || NodeFunction.new(flow: flow) update_node(t, current_node, node_input) - unless current_node.save - t.rollback_and_return! ServiceResponse.error( - message: 'Invalid node', - error_code: :invalid_node_function, - details: current_node.errors - ) - end - updated_nodes << { node: current_node, input: node_input } node_index += 1 end + persist_new_nodes(t, updated_nodes) + updated_nodes.each do |node| update_node_parameters(t, node[:node], node[:input], updated_nodes) update_next_node(t, node[:node], node[:input], updated_nodes) @@ -122,6 +115,19 @@ def update_nodes(t) delete_old_nodes(t, all_nodes.reject { |node| updated_nodes.pluck(:node).pluck(:id).include?(node.id) }) end + def persist_new_nodes(t, updated_nodes) + updated_nodes.each do |node| + next unless node[:node].new_record? + next if node[:node].save(validate: false) + + t.rollback_and_return! ServiceResponse.error( + message: 'Invalid node', + error_code: :invalid_node_function, + details: node[:node].errors + ) + end + end + def update_starting_node(t, all_nodes) starting_node = all_nodes.find { |n| n[:input].id == flow_input.starting_node_id } @@ -190,11 +196,11 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) end db_parameters[index].parameter_definition = parameter_definition - db_parameters[index].cast = optional_input(parameter, :cast) + db_parameters[index].cast = parameter.try(:cast) db_parameters[index].literal_value = parameter.value.literal_value - if optional_input(parameter.value, :sub_flow).present? + if parameter.value.try(:sub_flow).present? update_sub_flow(t, db_parameters[index], parameter.value.sub_flow, all_nodes) else db_parameters[index].sub_flow&.destroy @@ -265,8 +271,9 @@ def create_audit_event def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) starting_node_id = nil + function_definition = nil - if optional_input(sub_flow_input, :starting_node_id).present? + if sub_flow_input.starting_node_id.present? starting_node = all_nodes.find { |n| n[:input].id == sub_flow_input.starting_node_id } if starting_node.nil? @@ -277,36 +284,41 @@ def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) end starting_node_id = starting_node[:node].id + elsif sub_flow_input.function_identifier.present? + function_definition = flow.project.primary_runtime.function_definitions.find_by( + identifier: sub_flow_input.function_identifier + ) + + if function_definition.nil? + t.rollback_and_return! ServiceResponse.error( + message: 'Sub-flow function not found', + error_code: :invalid_function_id + ) + end end sub_flow = node_parameter.sub_flow || node_parameter.build_sub_flow sub_flow.assign_attributes( starting_node_id: starting_node_id, - function_identifier: optional_input(sub_flow_input, :function_identifier), + function_definition: function_definition, signature: sub_flow_input.signature ) - sub_flow_settings_input = Array(optional_input(sub_flow_input, :settings)) + sub_flow_settings_input = Array(sub_flow_input.try(:settings)) sub_flow_settings = sub_flow.sub_flow_settings.first(sub_flow_settings_input.length) sub_flow_settings_input.each_with_index do |setting, index| sub_flow_settings[index] ||= sub_flow.sub_flow_settings.build sub_flow_settings[index].assign_attributes( identifier: setting.identifier, - default_value: optional_input(setting, :default_value), - optional: optional_input(setting, :optional), - hidden: optional_input(setting, :hidden) + default_value: setting.try(:default_value), + optional: setting.try(:optional), + hidden: setting.try(:hidden) ) end (sub_flow.sub_flow_settings - sub_flow_settings).each(&:destroy) end - - def optional_input(input, attribute) - return unless input.respond_to?(attribute) - - input.public_send(attribute) - end end end end diff --git a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb index d9d75628..7ef077f2 100644 --- a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb +++ b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb @@ -10,10 +10,10 @@ def change t.references :node_parameter, null: false, index: { unique: true }, foreign_key: { to_table: :node_parameters, on_delete: :cascade } t.references :starting_node, null: true, foreign_key: { to_table: :node_functions, on_delete: :restrict } - t.text :function_identifier + t.references :function_definition, null: true, foreign_key: { on_delete: :restrict } t.text :signature, null: false - t.check_constraint 'num_nonnulls(starting_node_id, function_identifier) = 1', + t.check_constraint 'num_nonnulls(starting_node_id, function_definition_id) = 1', name: check_constraint_name(:sub_flows, :execution_reference, :one_of) t.timestamps_with_timezone diff --git a/db/structure.sql b/db/structure.sql index 16068bcd..53f5ae69 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -206,7 +206,8 @@ CREATE TABLE flow_settings ( object jsonb NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - "cast" text + "cast" text, + CONSTRAINT check_65f98666ae CHECK ((char_length("cast") <= 500)) ); CREATE SEQUENCE flow_settings_id_seq @@ -634,7 +635,8 @@ CREATE TABLE node_parameters ( created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, parameter_definition_id bigint NOT NULL, - "cast" text + "cast" text, + CONSTRAINT check_6439c80497 CHECK ((char_length("cast") <= 500)) ); CREATE SEQUENCE node_parameters_id_seq @@ -951,8 +953,8 @@ CREATE TABLE sub_flow_settings ( sub_flow_id bigint NOT NULL, identifier text NOT NULL, default_value jsonb, - optional boolean, - hidden boolean, + optional boolean DEFAULT false NOT NULL, + hidden boolean DEFAULT false NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL ); @@ -970,11 +972,11 @@ CREATE TABLE sub_flows ( id bigint NOT NULL, node_parameter_id bigint NOT NULL, starting_node_id bigint, - function_identifier text, + function_definition_id bigint, signature text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - CONSTRAINT check_53a99b1dd3 CHECK ((num_nonnulls(starting_node_id, function_identifier) = 1)) + CONSTRAINT check_53a99b1dd3 CHECK ((num_nonnulls(starting_node_id, function_definition_id) = 1)) ); CREATE SEQUENCE sub_flows_id_seq @@ -1521,6 +1523,8 @@ CREATE UNIQUE INDEX index_runtimes_on_token ON runtimes USING btree (token); CREATE INDEX index_sub_flow_settings_on_sub_flow_id ON sub_flow_settings USING btree (sub_flow_id); +CREATE INDEX index_sub_flows_on_function_definition_id ON sub_flows USING btree (function_definition_id); + CREATE UNIQUE INDEX index_sub_flows_on_node_parameter_id ON sub_flows USING btree (node_parameter_id); CREATE INDEX index_sub_flows_on_starting_node_id ON sub_flows USING btree (starting_node_id); @@ -1690,6 +1694,9 @@ ALTER TABLE ONLY user_sessions ALTER TABLE ONLY namespace_members ADD CONSTRAINT fk_rails_a0a760b9b4 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY sub_flows + ADD CONSTRAINT fk_rails_a99aa3478f FOREIGN KEY (function_definition_id) REFERENCES function_definitions(id) ON DELETE RESTRICT; + ALTER TABLE ONLY flows ADD CONSTRAINT fk_rails_ab927e0ecb FOREIGN KEY (project_id) REFERENCES namespace_projects(id) ON DELETE CASCADE; diff --git a/spec/factories/sub_flow_settings.rb b/spec/factories/sub_flow_settings.rb index 7071da42..89587658 100644 --- a/spec/factories/sub_flow_settings.rb +++ b/spec/factories/sub_flow_settings.rb @@ -5,7 +5,7 @@ sub_flow identifier { 'setting' } default_value { nil } - optional { nil } - hidden { nil } + optional { false } + hidden { false } end end diff --git a/spec/factories/sub_flows.rb b/spec/factories/sub_flows.rb index 4793a9bc..b32eeb9b 100644 --- a/spec/factories/sub_flows.rb +++ b/spec/factories/sub_flows.rb @@ -4,7 +4,7 @@ factory :sub_flow do node_parameter starting_node factory: :node_function - function_identifier { nil } + function_definition { nil } signature { '(): VOID' } end end diff --git a/spec/models/sub_flow_setting_spec.rb b/spec/models/sub_flow_setting_spec.rb index 75e0370f..1566609a 100644 --- a/spec/models/sub_flow_setting_spec.rb +++ b/spec/models/sub_flow_setting_spec.rb @@ -10,6 +10,8 @@ end describe 'validations' do + it { is_expected.to allow_values(true, false).for(:optional) } + it { is_expected.to allow_values(true, false).for(:hidden) } it { is_expected.to validate_presence_of(:identifier) } end end diff --git a/spec/models/sub_flow_spec.rb b/spec/models/sub_flow_spec.rb index abe68344..4d3f4870 100644 --- a/spec/models/sub_flow_spec.rb +++ b/spec/models/sub_flow_spec.rb @@ -8,15 +8,16 @@ describe 'associations' do it { is_expected.to belong_to(:node_parameter).inverse_of(:sub_flow) } it { is_expected.to belong_to(:starting_node).class_name('NodeFunction').optional } + it { is_expected.to belong_to(:function_definition).optional } it { is_expected.to have_many(:sub_flow_settings).inverse_of(:sub_flow) } end describe 'validations' do it 'requires exactly one execution reference' do - sub_flow = build(:sub_flow, starting_node: nil, function_identifier: nil) + sub_flow = build(:sub_flow, starting_node: nil, function_definition: nil) expect(sub_flow).not_to be_valid - expect(sub_flow.errors[:base]).to include('Exactly one of starting_node or function_identifier must be present') + expect(sub_flow.errors[:base]).to include('Exactly one of starting_node or function_definition must be present') end end end diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb index 626e4e9d..658bb4b4 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb @@ -30,6 +30,7 @@ value } ...on FlowSubFlow { + functionIdentifier signature startingNodeId } @@ -257,6 +258,74 @@ end end + context 'when updating a sub-flow by function identifier' do + before do + stub_allowed_ability(NamespaceProjectPolicy, :update_flow, user: current_user, subject: project) + stub_allowed_ability(NamespaceProjectPolicy, :read_namespace_project, user: current_user, subject: project) + end + + let(:input) do + { + flowId: flow.to_global_id.to_s, + flowInput: { + name: generate(:flow_name), + type: flow_type.to_global_id.to_s, + startingNodeId: flow.starting_node.to_global_id.to_s, + settings: [], + nodes: [ + { + id: flow.starting_node.to_global_id.to_s, + functionDefinitionId: function_definition.to_global_id.to_s, + nextNodeId: nil, + parameters: [ + { + value: { + subFlow: { + functionIdentifier: function_definition.identifier, + signature: '(input: INPUT): OUTPUT', + }, + }, + } + ], + } + ], + }, + } + end + + let(:flow) do + create(:flow, project: project, flow_type: flow_type).tap do |f| + node = create(:node_function, flow: f, function_definition: function_definition) + create( + :node_parameter, + node_function: node, + parameter_definition: function_definition.parameter_definitions.first, + literal_value: nil + ) + f.starting_node = node + f.save! + end + end + + it 'stores the referenced function definition on the sub-flow' do + mutate! + + expect(graphql_data_at(:namespaces_projects_flows_update, :errors)).to be_blank + + parameter_value = graphql_data_at(:namespaces_projects_flows_update, :flow, :nodes, :nodes) + .first['parameters']['nodes'].first['value'] + + expect(parameter_value).to include( + '__typename' => 'FlowSubFlow', + 'functionIdentifier' => function_definition.identifier, + 'startingNodeId' => nil + ) + + sub_flow = flow.reload.starting_node.node_parameters.first.sub_flow + expect(sub_flow.function_definition).to eq(function_definition) + end + end + context 'when removing nodes' do before do stub_allowed_ability(NamespaceProjectPolicy, :update_flow, user: current_user, subject: project) From d47a3abd2c0caea1a1c369525491df688523f675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 24 May 2026 11:24:25 +0200 Subject: [PATCH 08/13] Potential fix for pull request finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- app/services/namespaces/projects/flows/update_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index 497258b7..90d91b66 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -312,8 +312,8 @@ def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) sub_flow_settings[index].assign_attributes( identifier: setting.identifier, default_value: setting.try(:default_value), - optional: setting.try(:optional), - hidden: setting.try(:hidden) + optional: setting.try(:optional) || false, + hidden: setting.try(:hidden) || false ) end From 6fe1a76406fb0cd730bad630591f2df631148122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Mon, 25 May 2026 14:59:01 +0200 Subject: [PATCH 09/13] Update db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niklas van Schrick Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb index 7ef077f2..6f89f26a 100644 --- a/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb +++ b/db/migrate/20260520120000_add_tucana_shared_flow_sub_flows.rb @@ -11,7 +11,7 @@ def change foreign_key: { to_table: :node_parameters, on_delete: :cascade } t.references :starting_node, null: true, foreign_key: { to_table: :node_functions, on_delete: :restrict } t.references :function_definition, null: true, foreign_key: { on_delete: :restrict } - t.text :signature, null: false + t.text :signature, null: false, limit: 500 t.check_constraint 'num_nonnulls(starting_node_id, function_definition_id) = 1', name: check_constraint_name(:sub_flows, :execution_reference, :one_of) From f6ed328cf92fa56bdbf31d2eb1e49e3e982f013b Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 25 May 2026 15:00:22 +0200 Subject: [PATCH 10/13] feat: correct sql schema --- db/structure.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/structure.sql b/db/structure.sql index 53f5ae69..911d5dd1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -976,7 +976,8 @@ CREATE TABLE sub_flows ( signature text NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - CONSTRAINT check_53a99b1dd3 CHECK ((num_nonnulls(starting_node_id, function_definition_id) = 1)) + CONSTRAINT check_53a99b1dd3 CHECK ((num_nonnulls(starting_node_id, function_definition_id) = 1)), + CONSTRAINT check_943d01babb CHECK ((char_length(signature) <= 500)) ); CREATE SEQUENCE sub_flows_id_seq From 078613c72512ac716a3be70581f18fdff4fbb938 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 25 May 2026 15:11:56 +0200 Subject: [PATCH 11/13] feat: direct references for function definition --- app/graphql/types/flow_sub_flow_type.rb | 4 +-- app/models/sub_flow.rb | 2 +- app/models/sub_flow_setting.rb | 2 +- .../projects/flows/update_service.rb | 25 ++++--------------- spec/factories/sub_flows.rb | 2 +- .../projects/flows/update_mutation_spec.rb | 10 ++++++-- 6 files changed, 18 insertions(+), 27 deletions(-) diff --git a/app/graphql/types/flow_sub_flow_type.rb b/app/graphql/types/flow_sub_flow_type.rb index ec743081..d0a8c016 100644 --- a/app/graphql/types/flow_sub_flow_type.rb +++ b/app/graphql/types/flow_sub_flow_type.rb @@ -4,9 +4,9 @@ module Types class FlowSubFlowType < Types::BaseObject description 'Represents a sub-flow parameter value.' - field :function_identifier, String, + field :function_definition, Types::FunctionDefinitionType, null: true, - description: 'The function identifier to execute.' + description: 'The resolved function definition to execute.' field :settings, [Types::FlowSubFlowSettingType], method: :sub_flow_settings, null: false, diff --git a/app/models/sub_flow.rb b/app/models/sub_flow.rb index 48dea395..12d4b974 100644 --- a/app/models/sub_flow.rb +++ b/app/models/sub_flow.rb @@ -25,7 +25,7 @@ def to_grpc private def validate_execution_reference - return if [starting_node_id.present?, function_definition_id.present?].count(true) == 1 + return if [starting_node.present?, function_definition.present?].count(true) == 1 errors.add(:base, 'Exactly one of starting_node or function_definition must be present') end diff --git a/app/models/sub_flow_setting.rb b/app/models/sub_flow_setting.rb index c3545e47..15659a27 100644 --- a/app/models/sub_flow_setting.rb +++ b/app/models/sub_flow_setting.rb @@ -8,7 +8,7 @@ class SubFlowSetting < ApplicationRecord def to_grpc Tucana::Shared::SubFlowSetting.new( identifier: identifier, - default_value: default_value.nil? ? nil : Tucana::Shared::Value.from_ruby(default_value), + default_value: Tucana::Shared::Value.from_ruby(default_value), optional: optional, hidden: hidden ) diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index 90d91b66..bed2c683 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -90,8 +90,6 @@ def update_nodes(t) node_index += 1 end - persist_new_nodes(t, updated_nodes) - updated_nodes.each do |node| update_node_parameters(t, node[:node], node[:input], updated_nodes) update_next_node(t, node[:node], node[:input], updated_nodes) @@ -115,19 +113,6 @@ def update_nodes(t) delete_old_nodes(t, all_nodes.reject { |node| updated_nodes.pluck(:node).pluck(:id).include?(node.id) }) end - def persist_new_nodes(t, updated_nodes) - updated_nodes.each do |node| - next unless node[:node].new_record? - next if node[:node].save(validate: false) - - t.rollback_and_return! ServiceResponse.error( - message: 'Invalid node', - error_code: :invalid_node_function, - details: node[:node].errors - ) - end - end - def update_starting_node(t, all_nodes) starting_node = all_nodes.find { |n| n[:input].id == flow_input.starting_node_id } @@ -270,20 +255,20 @@ def create_audit_event end def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) - starting_node_id = nil + starting_node = nil function_definition = nil if sub_flow_input.starting_node_id.present? - starting_node = all_nodes.find { |n| n[:input].id == sub_flow_input.starting_node_id } + starting_node_reference = all_nodes.find { |n| n[:input].id == sub_flow_input.starting_node_id } - if starting_node.nil? + if starting_node_reference.nil? t.rollback_and_return! ServiceResponse.error( message: 'Sub-flow starting node not found', error_code: :node_not_found ) end - starting_node_id = starting_node[:node].id + starting_node = starting_node_reference[:node] elsif sub_flow_input.function_identifier.present? function_definition = flow.project.primary_runtime.function_definitions.find_by( identifier: sub_flow_input.function_identifier @@ -299,7 +284,7 @@ def update_sub_flow(t, node_parameter, sub_flow_input, all_nodes) sub_flow = node_parameter.sub_flow || node_parameter.build_sub_flow sub_flow.assign_attributes( - starting_node_id: starting_node_id, + starting_node: starting_node, function_definition: function_definition, signature: sub_flow_input.signature ) diff --git a/spec/factories/sub_flows.rb b/spec/factories/sub_flows.rb index b32eeb9b..3c848352 100644 --- a/spec/factories/sub_flows.rb +++ b/spec/factories/sub_flows.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :sub_flow do - node_parameter + node_parameter { association :node_parameter, literal_value: nil, reference_value: nil } starting_node factory: :node_function function_definition { nil } signature { '(): VOID' } diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb index 658bb4b4..0ec5642d 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb @@ -30,7 +30,10 @@ value } ...on FlowSubFlow { - functionIdentifier + functionDefinition { + id + identifier + } signature startingNodeId } @@ -317,9 +320,12 @@ expect(parameter_value).to include( '__typename' => 'FlowSubFlow', - 'functionIdentifier' => function_definition.identifier, 'startingNodeId' => nil ) + expect(parameter_value['functionDefinition']).to include( + 'id' => function_definition.to_global_id.to_s, + 'identifier' => function_definition.identifier + ) sub_flow = flow.reload.starting_node.node_parameters.first.sub_flow expect(sub_flow.function_definition).to eq(function_definition) From 16793127688cbf54931c2c234e865da2542598cd Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 25 May 2026 15:12:58 +0200 Subject: [PATCH 12/13] docs: regen graphql docs --- docs/graphql/object/flowsubflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/graphql/object/flowsubflow.md b/docs/graphql/object/flowsubflow.md index 9bff668c..f1ac2b94 100644 --- a/docs/graphql/object/flowsubflow.md +++ b/docs/graphql/object/flowsubflow.md @@ -8,7 +8,7 @@ Represents a sub-flow parameter value. | Name | Type | Description | |------|------|-------------| -| `functionIdentifier` | [`String`](../scalar/string.md) | The function identifier to execute. | +| `functionDefinition` | [`FunctionDefinition`](../object/functiondefinition.md) | The resolved function definition to execute. | | `settings` | [`[FlowSubFlowSetting!]!`](../object/flowsubflowsetting.md) | The sub-flow settings. | | `signature` | [`String!`](../scalar/string.md) | The sub-flow signature. | | `startingNodeId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The starting node to execute. | From b74a1816c5067767e3a0a44936010320b42d9033 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 25 May 2026 16:54:20 +0200 Subject: [PATCH 13/13] ref: renamed sub flow objects --- .../types/input/node_parameter_value_input_type.rb | 4 ++-- ...flow_input_type.rb => sub_flow_value_input_type.rb} | 4 ++-- ...ut_type.rb => sub_flow_value_setting_input_type.rb} | 2 +- app/graphql/types/node_parameter_value_type.rb | 4 ++-- ..._setting_type.rb => sub_flow_value_setting_type.rb} | 2 +- .../{flow_sub_flow_type.rb => sub_flow_value_type.rb} | 4 ++-- .../namespaces/projects/flows/update_service.rb | 4 ++-- docs/graphql/input_object/nodeparametervalueinput.md | 2 +- .../{flowsubflowinput.md => subflowvalueinput.md} | 4 ++-- ...flowsettinginput.md => subflowvaluesettinginput.md} | 2 +- .../graphql/object/{flowsubflow.md => subflowvalue.md} | 4 ++-- .../{flowsubflowsetting.md => subflowvaluesetting.md} | 2 +- docs/graphql/union/nodeparametervalue.md | 2 +- .../namespace/projects/flows/create_mutation_spec.rb | 6 +++--- .../namespace/projects/flows/update_mutation_spec.rb | 10 +++++----- 15 files changed, 28 insertions(+), 28 deletions(-) rename app/graphql/types/input/{flow_sub_flow_input_type.rb => sub_flow_value_input_type.rb} (84%) rename app/graphql/types/input/{flow_sub_flow_setting_input_type.rb => sub_flow_value_setting_input_type.rb} (91%) rename app/graphql/types/{flow_sub_flow_setting_type.rb => sub_flow_value_setting_type.rb} (91%) rename app/graphql/types/{flow_sub_flow_type.rb => sub_flow_value_type.rb} (87%) rename docs/graphql/input_object/{flowsubflowinput.md => subflowvalueinput.md} (73%) rename docs/graphql/input_object/{flowsubflowsettinginput.md => subflowvaluesettinginput.md} (93%) rename docs/graphql/object/{flowsubflow.md => subflowvalue.md} (78%) rename docs/graphql/object/{flowsubflowsetting.md => subflowvaluesetting.md} (94%) diff --git a/app/graphql/types/input/node_parameter_value_input_type.rb b/app/graphql/types/input/node_parameter_value_input_type.rb index 59cd1157..1e5a26de 100644 --- a/app/graphql/types/input/node_parameter_value_input_type.rb +++ b/app/graphql/types/input/node_parameter_value_input_type.rb @@ -9,10 +9,10 @@ class NodeParameterValueInputType < Types::BaseInputObject required: false, description: 'The literal value of the parameter' argument :reference_value, Types::Input::ReferenceValueInputType, required: false, description: 'The reference value of the parameter' - argument :sub_flow, Types::Input::FlowSubFlowInputType, + argument :sub_flow_value, Types::Input::SubFlowValueInputType, required: false, description: 'The sub-flow value of the parameter' - require_one_of %i[literal_value reference_value sub_flow] + require_one_of %i[literal_value reference_value sub_flow_value] end end end diff --git a/app/graphql/types/input/flow_sub_flow_input_type.rb b/app/graphql/types/input/sub_flow_value_input_type.rb similarity index 84% rename from app/graphql/types/input/flow_sub_flow_input_type.rb rename to app/graphql/types/input/sub_flow_value_input_type.rb index 43a82670..81a4b3d4 100644 --- a/app/graphql/types/input/flow_sub_flow_input_type.rb +++ b/app/graphql/types/input/sub_flow_value_input_type.rb @@ -2,13 +2,13 @@ module Types module Input - class FlowSubFlowInputType < Types::BaseInputObject + class SubFlowValueInputType < Types::BaseInputObject description 'Input type for sub-flow parameter values' argument :function_identifier, String, required: false, description: 'The function identifier to execute' - argument :settings, [Types::Input::FlowSubFlowSettingInputType], + argument :settings, [Types::Input::SubFlowValueSettingInputType], required: false, description: 'The sub-flow settings' argument :signature, String, diff --git a/app/graphql/types/input/flow_sub_flow_setting_input_type.rb b/app/graphql/types/input/sub_flow_value_setting_input_type.rb similarity index 91% rename from app/graphql/types/input/flow_sub_flow_setting_input_type.rb rename to app/graphql/types/input/sub_flow_value_setting_input_type.rb index 14cbe64d..976698f2 100644 --- a/app/graphql/types/input/flow_sub_flow_setting_input_type.rb +++ b/app/graphql/types/input/sub_flow_value_setting_input_type.rb @@ -2,7 +2,7 @@ module Types module Input - class FlowSubFlowSettingInputType < Types::BaseInputObject + class SubFlowValueSettingInputType < Types::BaseInputObject description 'Input type for sub-flow settings' argument :default_value, GraphQL::Types::JSON, diff --git a/app/graphql/types/node_parameter_value_type.rb b/app/graphql/types/node_parameter_value_type.rb index aa6f83da..d9ee8558 100644 --- a/app/graphql/types/node_parameter_value_type.rb +++ b/app/graphql/types/node_parameter_value_type.rb @@ -4,7 +4,7 @@ module Types class NodeParameterValueType < Types::BaseUnion description 'Represents a parameter value for a node.' - possible_types Types::FlowSubFlowType, Types::LiteralValueType, Types::ReferenceValueType, + possible_types Types::SubFlowValueType, Types::LiteralValueType, Types::ReferenceValueType, description: 'The value can be a literal, a reference, or a sub-flow.' def self.resolve_type(object, _context) @@ -12,7 +12,7 @@ def self.resolve_type(object, _context) when ReferenceValue Types::ReferenceValueType when SubFlow - Types::FlowSubFlowType + Types::SubFlowValueType else Types::LiteralValueType end diff --git a/app/graphql/types/flow_sub_flow_setting_type.rb b/app/graphql/types/sub_flow_value_setting_type.rb similarity index 91% rename from app/graphql/types/flow_sub_flow_setting_type.rb rename to app/graphql/types/sub_flow_value_setting_type.rb index bc80d8f9..20715e6b 100644 --- a/app/graphql/types/flow_sub_flow_setting_type.rb +++ b/app/graphql/types/sub_flow_value_setting_type.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Types - class FlowSubFlowSettingType < Types::BaseObject + class SubFlowValueSettingType < Types::BaseObject description 'Represents a sub-flow setting.' field :default_value, GraphQL::Types::JSON, diff --git a/app/graphql/types/flow_sub_flow_type.rb b/app/graphql/types/sub_flow_value_type.rb similarity index 87% rename from app/graphql/types/flow_sub_flow_type.rb rename to app/graphql/types/sub_flow_value_type.rb index d0a8c016..a264493c 100644 --- a/app/graphql/types/flow_sub_flow_type.rb +++ b/app/graphql/types/sub_flow_value_type.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module Types - class FlowSubFlowType < Types::BaseObject + class SubFlowValueType < Types::BaseObject description 'Represents a sub-flow parameter value.' field :function_definition, Types::FunctionDefinitionType, null: true, description: 'The resolved function definition to execute.' - field :settings, [Types::FlowSubFlowSettingType], + field :settings, [Types::SubFlowValueSettingType], method: :sub_flow_settings, null: false, description: 'The sub-flow settings.' diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index bed2c683..639b9b65 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -185,8 +185,8 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) db_parameters[index].literal_value = parameter.value.literal_value - if parameter.value.try(:sub_flow).present? - update_sub_flow(t, db_parameters[index], parameter.value.sub_flow, all_nodes) + if parameter.value.try(:sub_flow_value).present? + update_sub_flow(t, db_parameters[index], parameter.value.sub_flow_value, all_nodes) else db_parameters[index].sub_flow&.destroy db_parameters[index].sub_flow = nil diff --git a/docs/graphql/input_object/nodeparametervalueinput.md b/docs/graphql/input_object/nodeparametervalueinput.md index 2ccb1ee0..8a4ca5c3 100644 --- a/docs/graphql/input_object/nodeparametervalueinput.md +++ b/docs/graphql/input_object/nodeparametervalueinput.md @@ -10,4 +10,4 @@ Input type for parameter value |------|------|-------------| | `literalValue` | [`JSON`](../scalar/json.md) | The literal value of the parameter | | `referenceValue` | [`ReferenceValueInput`](../input_object/referencevalueinput.md) | The reference value of the parameter | -| `subFlow` | [`FlowSubFlowInput`](../input_object/flowsubflowinput.md) | The sub-flow value of the parameter | +| `subFlowValue` | [`SubFlowValueInput`](../input_object/subflowvalueinput.md) | The sub-flow value of the parameter | diff --git a/docs/graphql/input_object/flowsubflowinput.md b/docs/graphql/input_object/subflowvalueinput.md similarity index 73% rename from docs/graphql/input_object/flowsubflowinput.md rename to docs/graphql/input_object/subflowvalueinput.md index c853ee22..376e7cf8 100644 --- a/docs/graphql/input_object/flowsubflowinput.md +++ b/docs/graphql/input_object/subflowvalueinput.md @@ -1,5 +1,5 @@ --- -title: FlowSubFlowInput +title: SubFlowValueInput --- Input type for sub-flow parameter values @@ -9,6 +9,6 @@ Input type for sub-flow parameter values | Name | Type | Description | |------|------|-------------| | `functionIdentifier` | [`String`](../scalar/string.md) | The function identifier to execute | -| `settings` | [`[FlowSubFlowSettingInput!]`](../input_object/flowsubflowsettinginput.md) | The sub-flow settings | +| `settings` | [`[SubFlowValueSettingInput!]`](../input_object/subflowvaluesettinginput.md) | The sub-flow settings | | `signature` | [`String!`](../scalar/string.md) | The sub-flow signature | | `startingNodeId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The starting node to execute | diff --git a/docs/graphql/input_object/flowsubflowsettinginput.md b/docs/graphql/input_object/subflowvaluesettinginput.md similarity index 93% rename from docs/graphql/input_object/flowsubflowsettinginput.md rename to docs/graphql/input_object/subflowvaluesettinginput.md index 521c0c89..2620157b 100644 --- a/docs/graphql/input_object/flowsubflowsettinginput.md +++ b/docs/graphql/input_object/subflowvaluesettinginput.md @@ -1,5 +1,5 @@ --- -title: FlowSubFlowSettingInput +title: SubFlowValueSettingInput --- Input type for sub-flow settings diff --git a/docs/graphql/object/flowsubflow.md b/docs/graphql/object/subflowvalue.md similarity index 78% rename from docs/graphql/object/flowsubflow.md rename to docs/graphql/object/subflowvalue.md index f1ac2b94..628226c0 100644 --- a/docs/graphql/object/flowsubflow.md +++ b/docs/graphql/object/subflowvalue.md @@ -1,5 +1,5 @@ --- -title: FlowSubFlow +title: SubFlowValue --- Represents a sub-flow parameter value. @@ -9,6 +9,6 @@ Represents a sub-flow parameter value. | Name | Type | Description | |------|------|-------------| | `functionDefinition` | [`FunctionDefinition`](../object/functiondefinition.md) | The resolved function definition to execute. | -| `settings` | [`[FlowSubFlowSetting!]!`](../object/flowsubflowsetting.md) | The sub-flow settings. | +| `settings` | [`[SubFlowValueSetting!]!`](../object/subflowvaluesetting.md) | The sub-flow settings. | | `signature` | [`String!`](../scalar/string.md) | The sub-flow signature. | | `startingNodeId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The starting node to execute. | diff --git a/docs/graphql/object/flowsubflowsetting.md b/docs/graphql/object/subflowvaluesetting.md similarity index 94% rename from docs/graphql/object/flowsubflowsetting.md rename to docs/graphql/object/subflowvaluesetting.md index 2fc41613..e15a3092 100644 --- a/docs/graphql/object/flowsubflowsetting.md +++ b/docs/graphql/object/subflowvaluesetting.md @@ -1,5 +1,5 @@ --- -title: FlowSubFlowSetting +title: SubFlowValueSetting --- Represents a sub-flow setting. diff --git a/docs/graphql/union/nodeparametervalue.md b/docs/graphql/union/nodeparametervalue.md index 97435410..1db6d003 100644 --- a/docs/graphql/union/nodeparametervalue.md +++ b/docs/graphql/union/nodeparametervalue.md @@ -6,6 +6,6 @@ Represents a parameter value for a node. ## Possible types -- [`FlowSubFlow`](../object/flowsubflow.md) - [`LiteralValue`](../object/literalvalue.md) - [`ReferenceValue`](../object/referencevalue.md) +- [`SubFlowValue`](../object/subflowvalue.md) diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb index 4fb845b7..45dec25b 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/create_mutation_spec.rb @@ -29,7 +29,7 @@ ...on LiteralValue { value } - ...on FlowSubFlow { + ...on SubFlowValue { signature startingNodeId } @@ -108,7 +108,7 @@ parameters: [ { value: { - subFlow: { + subFlowValue: { startingNodeId: 'gid://sagittarius/NodeFunction/2000', signature: '(input: INPUT): OUTPUT', }, @@ -210,7 +210,7 @@ expect(parameter_values).to include(a_hash_including('value' => 100)) expect(parameter_values).to include( a_hash_including( - '__typename' => 'FlowSubFlow', + '__typename' => 'SubFlowValue', 'signature' => '(input: INPUT): OUTPUT', 'startingNodeId' => a_string_matching(%r{gid://sagittarius/NodeFunction/\d+}) ) diff --git a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb index 0ec5642d..734dda7d 100644 --- a/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb +++ b/spec/requests/graphql/mutation/namespace/projects/flows/update_mutation_spec.rb @@ -29,7 +29,7 @@ ...on LiteralValue { value } - ...on FlowSubFlow { + ...on SubFlowValue { functionDefinition { id identifier @@ -118,7 +118,7 @@ parameters: [ { value: { - subFlow: { + subFlowValue: { startingNodeId: 'gid://sagittarius/NodeFunction/2000', signature: '(input: INPUT): OUTPUT', }, @@ -226,7 +226,7 @@ ) expect(parameter_values).to include( a_hash_including( - '__typename' => 'FlowSubFlow', + '__typename' => 'SubFlowValue', 'signature' => '(input: INPUT): OUTPUT', 'startingNodeId' => a_string_matching(%r{gid://sagittarius/NodeFunction/\d+}) ) @@ -283,7 +283,7 @@ parameters: [ { value: { - subFlow: { + subFlowValue: { functionIdentifier: function_definition.identifier, signature: '(input: INPUT): OUTPUT', }, @@ -319,7 +319,7 @@ .first['parameters']['nodes'].first['value'] expect(parameter_value).to include( - '__typename' => 'FlowSubFlow', + '__typename' => 'SubFlowValue', 'startingNodeId' => nil ) expect(parameter_value['functionDefinition']).to include(