diff --git a/Gemfile b/Gemfile index c824482d..cbabc137 100644 --- a/Gemfile +++ b/Gemfile @@ -79,7 +79,7 @@ gem 'good_job', '~> 4.0' gem 'rotp' gem 'grpc', '~> 1.67' -gem 'tucana', '0.0.52' +gem 'tucana', '0.0.53' gem 'code0-identities', '~> 0.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 19349ef4..ccac940f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -377,7 +377,7 @@ GEM thor (1.4.0) timeout (0.6.0) tsort (0.2.0) - tucana (0.0.52) + tucana (0.0.53) grpc (~> 1.64) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -431,7 +431,7 @@ DEPENDENCIES simplecov (~> 0.22.0) simplecov-cobertura (~> 3.0) test-prof (~> 1.0) - tucana (= 0.0.52) + tucana (= 0.0.53) tzinfo-data RUBY VERSION diff --git a/app/graphql/types/input/reference_value_input_type.rb b/app/graphql/types/input/reference_value_input_type.rb index 22242959..ef0fcaac 100644 --- a/app/graphql/types/input/reference_value_input_type.rb +++ b/app/graphql/types/input/reference_value_input_type.rb @@ -6,9 +6,20 @@ class ReferenceValueInputType < Types::BaseInputObject description 'Input type for reference value' argument :reference_path, [Types::Input::ReferencePathInputType], - required: true, description: 'The paths associated with this reference value' + required: true, + description: 'The paths associated with this reference value' - argument :node_function_id, GlobalIdType[::NodeFunction], required: true, description: 'The referenced value' + argument :node_function_id, GlobalIdType[::NodeFunction], + required: false, + description: 'The referenced value unless referencing the flow input' + + argument :parameter_index, GraphQL::Types::Int, + required: false, + description: 'The index of the referenced parameter' + + argument :input_index, GraphQL::Types::Int, + required: false, + description: 'The index of the referenced input' end end end diff --git a/app/graphql/types/reference_value_type.rb b/app/graphql/types/reference_value_type.rb index bcc66f79..89ad4376 100644 --- a/app/graphql/types/reference_value_type.rb +++ b/app/graphql/types/reference_value_type.rb @@ -4,18 +4,28 @@ module Types class ReferenceValueType < Types::BaseObject description 'Represents a reference value in the system.' - field :node_function_id, GlobalIdType[::NodeFunction], null: false, description: 'The referenced value.' + field :node_function_id, GlobalIdType[::NodeFunction], + null: true, + description: 'The referenced value unless referencing the flow input.' field :reference_path, [Types::ReferencePathType], null: false, description: 'The paths associated with this reference value.', method: :reference_paths + field :parameter_index, GraphQL::Types::Int, + null: true, + description: 'The index of the referenced parameter' + + field :input_index, GraphQL::Types::Int, + null: true, + description: 'The index of the referenced input' + id_field ReferenceValue timestamps def node_function_id - object.node_function.to_global_id + object.node_function&.to_global_id end end end diff --git a/app/models/flow.rb b/app/models/flow.rb index d979fb2f..89f748d2 100644 --- a/app/models/flow.rb +++ b/app/models/flow.rb @@ -21,8 +21,8 @@ def to_grpc project_slug: project.slug, type: flow_type.identifier, data_types: [], # TODO: when data types are creatable - input_type_identifier: input_type&.identifier, - return_type_identifier: return_type&.identifier, + input_type: Tucana::Shared::DataTypeIdentifier.from_hash({ data_type_identifier: input_type&.identifier }), + return_type: Tucana::Shared::DataTypeIdentifier.from_hash({ data_type_identifier: return_type&.identifier }), settings: flow_settings.map(&:to_grpc), starting_node_id: starting_node.id, node_functions: node_functions.map(&:to_grpc) diff --git a/app/models/reference_value.rb b/app/models/reference_value.rb index c1a9dbb9..5a9d0327 100644 --- a/app/models/reference_value.rb +++ b/app/models/reference_value.rb @@ -1,14 +1,37 @@ # frozen_string_literal: true class ReferenceValue < ApplicationRecord - belongs_to :node_function # real value association + belongs_to :node_function, optional: true # real value association has_many :reference_paths, inverse_of: :reference_value, autosave: true, dependent: :destroy has_many :node_parameters, inverse_of: :reference_value + validate :validate_indexes + + def validate_indexes + return if parameter_index.nil? && input_index.nil? + + errors.add(:node_function, :blank) if node_function.nil? + errors.add(:input_index, :blank) if parameter_index.present? && input_index.nil? + errors.add(:parameter_index, :blank) if input_index.present? && parameter_index.nil? + end + def to_grpc - Tucana::Shared::ReferenceValue.new( - node_id: node_function.id, + reference_value = Tucana::Shared::ReferenceValue.new( paths: reference_paths.map(&:to_grpc) ) + + if node_function.nil? + reference_value.flow_input = Tucana::Shared::FlowInput.new + elsif parameter_index.present? && input_index.present? + reference_value.input_type = Tucana::Shared::InputType.new( + node_id: node_function.id, + parameter_index: parameter_index, + input_index: input_index + ) + else + reference_value.node_id = node_function.id + end + + reference_value end end diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index 50e5631a..141a9495 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -202,15 +202,19 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) end if parameter.value.reference_value.present? - referenced_node = all_nodes.find do |n| - n[:input].id == parameter.value.reference_value.node_function_id - end - - if referenced_node.nil? - t.rollback_and_return! ServiceResponse.error( - message: 'Referenced node function not found', - error_code: :referenced_value_not_found - ) + if parameter.value.reference_value.node_function_id.present? + referenced_node = all_nodes.find do |n| + n[:input].id == parameter.value.reference_value.node_function_id + end + + if referenced_node.nil? + t.rollback_and_return! ServiceResponse.error( + message: 'Referenced node function not found', + error_code: :referenced_value_not_found + ) + end + else + referenced_node = { node: nil } end db_parameters[index].reference_value ||= ReferenceValue.new @@ -225,7 +229,9 @@ def update_node_parameters(t, current_node, current_node_input, all_nodes) reference_value.assign_attributes( node_function: referenced_node[:node], - reference_paths: reference_paths + reference_paths: reference_paths, + parameter_index: parameter.value.reference_value.parameter_index, + input_index: parameter.value.reference_value.input_index ) else db_parameters[index].reference_value&.destroy diff --git a/db/migrate/20260222154501_add_input_type_fields_to_reference_value.rb b/db/migrate/20260222154501_add_input_type_fields_to_reference_value.rb new file mode 100644 index 00000000..40802362 --- /dev/null +++ b/db/migrate/20260222154501_add_input_type_fields_to_reference_value.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddInputTypeFieldsToReferenceValue < Code0::ZeroTrack::Database::Migration[1.0] + def change + add_column :reference_values, :parameter_index, :int + add_column :reference_values, :input_index, :int + + add_check_constraint :reference_values, 'num_nonnulls(parameter_index, input_index) IN (0, 2)', + name: check_constraint_name(:reference_values, :indexes, :none_or_both) + + change_column_null :reference_values, :node_function_id, true + end +end diff --git a/db/schema_migrations/20260222154501 b/db/schema_migrations/20260222154501 new file mode 100644 index 00000000..1ffa3866 --- /dev/null +++ b/db/schema_migrations/20260222154501 @@ -0,0 +1 @@ +12fbf843d279b87a5a6da04d17a2ccb718a18979fe6eaeb53ed76e7ad131fb17 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 14cb22f0..07280afb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -668,7 +668,10 @@ CREATE TABLE reference_values ( id bigint NOT NULL, created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL, - node_function_id bigint NOT NULL + node_function_id bigint, + parameter_index integer, + input_index integer, + CONSTRAINT check_a2e3734389 CHECK ((num_nonnulls(parameter_index, input_index) = ANY (ARRAY[0, 2]))) ); CREATE SEQUENCE reference_values_id_seq diff --git a/docs/graphql/input_object/referencevalueinput.md b/docs/graphql/input_object/referencevalueinput.md index 842913f3..ed532bd2 100644 --- a/docs/graphql/input_object/referencevalueinput.md +++ b/docs/graphql/input_object/referencevalueinput.md @@ -8,5 +8,7 @@ Input type for reference value | Name | Type | Description | |------|------|-------------| -| `nodeFunctionId` | [`NodeFunctionID!`](../scalar/nodefunctionid.md) | The referenced value | +| `inputIndex` | [`Int`](../scalar/int.md) | The index of the referenced input | +| `nodeFunctionId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The referenced value unless referencing the flow input | +| `parameterIndex` | [`Int`](../scalar/int.md) | The index of the referenced parameter | | `referencePath` | [`[ReferencePathInput!]!`](../input_object/referencepathinput.md) | The paths associated with this reference value | diff --git a/docs/graphql/object/referencevalue.md b/docs/graphql/object/referencevalue.md index b50d76ed..24b8b549 100644 --- a/docs/graphql/object/referencevalue.md +++ b/docs/graphql/object/referencevalue.md @@ -10,7 +10,9 @@ Represents a reference value in the system. |------|------|-------------| | `createdAt` | [`Time!`](../scalar/time.md) | Time when this ReferenceValue was created | | `id` | [`ReferenceValueID!`](../scalar/referencevalueid.md) | Global ID of this ReferenceValue | -| `nodeFunctionId` | [`NodeFunctionID!`](../scalar/nodefunctionid.md) | The referenced value. | +| `inputIndex` | [`Int`](../scalar/int.md) | The index of the referenced input | +| `nodeFunctionId` | [`NodeFunctionID`](../scalar/nodefunctionid.md) | The referenced value unless referencing the flow input. | +| `parameterIndex` | [`Int`](../scalar/int.md) | The index of the referenced parameter | | `referencePath` | [`[ReferencePath!]!`](../object/referencepath.md) | The paths associated with this reference value. | | `updatedAt` | [`Time!`](../scalar/time.md) | Time when this ReferenceValue was last updated | diff --git a/spec/factories/reference_values.rb b/spec/factories/reference_values.rb index 9bdc12f0..5b42a9e8 100644 --- a/spec/factories/reference_values.rb +++ b/spec/factories/reference_values.rb @@ -3,5 +3,7 @@ FactoryBot.define do factory :reference_value do node_function + parameter_index { nil } + input_index { nil } end end diff --git a/spec/models/flow_spec.rb b/spec/models/flow_spec.rb index d57efd3a..37ee710f 100644 --- a/spec/models/flow_spec.rb +++ b/spec/models/flow_spec.rb @@ -69,6 +69,8 @@ project_id: flow.project.id, project_slug: flow.project.slug, type: flow.flow_type.identifier, + input_type: {}, + return_type: {}, node_functions: [ { database_id: starting_node.id, diff --git a/spec/models/reference_value_spec.rb b/spec/models/reference_value_spec.rb index f4cd8c16..576ebecb 100644 --- a/spec/models/reference_value_spec.rb +++ b/spec/models/reference_value_spec.rb @@ -3,12 +3,36 @@ require 'rails_helper' RSpec.describe ReferenceValue do - subject do + subject(:reference_value) do create(:reference_value, node_function: create(:node_function)) end describe 'associations' do - it { is_expected.to belong_to(:node_function) } + it { is_expected.to belong_to(:node_function).optional } it { is_expected.to have_many(:reference_paths) } end + + describe 'validations' do + describe 'validate_indexes' do + it do + reference_value.parameter_index = 1 + is_expected.not_to be_valid + end + + it do + reference_value.input_index = 1 + is_expected.not_to be_valid + end + + it do + reference_value.parameter_index = 1 + reference_value.input_index = 1 + is_expected.to be_valid + end + + it do + is_expected.to be_valid + end + 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 f14f5e83..c31b35f6 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 @@ -37,6 +37,8 @@ id path } + parameterIndex + inputIndex updatedAt } } @@ -125,16 +127,33 @@ id: 'gid://sagittarius/NodeFunction/1001', functionDefinitionId: function_definition.to_global_id.to_s, parameters: [ - parameterDefinitionId: function_definition.parameter_definitions.first.to_global_id.to_s, - value: { - referenceValue: { - referencePath: [ - { - arrayIndex: 0, - path: 'some.path', - } - ], - nodeFunctionId: 'gid://sagittarius/NodeFunction/2000', + { + parameterDefinitionId: function_definition.parameter_definitions.first.to_global_id.to_s, + value: { + referenceValue: { + referencePath: [ + { + arrayIndex: 0, + path: 'some.path', + } + ], + nodeFunctionId: 'gid://sagittarius/NodeFunction/2000', + parameterIndex: 1, + inputIndex: 1, + }, + }, + }, + { + parameterDefinitionId: function_definition.parameter_definitions.first.to_global_id.to_s, + value: { + referenceValue: { + referencePath: [ + { + arrayIndex: 1, + path: 'some.path', + } + ], + }, }, } ], @@ -181,9 +200,26 @@ :nodes, :value ) - expect(parameter_values).to include(a_hash_including('value' => 100)) expect(parameter_values).to include( - a_hash_including('referencePath' => [a_hash_including('arrayIndex' => 0, 'path' => 'some.path')]) + a_hash_including( + '__typename' => 'LiteralValue', + 'value' => 100 + ) + ) + expect(parameter_values).to include( + a_hash_including( + '__typename' => 'ReferenceValue', + 'nodeFunctionId' => a_string_matching(%r{gid://sagittarius/NodeFunction/\d+}), + 'referencePath' => [a_hash_including('arrayIndex' => 0, 'path' => 'some.path')], + 'parameterIndex' => 1, + 'inputIndex' => 1 + ) + ) + expect(parameter_values).to include( + a_hash_including( + '__typename' => 'ReferenceValue', + 'referencePath' => [a_hash_including('arrayIndex' => 1, 'path' => 'some.path')] + ) ) is_expected.to create_audit_event(