From a0b346263f8a87ea2524ca9f81d2558233c86fa9 Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Wed, 17 Dec 2025 14:59:11 +0100 Subject: [PATCH 1/2] Add disabled reason to flow --- app/graphql/types/flow_type.rb | 4 +++ app/grpc/flow_handler.rb | 2 +- app/models/flow.rb | 12 ++++++++ ...0328152854_add_disabled_reason_to_flows.rb | 8 ++++++ db/schema_migrations/20260328152854 | 1 + db/structure.sql | 3 ++ docs/graphql/object/flow.md | 1 + .../projects/flows/update_mutation_spec.rb | 28 +++++++++++++++++++ 8 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20260328152854_add_disabled_reason_to_flows.rb create mode 100644 db/schema_migrations/20260328152854 diff --git a/app/graphql/types/flow_type.rb b/app/graphql/types/flow_type.rb index 05281d66..ec17cac6 100644 --- a/app/graphql/types/flow_type.rb +++ b/app/graphql/types/flow_type.rb @@ -8,6 +8,10 @@ class FlowType < Types::BaseObject field :name, String, null: false, description: 'Name of the flow' + field :disabled_reason, String, + null: true, + description: 'The reason why the flow is disabled, if it is disabled' + field :validation_status, Types::FlowValidationStatusEnum, null: false, description: 'The validation status of the flow' diff --git a/app/grpc/flow_handler.rb b/app/grpc/flow_handler.rb index e570549d..81932dc3 100644 --- a/app/grpc/flow_handler.rb +++ b/app/grpc/flow_handler.rb @@ -10,7 +10,7 @@ class FlowHandler < Tucana::Sagittarius::FlowService::Service def self.update_runtime(runtime) flows = [] runtime.project_assignments.compatible.each do |assignment| - assignment.namespace_project.flows.validation_status_valid.each do |flow| + assignment.namespace_project.flowsvalidation_status_valid.enabled.each do |flow| flows << flow.to_grpc end end diff --git a/app/models/flow.rb b/app/models/flow.rb index c6026139..b216a442 100644 --- a/app/models/flow.rb +++ b/app/models/flow.rb @@ -32,6 +32,17 @@ class Flow < ApplicationRecord validates :input_type, length: { maximum: 2000 }, allow_nil: true validates :return_type, length: { maximum: 2000 }, allow_nil: true + validates :disabled_reason, presence: false, + allow_blank: true, + length: { maximum: 100, minimum: 0 } + + scope :enabled, -> { where(disabled_reason: nil) } + scope :disabled, -> { where.not(disabled_reason: nil) } + + def disabled? + disabled_reason.present? + end + def to_grpc Tucana::Shared::ValidationFlow.new( flow_id: id, @@ -39,6 +50,7 @@ def to_grpc project_slug: project.slug, type: flow_type.identifier, data_types: [], # TODO: when data types are creatable + disabled_reason: disabled_reason, input_type: input_type, return_type: return_type, settings: flow_settings.map(&:to_grpc), diff --git a/db/migrate/20260328152854_add_disabled_reason_to_flows.rb b/db/migrate/20260328152854_add_disabled_reason_to_flows.rb new file mode 100644 index 00000000..eeff2259 --- /dev/null +++ b/db/migrate/20260328152854_add_disabled_reason_to_flows.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddDisabledReasonToFlows < Code0::ZeroTrack::Database::Migration[1.0] + def change + add_column :flows, :disabled_reason, :text, null: true, default: nil + add_index :flows, :disabled_reason, length: 100 + end +end diff --git a/db/schema_migrations/20260328152854 b/db/schema_migrations/20260328152854 new file mode 100644 index 00000000..9aaa898b --- /dev/null +++ b/db/schema_migrations/20260328152854 @@ -0,0 +1 @@ +a75b8ec7e9c47f5967a2f4b3fc2ffb6870f850479132b0fb7c0f64eb951a5cb8 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 47bcfce1..8b56dc6d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -304,6 +304,7 @@ CREATE TABLE flows ( input_type text, return_type text, validation_status integer DEFAULT 0 NOT NULL, + disabled_reason text, CONSTRAINT check_1c805d704f CHECK ((char_length(input_type) <= 2000)), CONSTRAINT check_b2f3f83908 CHECK ((char_length(return_type) <= 2000)) ); @@ -1183,6 +1184,8 @@ CREATE UNIQUE INDEX index_flow_type_settings_on_flow_type_id_and_identifier ON f CREATE UNIQUE INDEX index_flow_types_on_runtime_id_and_identifier ON flow_types USING btree (runtime_id, identifier); +CREATE INDEX index_flows_on_disabled_reason ON flows USING btree (disabled_reason); + CREATE INDEX index_flows_on_flow_type_id ON flows USING btree (flow_type_id); CREATE UNIQUE INDEX index_flows_on_name_and_project_id ON flows USING btree (name, project_id); diff --git a/docs/graphql/object/flow.md b/docs/graphql/object/flow.md index a1740544..96618146 100644 --- a/docs/graphql/object/flow.md +++ b/docs/graphql/object/flow.md @@ -9,6 +9,7 @@ Represents a flow | Name | Type | Description | |------|------|-------------| | `createdAt` | [`Time!`](../scalar/time.md) | Time when this Flow was created | +| `disabledReason` | [`String`](../scalar/string.md) | The reason why the flow is disabled, if it is disabled | | `id` | [`FlowID!`](../scalar/flowid.md) | Global ID of this Flow | | `inputType` | [`String`](../scalar/string.md) | The input data type of the flow | | `linkedDataTypes` | [`DataTypeConnection!`](../object/datatypeconnection.md) | The data types that are referenced in this flow | 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 58ad6f3b..7af40a39 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 @@ -237,6 +237,34 @@ target_type: 'NamespaceProject' ) end + + context 'when flow is disabled' do + let(:input) do + { + flowId: flow.to_global_id.to_s, + flowInput: { + name: generate(:flow_name), + disabledReason: 'Some reason', + type: flow_type.to_global_id.to_s, + startingNodeId: nil, + settings: [], + nodes: [], + }, + } + end + + it 'updates flow as disabled' do + mutate! + + updated_flow_id = graphql_data_at(:namespaces_projects_flows_update, :flow, :id) + expect(updated_flow_id).to be_present + flow = SagittariusSchema.object_from_id(updated_flow_id) + + expect(flow).to be_present + expect(project.flows).to include(flow) + expect(flow.disabled_reason).to eq('Some reason') + end + end end context 'when removing nodes' do From b685ece82d79072b128496e5107e8a1c5984de5a Mon Sep 17 00:00:00 2001 From: Niklas van Schrick Date: Sat, 28 Mar 2026 16:26:27 +0100 Subject: [PATCH 2/2] Switch disabled_reason to an enum --- .../types/flow_disabled_reason_enum.rb | 11 ++++++++ app/graphql/types/flow_type.rb | 2 +- app/grpc/flow_handler.rb | 2 +- app/models/flow.rb | 17 +++++++---- ...0328152854_add_disabled_reason_to_flows.rb | 3 +- db/structure.sql | 4 +-- docs/graphql/enum/flowdisabledreason.md | 9 ++++++ docs/graphql/object/flow.md | 2 +- spec/models/flow_spec.rb | 4 +++ .../projects/flows/update_mutation_spec.rb | 28 ------------------- 10 files changed, 41 insertions(+), 41 deletions(-) create mode 100644 app/graphql/types/flow_disabled_reason_enum.rb create mode 100644 docs/graphql/enum/flowdisabledreason.md diff --git a/app/graphql/types/flow_disabled_reason_enum.rb b/app/graphql/types/flow_disabled_reason_enum.rb new file mode 100644 index 00000000..6e011e9c --- /dev/null +++ b/app/graphql/types/flow_disabled_reason_enum.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class FlowDisabledReasonEnum < Types::BaseEnum + description 'The disabled reason of a flow.' + + Flow::DISABLED_REASON.each do |reason, settings| + value reason.upcase, settings[:description], value: reason.to_s + end + end +end diff --git a/app/graphql/types/flow_type.rb b/app/graphql/types/flow_type.rb index ec17cac6..0da105f7 100644 --- a/app/graphql/types/flow_type.rb +++ b/app/graphql/types/flow_type.rb @@ -8,7 +8,7 @@ class FlowType < Types::BaseObject field :name, String, null: false, description: 'Name of the flow' - field :disabled_reason, String, + field :disabled_reason, Types::FlowDisabledReasonEnum, null: true, description: 'The reason why the flow is disabled, if it is disabled' diff --git a/app/grpc/flow_handler.rb b/app/grpc/flow_handler.rb index 81932dc3..e570549d 100644 --- a/app/grpc/flow_handler.rb +++ b/app/grpc/flow_handler.rb @@ -10,7 +10,7 @@ class FlowHandler < Tucana::Sagittarius::FlowService::Service def self.update_runtime(runtime) flows = [] runtime.project_assignments.compatible.each do |assignment| - assignment.namespace_project.flowsvalidation_status_valid.enabled.each do |flow| + assignment.namespace_project.flows.validation_status_valid.each do |flow| flows << flow.to_grpc end end diff --git a/app/models/flow.rb b/app/models/flow.rb index b216a442..8697628e 100644 --- a/app/models/flow.rb +++ b/app/models/flow.rb @@ -7,11 +7,16 @@ class Flow < ApplicationRecord invalid: 2, }.with_indifferent_access + DISABLED_REASON = { + _dummy: { db: 0, description: 'Dummy value' }, # temporary until the first real disabled reason gets introduced + }.with_indifferent_access + belongs_to :project, class_name: 'NamespaceProject' belongs_to :flow_type belongs_to :starting_node, class_name: 'NodeFunction', optional: true enum :validation_status, VALIDATION_STATUS, prefix: :validation_status + enum :disabled_reason, DISABLED_REASON.transform_values { |v| v[:db] }, prefix: :disabled_reason has_many :flow_settings, class_name: 'FlowSetting', inverse_of: :flow has_many :node_functions, class_name: 'NodeFunction', inverse_of: :flow @@ -25,6 +30,12 @@ class Flow < ApplicationRecord in: VALIDATION_STATUS.keys.map(&:to_s), } + validates :disabled_reason, + inclusion: { + in: DISABLED_REASON.keys.map(&:to_s), + }, + if: :disabled? + validates :name, presence: true, allow_blank: false, uniqueness: { case_sensitive: false, scope: :project_id } @@ -32,10 +43,6 @@ class Flow < ApplicationRecord validates :input_type, length: { maximum: 2000 }, allow_nil: true validates :return_type, length: { maximum: 2000 }, allow_nil: true - validates :disabled_reason, presence: false, - allow_blank: true, - length: { maximum: 100, minimum: 0 } - scope :enabled, -> { where(disabled_reason: nil) } scope :disabled, -> { where.not(disabled_reason: nil) } @@ -50,7 +57,7 @@ def to_grpc project_slug: project.slug, type: flow_type.identifier, data_types: [], # TODO: when data types are creatable - disabled_reason: disabled_reason, + disable_reason: disabled_reason, input_type: input_type, return_type: return_type, settings: flow_settings.map(&:to_grpc), diff --git a/db/migrate/20260328152854_add_disabled_reason_to_flows.rb b/db/migrate/20260328152854_add_disabled_reason_to_flows.rb index eeff2259..594f9b2f 100644 --- a/db/migrate/20260328152854_add_disabled_reason_to_flows.rb +++ b/db/migrate/20260328152854_add_disabled_reason_to_flows.rb @@ -2,7 +2,6 @@ class AddDisabledReasonToFlows < Code0::ZeroTrack::Database::Migration[1.0] def change - add_column :flows, :disabled_reason, :text, null: true, default: nil - add_index :flows, :disabled_reason, length: 100 + add_column :flows, :disabled_reason, :integer, null: true end end diff --git a/db/structure.sql b/db/structure.sql index 8b56dc6d..9ba1ed98 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -304,7 +304,7 @@ CREATE TABLE flows ( input_type text, return_type text, validation_status integer DEFAULT 0 NOT NULL, - disabled_reason text, + disabled_reason integer, CONSTRAINT check_1c805d704f CHECK ((char_length(input_type) <= 2000)), CONSTRAINT check_b2f3f83908 CHECK ((char_length(return_type) <= 2000)) ); @@ -1184,8 +1184,6 @@ CREATE UNIQUE INDEX index_flow_type_settings_on_flow_type_id_and_identifier ON f CREATE UNIQUE INDEX index_flow_types_on_runtime_id_and_identifier ON flow_types USING btree (runtime_id, identifier); -CREATE INDEX index_flows_on_disabled_reason ON flows USING btree (disabled_reason); - CREATE INDEX index_flows_on_flow_type_id ON flows USING btree (flow_type_id); CREATE UNIQUE INDEX index_flows_on_name_and_project_id ON flows USING btree (name, project_id); diff --git a/docs/graphql/enum/flowdisabledreason.md b/docs/graphql/enum/flowdisabledreason.md new file mode 100644 index 00000000..d534931e --- /dev/null +++ b/docs/graphql/enum/flowdisabledreason.md @@ -0,0 +1,9 @@ +--- +title: FlowDisabledReason +--- + +The disabled reason of a flow. + +| Value | Description | +|-------|-------------| +| `_DUMMY` | Dummy value | diff --git a/docs/graphql/object/flow.md b/docs/graphql/object/flow.md index 96618146..7fd6fd73 100644 --- a/docs/graphql/object/flow.md +++ b/docs/graphql/object/flow.md @@ -9,7 +9,7 @@ Represents a flow | Name | Type | Description | |------|------|-------------| | `createdAt` | [`Time!`](../scalar/time.md) | Time when this Flow was created | -| `disabledReason` | [`String`](../scalar/string.md) | The reason why the flow is disabled, if it is disabled | +| `disabledReason` | [`FlowDisabledReason`](../enum/flowdisabledreason.md) | The reason why the flow is disabled, if it is disabled | | `id` | [`FlowID!`](../scalar/flowid.md) | Global ID of this Flow | | `inputType` | [`String`](../scalar/string.md) | The input data type of the flow | | `linkedDataTypes` | [`DataTypeConnection!`](../object/datatypeconnection.md) | The data types that are referenced in this flow | diff --git a/spec/models/flow_spec.rb b/spec/models/flow_spec.rb index b8017ae3..1f6ba842 100644 --- a/spec/models/flow_spec.rb +++ b/spec/models/flow_spec.rb @@ -21,6 +21,8 @@ describe 'validations' do it { is_expected.to allow_values(*described_class::VALIDATION_STATUS.keys).for(:validation_status) } + it { is_expected.to allow_values(nil, *described_class::DISABLED_REASON.keys).for(:disabled_reason) } + it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).case_insensitive.scoped_to(:project_id) } @@ -47,6 +49,7 @@ flow_type: create(:flow_type, identifier: 'HTTP'), input_type: 'string', return_type: 'number', + disabled_reason: 0, flow_settings: [ create( :flow_setting, @@ -131,6 +134,7 @@ }, } ], + disable_reason: '_dummy', } ) 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 7af40a39..58ad6f3b 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 @@ -237,34 +237,6 @@ target_type: 'NamespaceProject' ) end - - context 'when flow is disabled' do - let(:input) do - { - flowId: flow.to_global_id.to_s, - flowInput: { - name: generate(:flow_name), - disabledReason: 'Some reason', - type: flow_type.to_global_id.to_s, - startingNodeId: nil, - settings: [], - nodes: [], - }, - } - end - - it 'updates flow as disabled' do - mutate! - - updated_flow_id = graphql_data_at(:namespaces_projects_flows_update, :flow, :id) - expect(updated_flow_id).to be_present - flow = SagittariusSchema.object_from_id(updated_flow_id) - - expect(flow).to be_present - expect(project.flows).to include(flow) - expect(flow.disabled_reason).to eq('Some reason') - end - end end context 'when removing nodes' do