Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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,42 @@
# 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],
required: true,
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
3 changes: 3 additions & 0 deletions app/graphql/types/namespace_project_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +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 :runtime_assignments, Types::NamespaceProjectRuntimeAssignmentType.connection_type,
null: false,
description: 'Runtime assignments of this project.'
field :runtimes, Types::RuntimeType.connection_type, null: false, description: 'Runtimes assigned to this project'

Comment thread
raphael-goetz marked this conversation as resolved.
field :roles, Types::NamespaceRoleType.connection_type, null: false,
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
3 changes: 3 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,9 @@ 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

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

validate :validate_namespaces, if: :runtime_changed?
Expand Down
1 change: 1 addition & 0 deletions app/models/namespace_role_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class NamespaceRoleAbility < ApplicationRecord
create_flow: { db: 24, description: 'Allows to create flows in a namespace project' },
delete_flow: { db: 25, description: 'Allows to delete flows in a namespace project' },
update_flow: { db: 26, description: 'Allows to update flows in the project' },
update_module_configurations: { db: 27, description: 'Allows to update module configurations in the project' },
}.with_indifferent_access
enum :ability, ABILITIES.transform_values { |v| v[:db] }, prefix: :can

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
10 changes: 9 additions & 1 deletion app/policies/concerns/customizable_permission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ def user_has_ability?(ability, user, subject)

roles = namespace_member(user, subject).roles

roles = roles.applicable_to_project(subject) if subject.is_a?(NamespaceProject)
project = project_scope(subject)
roles = roles.applicable_to_project(project) if project.present?

roles.joins(:abilities).exists?(namespace_role_abilities: { ability: ability })
end

def project_scope(subject)
return subject if subject.is_a?(NamespaceProject)
return subject.namespace_project if subject.respond_to?(:namespace_project)

subject.project if subject.respond_to?(:project)
end
end
end
7 changes: 7 additions & 0 deletions app/policies/module_configuration_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class ModuleConfigurationPolicy < BasePolicy
delegate { subject.namespace_project_runtime_assignment }

rule { can?(:read_namespace_project_runtime_assignment) }.enable :read_module_configuration
end
12 changes: 12 additions & 0 deletions app/policies/namespace_project_runtime_assignment_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

class NamespaceProjectRuntimeAssignmentPolicy < BasePolicy
include CustomizablePermission

delegate { subject.namespace_project }

namespace_resolver { |runtime_assignment| runtime_assignment.namespace_project.namespace }

rule { can?(:read_namespace_project) }.enable :read_namespace_project_runtime_assignment
customizable_permission :update_module_configurations
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
Loading