From a715b170d50a5f57309bf38980b9f136a178ab9e Mon Sep 17 00:00:00 2001 From: Niklas van Schrick Date: Wed, 21 Jan 2026 20:16:38 +0100 Subject: [PATCH] Add support for null literal values --- .../namespaces/projects/flows/update.rb | 2 +- .../types/input/node_parameter_input_type.rb | 2 +- app/graphql/types/literal_value_type.rb | 10 ++++---- app/graphql/types/node_parameter_type.rb | 6 ++--- app/models/node_parameter.rb | 11 ++++----- app/models/reference_value.rb | 2 +- .../projects/flows/update_service.rb | 4 +++- ...allow_all_null_values_in_node_parameter.rb | 13 +++++++++++ db/schema_migrations/20260121183311 | 1 + db/structure.sql | 2 +- .../input_object/nodeparameterinput.md | 2 +- docs/graphql/object/literalvalue.md | 4 +--- spec/models/node_parameter_spec.rb | 23 ++++++++++++++++--- 13 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 db/migrate/20260121183311_allow_all_null_values_in_node_parameter.rb create mode 100644 db/schema_migrations/20260121183311 diff --git a/app/graphql/mutations/namespaces/projects/flows/update.rb b/app/graphql/mutations/namespaces/projects/flows/update.rb index 8e60a24f..e1824c72 100644 --- a/app/graphql/mutations/namespaces/projects/flows/update.rb +++ b/app/graphql/mutations/namespaces/projects/flows/update.rb @@ -14,7 +14,7 @@ class Update < BaseMutation argument :flow_input, Types::Input::FlowInputType, description: 'The updated flow', required: true - def resolve(flow_id:, flow_input:, **_params) + def resolve(flow_id:, flow_input:) flow = SagittariusSchema.object_from_id(flow_id) return { errors: [create_error(:flow_not_found, 'Flow does not exist')] } if flow.nil? diff --git a/app/graphql/types/input/node_parameter_input_type.rb b/app/graphql/types/input/node_parameter_input_type.rb index a89a5aa6..2aae0304 100644 --- a/app/graphql/types/input/node_parameter_input_type.rb +++ b/app/graphql/types/input/node_parameter_input_type.rb @@ -8,7 +8,7 @@ class NodeParameterInputType < Types::BaseInputObject argument :parameter_definition_id, Types::GlobalIdType[::ParameterDefinition], required: true, description: 'The identifier of the Parameter Definition' - argument :value, Types::Input::NodeParameterValueInputType, required: false, + argument :value, Types::Input::NodeParameterValueInputType, required: true, description: 'The value of the parameter' end end diff --git a/app/graphql/types/literal_value_type.rb b/app/graphql/types/literal_value_type.rb index a88f1994..c9c9ed7c 100644 --- a/app/graphql/types/literal_value_type.rb +++ b/app/graphql/types/literal_value_type.rb @@ -5,10 +5,12 @@ class LiteralValueType < Types::BaseObject description 'Represents a literal value, such as a string or number.' field :value, GraphQL::Types::JSON, - null: false, - description: 'The literal value itself as JSON.', - method: :itself + null: true, + description: 'The literal value itself as JSON.' - timestamps + # can't use method: :itself on the field because that turns {} into null + def value + object + end end end diff --git a/app/graphql/types/node_parameter_type.rb b/app/graphql/types/node_parameter_type.rb index 5b89947c..d452f261 100644 --- a/app/graphql/types/node_parameter_type.rb +++ b/app/graphql/types/node_parameter_type.rb @@ -11,12 +11,12 @@ class NodeParameterType < Types::BaseObject field :value, Types::NodeParameterValueType, null: true, description: 'The value of the parameter' def value - if object.literal_value.present? - object.literal_value - elsif object.reference_value.present? + if object.reference_value.present? object.reference_value elsif object.function_value_id.present? object.function_value + else + object.literal_value end end diff --git a/app/models/node_parameter.rb b/app/models/node_parameter.rb index 694c04dc..e6bc7b80 100644 --- a/app/models/node_parameter.rb +++ b/app/models/node_parameter.rb @@ -16,12 +16,12 @@ def to_grpc param.value = Tucana::Shared::NodeValue.new(literal_value: Tucana::Shared::Value.from_ruby({})) - if literal_value.present? - param.value.literal_value = Tucana::Shared::Value.from_ruby(literal_value) - elsif reference_value.present? + if reference_value.present? param.value.reference_value = reference_value.to_grpc elsif function_value.present? param.value.node_function_id = function_value.id + else + param.value.literal_value = Tucana::Shared::Value.from_ruby(literal_value) end param @@ -31,9 +31,8 @@ def to_grpc def only_one_value_present values = [!literal_value.nil?, reference_value.present?, function_value.present?] - return if values.count(true) == 1 + return if values.count(true) <= 1 - errors.add(:value, - 'Exactly one of literal_value, reference_value, or function_value must be present') + errors.add(:value, 'Only one of literal_value, reference_value, or function_value must be present') end end diff --git a/app/models/reference_value.rb b/app/models/reference_value.rb index 7b1da807..360fd101 100644 --- a/app/models/reference_value.rb +++ b/app/models/reference_value.rb @@ -3,7 +3,7 @@ class ReferenceValue < ApplicationRecord belongs_to :node_function # real value association belongs_to :data_type_identifier - has_many :reference_paths, inverse_of: :reference_value, autosave: true + has_many :reference_paths, inverse_of: :reference_value, autosave: true, dependent: :destroy has_many :node_parameters, inverse_of: :reference_value def to_grpc diff --git a/app/services/namespaces/projects/flows/update_service.rb b/app/services/namespaces/projects/flows/update_service.rb index e75b2621..1f38664a 100644 --- a/app/services/namespaces/projects/flows/update_service.rb +++ b/app/services/namespaces/projects/flows/update_service.rb @@ -54,7 +54,9 @@ def update_flow_attributes end def update_settings(t) - flow_input.settings&.each do |setting| + return if flow_input.settings.blank? + + flow_input.settings.each do |setting| flow_setting = flow.flow_settings.find_or_initialize_by(flow_setting_id: setting.flow_setting_identifier) flow_setting.object = setting.value diff --git a/db/migrate/20260121183311_allow_all_null_values_in_node_parameter.rb b/db/migrate/20260121183311_allow_all_null_values_in_node_parameter.rb new file mode 100644 index 00000000..91ab19b6 --- /dev/null +++ b/db/migrate/20260121183311_allow_all_null_values_in_node_parameter.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AllowAllNullValuesInNodeParameter < Code0::ZeroTrack::Database::Migration[1.0] + def change + add_check_constraint :node_parameters, + '(num_nonnulls(literal_value, reference_value_id, function_value_id) <= 1)', + name: check_constraint_name(:node_parameters, :value, :at_most_one) + + remove_check_constraint :node_parameters, + '(num_nonnulls(literal_value, reference_value_id, function_value_id) = 1)', + name: check_constraint_name(:node_parameters, :value, :one_of) + end +end diff --git a/db/schema_migrations/20260121183311 b/db/schema_migrations/20260121183311 new file mode 100644 index 00000000..aa5cc4de --- /dev/null +++ b/db/schema_migrations/20260121183311 @@ -0,0 +1 @@ +44b8e5e992817dec1f464949ee61b1c7937421aec377d3ee8d210ca06c2be9bc \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3868476f..ec628740 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -596,7 +596,7 @@ 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, - CONSTRAINT check_fdac0ea550 CHECK ((num_nonnulls(literal_value, reference_value_id, function_value_id) = 1)) + CONSTRAINT check_46b42221bc CHECK ((num_nonnulls(literal_value, reference_value_id, function_value_id) <= 1)) ); CREATE SEQUENCE node_parameters_id_seq diff --git a/docs/graphql/input_object/nodeparameterinput.md b/docs/graphql/input_object/nodeparameterinput.md index d9b8a072..37613566 100644 --- a/docs/graphql/input_object/nodeparameterinput.md +++ b/docs/graphql/input_object/nodeparameterinput.md @@ -9,4 +9,4 @@ Input type for Node parameter | Name | Type | Description | |------|------|-------------| | `parameterDefinitionId` | [`ParameterDefinitionID!`](../scalar/parameterdefinitionid.md) | The identifier of the Parameter Definition | -| `value` | [`NodeParameterValueInput`](../input_object/nodeparametervalueinput.md) | The value of the parameter | +| `value` | [`NodeParameterValueInput!`](../input_object/nodeparametervalueinput.md) | The value of the parameter | diff --git a/docs/graphql/object/literalvalue.md b/docs/graphql/object/literalvalue.md index 67eca3cc..23514eb2 100644 --- a/docs/graphql/object/literalvalue.md +++ b/docs/graphql/object/literalvalue.md @@ -8,7 +8,5 @@ Represents a literal value, such as a string or number. | Name | Type | Description | |------|------|-------------| -| `createdAt` | [`Time!`](../scalar/time.md) | Time when this LiteralValue was created | -| `updatedAt` | [`Time!`](../scalar/time.md) | Time when this LiteralValue was last updated | -| `value` | [`JSON!`](../scalar/json.md) | The literal value itself as JSON. | +| `value` | [`JSON`](../scalar/json.md) | The literal value itself as JSON. | diff --git a/spec/models/node_parameter_spec.rb b/spec/models/node_parameter_spec.rb index 9ebcc716..48d80194 100644 --- a/spec/models/node_parameter_spec.rb +++ b/spec/models/node_parameter_spec.rb @@ -28,8 +28,8 @@ it 'validates only one of the value fields is present' do param = build( :node_parameter, - literal_value: nil, - reference_value: nil, + literal_value: 1, + reference_value: create(:reference_value), function_value: nil, parameter_definition: create( :parameter_definition, @@ -41,7 +41,24 @@ ) expect(param).not_to be_valid expect(param.errors[:value]) - .to include('Exactly one of literal_value, reference_value, or function_value must be present') + .to include('Only one of literal_value, reference_value, or function_value 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, + parameter_definition: create( + :parameter_definition, + data_type: create( + :data_type_identifier, + data_type: create(:data_type) + ) + ) + ) + expect(param).to be_valid end end end