diff --git a/app/services/namespaces/projects/assign_runtimes_service.rb b/app/services/namespaces/projects/assign_runtimes_service.rb index 57697e90..d4dcf0ff 100644 --- a/app/services/namespaces/projects/assign_runtimes_service.rb +++ b/app/services/namespaces/projects/assign_runtimes_service.rb @@ -24,7 +24,7 @@ def execute end namespace_project.runtimes = runtimes - namespace_project.primary_runtime = runtimes.first if runtimes.size == 1 + namespace_project.primary_runtime = primary_runtime_after_assignment unless namespace_project.save t.rollback_and_return! ServiceResponse.error( @@ -50,6 +50,15 @@ def execute ServiceResponse.success(message: 'Assigned runtimes to a project', payload: namespace_project) end end + + private + + def primary_runtime_after_assignment + return if runtimes.empty? + return namespace_project.primary_runtime if runtimes.include?(namespace_project.primary_runtime) + + runtimes.first + end end end end diff --git a/app/services/velorum/generation_flow_serializer.rb b/app/services/velorum/generation_flow_serializer.rb index 7ccab44b..0196f45c 100644 --- a/app/services/velorum/generation_flow_serializer.rb +++ b/app/services/velorum/generation_flow_serializer.rb @@ -34,7 +34,7 @@ def to_h { name: flow.name, type: flow_type, - starting_node_id: node_reference_id(flow.starting_node_id) || generated_starting_node_id, + starting_node_id: starting_node_id_for, settings: flow.settings.map.with_index { |setting, index| flow_setting_to_h(setting, index, flow_type) }, nodes: serialized_nodes, } @@ -92,7 +92,7 @@ def node_to_h(node, index) { id: generated_node_ids.fetch(node), function_definition: function_definition, - next_node_id: node_reference_id(node.next_node_id) || generated_next_node_id(index), + next_node_id: next_node_id_for(node, index), parameters: node.parameters.map.with_index do |parameter, parameter_index| parameter_to_h(parameter, parameter_index, function_definition) end, @@ -224,19 +224,23 @@ def flow_setting_to_h(setting, index, flow_type) def node_reference_id(value) value = blank_zero(value) - return if value.blank? + return if value.blank? || value.to_s == 'None' node_id_by_source_id.fetch(value.to_s, value) end - def generated_starting_node_id - generated_node_ids.values.first + def starting_node_id_for + node_reference_id(flow.starting_node_id) end def generated_next_node_id(index) flow.node_functions[index + 1]&.then { |next_node| generated_node_ids.fetch(next_node) } end + def next_node_id_for(node, index) + node_reference_id(node.next_node_id) || (node.has_next_node_id? ? generated_next_node_id(index) : nil) + end + def blank_zero(value) return if value.blank? || value.to_s == '0' diff --git a/spec/services/namespaces/projects/assign_runtimes_service_spec.rb b/spec/services/namespaces/projects/assign_runtimes_service_spec.rb index 0ec0b78d..3a53e96e 100644 --- a/spec/services/namespaces/projects/assign_runtimes_service_spec.rb +++ b/spec/services/namespaces/projects/assign_runtimes_service_spec.rb @@ -73,7 +73,18 @@ context 'when adding multiple runtimes' do let(:runtimes) { 2.times.map { create(:runtime, namespace: project.namespace) } } - it { expect { service_response }.not_to change { project.reload.primary_runtime } } + it { expect { service_response }.to change { project.reload.primary_runtime }.to(runtimes.first) } + + context 'when the current primary runtime remains assigned' do + let(:primary_runtime) { create(:runtime, namespace: project.namespace) } + let(:runtimes) { [create(:runtime, namespace: project.namespace), primary_runtime] } + + before do + project.update!(primary_runtime: primary_runtime) + end + + it { expect { service_response }.not_to change { project.reload.primary_runtime } } + end end end @@ -85,6 +96,7 @@ let(:runtimes) { [] } before do + project.update!(primary_runtime: runtime) stub_allowed_ability(NamespaceProjectPolicy, :assign_project_runtimes, user: current_user, subject: project) end @@ -116,6 +128,8 @@ { namespace_project_id: project.id } ) end + + it { expect { service_response }.to change { project.reload.primary_runtime }.to(nil) } end context 'when adding and removing a runtime' do diff --git a/spec/services/velorum/generation_flow_serializer_spec.rb b/spec/services/velorum/generation_flow_serializer_spec.rb index ec6e603c..ce7addb3 100644 --- a/spec/services/velorum/generation_flow_serializer_spec.rb +++ b/spec/services/velorum/generation_flow_serializer_spec.rb @@ -61,7 +61,7 @@ expect(described_class.new(flow, project: project).to_h).to include( name: 'Generated flow', type: flow_type, - starting_node_id: 'generated-1', + starting_node_id: nil, settings: [ a_hash_including( id: 1, @@ -109,13 +109,14 @@ ) end - it 'maps generated node IDs into references and inferred next-node links' do + it 'maps generated node IDs into references and explicit fallback next-node links' do flow = Tucana::Shared::GenerationFlow.new( name: 'Generated flow', type: 'default', node_functions: [ Tucana::Shared::NodeFunction.new( runtime_function_id: 'input', + next_node_id: 0, parameters: [] ), Tucana::Shared::NodeFunction.new( @@ -262,4 +263,73 @@ function_definition: function_definition ) end + + it 'does not infer a next-node link when next_node_id is absent' do + flow = Tucana::Shared::GenerationFlow.new( + name: 'Generated flow', + type: 'default', + node_functions: [ + Tucana::Shared::NodeFunction.new( + database_id: 6, + runtime_function_id: 'rest::control::respond' + ), + Tucana::Shared::NodeFunction.new( + database_id: 7, + runtime_function_id: 'std::control::value', + next_node_id: 6 + ) + ], + starting_node_id: '7' + ) + + serialized = described_class.new(flow).to_h + + expect(serialized).to include(starting_node_id: '7') + expect(serialized[:nodes][0]).to include(id: '6', next_node_id: nil) + expect(serialized[:nodes][1]).to include(id: '7', next_node_id: '6') + end + + it 'does not infer a starting node when starting_node_id is absent' do + flow_type = create(:flow_type, runtime: runtime, identifier: 'REST') + create(:flow_type_setting, flow_type: flow_type) + create(:flow_type_setting, flow_type: flow_type) + create(:flow_type_setting, flow_type: flow_type) + flow = Tucana::Shared::GenerationFlow.new( + name: 'Hello World Flow', + type: 'REST', + node_functions: [], + settings: [ + Tucana::Shared::FlowSetting.new(value: Tucana::Shared::Value.from_ruby({})), + Tucana::Shared::FlowSetting.new(value: Tucana::Shared::Value.from_ruby('/hello')), + Tucana::Shared::FlowSetting.new(value: Tucana::Shared::Value.from_ruby('GET')) + ] + ) + + serialized = described_class.new(flow, project: project).to_h + + expect(serialized).to include( + name: 'Hello World Flow', + type: flow_type, + starting_node_id: nil, + nodes: [] + ) + end + + it 'does not serialize a None starting node reference' do + flow_type = create(:flow_type, runtime: runtime, identifier: 'REST') + flow = Tucana::Shared::GenerationFlow.new( + name: 'Hello World Flow', + type: 'REST', + node_functions: [], + starting_node_id: 'None' + ) + + serialized = described_class.new(flow, project: project).to_h + + expect(serialized).to include( + type: flow_type, + starting_node_id: nil, + nodes: [] + ) + end end