Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Mutations
module Namespaces
module Projects
module RuntimeAssignments
class UpdateModuleConfigurations < BaseMutation
description 'Updates the saved module configurations for a project runtime assignment.'

argument :module_configurations, [Types::Input::ModuleConfigurationInputType],
required: true,
description: 'The full set of saved module configurations for this assignment.'
argument :namespace_project_runtime_assignment_id,
Comment thread
raphael-goetz marked this conversation as resolved.
Types::GlobalIdType[::NamespaceProjectRuntimeAssignment],
description: 'The project runtime assignment to update.'

field :namespace_project_runtime_assignment, Types::NamespaceProjectRuntimeAssignmentType,
null: true,
description: 'The updated project runtime assignment.'

def resolve(namespace_project_runtime_assignment_id:, module_configurations:)
runtime_assignment = SagittariusSchema.object_from_id(namespace_project_runtime_assignment_id)

if runtime_assignment.nil?
return {
namespace_project_runtime_assignment: nil,
errors: [create_error(:runtime_not_assigned, 'Invalid project runtime assignment')],
}
end

::Namespaces::Projects::RuntimeAssignments::UpdateModuleConfigurationsService.new(
current_authentication,
runtime_assignment,
module_configurations
).execute.to_mutation_response(success_key: :namespace_project_runtime_assignment)
end
end
end
end
end
end
16 changes: 16 additions & 0 deletions app/graphql/types/input/module_configuration_input_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Types
module Input
class ModuleConfigurationInputType < Types::BaseInputObject
description 'Input type for saving a module configuration value.'

argument :module_configuration_definition_id, Types::GlobalIdType[::ModuleConfigurationDefinition],
required: true,
description: 'The configuration definition to save a value for.'
argument :value, GraphQL::Types::JSON,
required: false,
description: 'The saved configuration value.'
end
end
end
20 changes: 20 additions & 0 deletions app/graphql/types/module_configuration_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Types
class ModuleConfigurationType < Types::BaseObject
description 'Represents a saved module configuration value for a project runtime assignment.'

authorize :read_module_configuration

field :definition, Types::ModuleConfigurationDefinitionType,
null: false,
method: :module_configuration_definition,
description: 'The configuration definition this saved value belongs to.'
field :value, GraphQL::Types::JSON,
null: true,
description: 'The saved configuration value.'

id_field ModuleConfiguration
timestamps
end
end
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class MutationType < Types::BaseObject
mount_mutation Mutations::Namespaces::Projects::AssignRuntimes
mount_mutation Mutations::Namespaces::Projects::Create
mount_mutation Mutations::Namespaces::Projects::Delete
mount_mutation Mutations::Namespaces::Projects::RuntimeAssignments::UpdateModuleConfigurations
mount_mutation Mutations::Namespaces::Projects::Update
mount_mutation Mutations::Namespaces::Projects::Flows::Create
mount_mutation Mutations::Namespaces::Projects::Flows::Delete
Expand Down
18 changes: 18 additions & 0 deletions app/graphql/types/namespace_project_runtime_assignment_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Types
class NamespaceProjectRuntimeAssignmentType < Types::BaseObject
description 'Represents a runtime assignment for a project.'

authorize :read_namespace_project_runtime_assignment

field :compatible, Boolean, null: false, description: 'Whether the assigned runtime is compatible.'
field :module_configurations, Types::ModuleConfigurationType.connection_type,
null: false,
description: 'Saved module configuration values for this project runtime assignment.'
field :runtime, Types::RuntimeType, null: false, description: 'The assigned runtime.'

id_field NamespaceProjectRuntimeAssignment
timestamps
end
end
4 changes: 3 additions & 1 deletion app/graphql/types/namespace_project_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class NamespaceProjectType < Types::BaseObject

field :slug, String, null: false, description: 'Slug of the project used in URLs to identify flows'

Comment thread
raphael-goetz marked this conversation as resolved.
field :runtimes, Types::RuntimeType.connection_type, null: false, description: 'Runtimes assigned to this project'
field :runtime_assignments, Types::NamespaceProjectRuntimeAssignmentType.connection_type,
null: false,
description: 'Runtime assignments of this project.'

Comment thread
raphael-goetz marked this conversation as resolved.
field :roles, Types::NamespaceRoleType.connection_type, null: false,
description: 'Roles assigned to this project',
Expand Down
53 changes: 52 additions & 1 deletion app/grpc/flow_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ class FlowHandler < Tucana::Sagittarius::FlowService::Service
grpc_stream :update

def self.update_runtime(runtime)
assignments = runtime.project_assignments.compatible.includes(
:namespace_project,
module_configurations: { module_configuration_definition: :runtime_module }
)

flows = []
runtime.project_assignments.compatible.each do |assignment|
assignments.each do |assignment|
assignment.namespace_project.flows.validation_status_valid.each do |flow|
flows << flow.to_grpc
end
Expand All @@ -23,6 +28,15 @@ def self.update_runtime(runtime)
),
runtime.id
)

grouped_module_configurations(assignments).each do |module_configuration|
send_update(
Tucana::Sagittarius::FlowResponse.new(
module_configurations: module_configuration
),
runtime.id
)
end
end

def self.update_started(runtime_id)
Expand All @@ -34,6 +48,43 @@ def self.update_started(runtime_id)
update_runtime(runtime)
end

def self.grouped_module_configurations(assignments)
grouped_entries = assignments.flat_map do |assignment|
assignment.module_configurations.map do |configuration|
[
configuration.module_configuration_definition.runtime_module.identifier,
assignment,
configuration
]
end
end.group_by(&:first)

grouped_entries.sort_by(&:first).map do |module_identifier, entries|
Tucana::Shared::ModuleConfigurations.new(
module_identifier: module_identifier,
module_configurations: grouped_project_configurations(entries)
)
end
end

def self.grouped_project_configurations(entries)
entries.group_by { |_, assignment, _| assignment.id }
.sort_by { |_, grouped_entries| grouped_entries.first[1].namespace_project_id }
.map do |_, grouped_entries|
assignment = grouped_entries.first[1]
Tucana::Shared::ModuleProjectConfigurations.new(
project_id: assignment.namespace_project_id,
module_configurations: grpc_module_configurations(grouped_entries)
)
end
end

def self.grpc_module_configurations(entries)
entries.map(&:last)
.sort_by { |configuration| configuration.module_configuration_definition.identifier }
.map(&:to_grpc)
end

def self.encoders = { update: ->(grpc_object) { Tucana::Sagittarius::FlowResponse.encode(grpc_object) } }

def self.decoders = { update: ->(string) { Tucana::Sagittarius::FlowResponse.decode(string) } }
Expand Down
1 change: 1 addition & 0 deletions app/models/audit_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class AuditEvent < ApplicationRecord
password_reset: 37,
user_deleted: 38,
user_created: 39,
project_module_configurations_updated: 40,
}.with_indifferent_access

# rubocop:disable Lint/StructNewOverride
Expand Down
29 changes: 29 additions & 0 deletions app/models/module_configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

class ModuleConfiguration < ApplicationRecord
belongs_to :namespace_project_runtime_assignment, inverse_of: :module_configurations
belongs_to :module_configuration_definition, inverse_of: :module_configurations

validates :module_configuration_definition_id,
uniqueness: { scope: :namespace_project_runtime_assignment_id }
validate :validate_runtime

def to_grpc
Tucana::Shared::ModuleConfiguration.new(
identifier: module_configuration_definition.identifier,
value: Tucana::Shared::Value.from_ruby(value)
)
end

private

def validate_runtime
return if namespace_project_runtime_assignment.nil? || module_configuration_definition.nil?

definition_runtime_id = module_configuration_definition.runtime_module.runtime_id
assignment_runtime_id = namespace_project_runtime_assignment.runtime_id
return if definition_runtime_id == assignment_runtime_id

errors.add(:module_configuration_definition, 'must belong to the assigned runtime')
end
end
1 change: 1 addition & 0 deletions app/models/module_configuration_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ModuleConfigurationDefinition < ApplicationRecord

belongs_to :runtime_module, inverse_of: :module_configuration_definitions

has_many :module_configurations, inverse_of: :module_configuration_definition
has_many :module_configuration_definition_data_type_links, inverse_of: :module_configuration_definition
has_many :referenced_data_types,
through: :module_configuration_definition_data_type_links,
Expand Down
4 changes: 4 additions & 0 deletions app/models/namespace_project_runtime_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class NamespaceProjectRuntimeAssignment < ApplicationRecord
belongs_to :runtime, inverse_of: :project_assignments
belongs_to :namespace_project, inverse_of: :runtime_assignments

has_many :module_configurations,
inverse_of: :namespace_project_runtime_assignment,
dependent: :destroy
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated

validates :runtime, uniqueness: { scope: :namespace_project_id }

validate :validate_namespaces, if: :runtime_changed?
Expand Down
1 change: 1 addition & 0 deletions app/models/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Runtime < ApplicationRecord
has_many :primary_projects, class_name: 'NamespaceProject', inverse_of: :primary_runtime

has_many :runtime_modules, inverse_of: :runtime
has_many :module_configuration_definitions, through: :runtime_modules

has_many :data_types, inverse_of: :runtime

Expand Down
8 changes: 8 additions & 0 deletions app/policies/module_configuration_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class ModuleConfigurationPolicy < BasePolicy
delegate { subject.namespace_project_runtime_assignment }

rule { can?(:update_namespace_project_runtime_assignment) }.enable :read_module_configuration
rule { can?(:update_namespace_project_runtime_assignment) }.enable :update_module_configuration
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated
end
8 changes: 8 additions & 0 deletions app/policies/namespace_project_runtime_assignment_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class NamespaceProjectRuntimeAssignmentPolicy < BasePolicy
delegate { subject.namespace_project }

rule { can?(:read_namespace_project) }.enable :read_namespace_project_runtime_assignment
rule { can?(:assign_project_runtimes) }.enable :update_namespace_project_runtime_assignment
Comment thread
Taucher2003 marked this conversation as resolved.
Outdated
end
1 change: 1 addition & 0 deletions app/services/error_code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def self.error_codes
invalid_runtime_parameter_definition: { description: 'The runtime parameter definition is invalid' },
invalid_runtime_function_definition: { description: 'The runtime function definition is invalid' },
invalid_runtime_module: { description: 'The runtime module is invalid' },
invalid_module_configuration: { description: 'The module configuration is invalid because of active model errors' },
invalid_module_configuration_definition: { description: 'The module configuration definition is invalid' },
invalid_function_definition: { description: 'The function definition is invalid' },
invalid_parameter_definition: { description: 'The parameter definition is invalid' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

module Namespaces
module Projects
module RuntimeAssignments
class UpdateModuleConfigurationsService
include Sagittarius::Database::Transactional

attr_reader :current_authentication, :runtime_assignment, :module_configurations

def initialize(current_authentication, runtime_assignment, module_configurations)
@current_authentication = current_authentication
@runtime_assignment = runtime_assignment
@module_configurations = module_configurations
end

def execute
unless Ability.allowed?(
current_authentication,
:assign_project_runtimes,
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated
runtime_assignment.namespace_project
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated
)
return ServiceResponse.error(message: 'Missing permission', error_code: :missing_permission)
end

response = transactional do |t|
db_configurations = update_configurations(t)

AuditService.audit(
:project_module_configurations_updated,
author_id: current_authentication.user.id,
entity: runtime_assignment,
target: runtime_assignment.namespace_project,
details: {
runtime_assignment_id: runtime_assignment.id,
module_configurations: db_configurations.map do |configuration|
{
id: configuration.id,
module_configuration_definition_id: configuration.module_configuration_definition_id,
}
end,
Comment thread
raphael-goetz marked this conversation as resolved.
}
)

ServiceResponse.success(message: 'Updated module configurations', payload: runtime_assignment)
end

return response if response.error?

FlowHandler.update_runtime(runtime_assignment.runtime)
response
end

private

def update_configurations(t)
existing_configurations = runtime_assignment.module_configurations.index_by(
&:module_configuration_definition_id
)
db_configurations = []
kept_definition_ids = []

module_configurations.each do |configuration_input|
definition = runtime_assignment.runtime.module_configuration_definitions.find_by(
id: configuration_input.module_configuration_definition_id.model_id
)
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated

if definition.nil?
t.rollback_and_return! ServiceResponse.error(
message: 'Invalid module configuration definition',
error_code: :invalid_module_configuration_definition
)
end

db_configuration = existing_configurations[definition.id] || runtime_assignment.module_configurations.build
db_configuration.module_configuration_definition = definition
db_configuration.value = configuration_input.try(:value)

unless db_configuration.save
t.rollback_and_return! ServiceResponse.error(
message: 'Invalid module configuration',
error_code: :invalid_module_configuration,
details: db_configuration.errors
)
end

kept_definition_ids << definition.id
db_configurations << db_configuration
end

runtime_assignment.module_configurations
.where.not(module_configuration_definition_id: kept_definition_ids)
.destroy_all
Comment thread
raphael-goetz marked this conversation as resolved.
Outdated

db_configurations
end
end
end
end
end
Loading