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
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ gem 'good_job', '~> 4.0'
gem 'rotp'

gem 'grpc', '~> 1.67'
gem 'tucana', '0.0.58'
gem 'tucana', '0.0.62'

gem 'code0-identities', '~> 0.0.3'

Expand All @@ -91,3 +91,5 @@ gem 'code0-zero_track', '0.0.6'
gem 'image_processing', '>= 1.2'

gem 'json-schema', '~> 6.0'

gem 'triangulum', '0.5.2'
13 changes: 10 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ GEM
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.12.2)
json (2.19.2)
json-schema (6.1.0)
addressable (~> 2.8)
bigdecimal (>= 3.1, < 5)
Expand Down Expand Up @@ -203,6 +203,7 @@ GEM
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
open3 (0.2.1)
parallel (1.27.0)
parser (3.3.10.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -376,8 +377,13 @@ GEM
test-prof (1.5.0)
thor (1.4.0)
timeout (0.6.0)
triangulum (0.5.2)
base64 (~> 0.3)
json (~> 2.19)
open3 (~> 0.2)
tucana (~> 0.0, >= 0.0.62)
tsort (0.2.0)
tucana (0.0.58)
tucana (0.0.62)
grpc (~> 1.64)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -431,7 +437,8 @@ DEPENDENCIES
simplecov (~> 0.22.0)
simplecov-cobertura (~> 3.0)
test-prof (~> 1.0)
tucana (= 0.0.58)
triangulum (= 0.5.2)
tucana (= 0.0.62)
tzinfo-data

RUBY VERSION
Expand Down
6 changes: 5 additions & 1 deletion app/finders/data_types_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ def by_data_type(data_types)
def by_runtime_function_definition(data_types)
return data_types unless params[:runtime_function_definition]

data_types.where(id: params[:runtime_function_definition].referenced_data_types.pluck(:id))
referenced_data_types_ids = RuntimeFunctionDefinitionDataTypeLink
.where(runtime_function_definition: params[:runtime_function_definition])
.select(:referenced_data_type_id)

data_types.where(id: referenced_data_types_ids)
end

def by_flow_type_setting(data_types)
Expand Down
4 changes: 4 additions & 0 deletions app/graphql/types/flow_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class FlowType < Types::BaseObject

field :name, String, null: false, description: 'Name of the flow'

field :validation_status, Types::FlowValidationStatusEnum,
null: false,
description: 'The validation status of the flow'

field :input_type, String,
null: true,
description: 'The input data type of the flow'
Expand Down
11 changes: 11 additions & 0 deletions app/graphql/types/flow_validation_status_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Types
class FlowValidationStatusEnum < Types::BaseEnum
description 'The validation status of a flow.'

value :UNVALIDATED, 'The flow has not been validated yet.', value: 'unvalidated'
value :VALID, 'The flow has been validated and is valid.', value: 'valid'
value :INVALID, 'The flow has been validated and is invalid.', value: 'invalid'
end
end
2 changes: 1 addition & 1 deletion app/grpc/flow_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.each do |flow|
assignment.namespace_project.flows.validation_status_valid.each do |flow|
flows << flow.to_grpc
end
end
Expand Down
10 changes: 10 additions & 0 deletions app/jobs/flow_validation_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class FlowValidationJob < ApplicationJob
def perform(flow_id)
flow = Flow.find_by(id: flow_id)
return if flow.nil?

Namespaces::Projects::Flows::ValidationService.new(flow).execute
end
end
14 changes: 14 additions & 0 deletions app/models/data_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,18 @@ def validate_version
def parsed_version
Gem::Version.new(version)
end

def to_grpc
Tucana::Shared::DefinitionDataType.new(
identifier: identifier,
name: names.map(&:to_grpc),
display_message: display_messages.map(&:to_grpc),
alias: aliases.map(&:to_grpc),
rules: rules.map(&:to_grpc),
generic_keys: generic_keys,
type: type,
linked_data_type_identifiers: referenced_data_types.map(&:identifier),
version: version
)
end
end
4 changes: 4 additions & 0 deletions app/models/data_type_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ class DataTypeRule < ApplicationRecord
filename: 'data_types/RegexRuleConfig',
hash_conversion: true,
}

def to_grpc
Tucana::Shared::DefinitionDataTypeRule.create(variant.to_sym, config)
end
end
14 changes: 14 additions & 0 deletions app/models/flow.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
# frozen_string_literal: true

class Flow < ApplicationRecord
VALIDATION_STATUS = {
unvalidated: 0,
valid: 1,
invalid: 2,
}.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

has_many :flow_settings, class_name: 'FlowSetting', inverse_of: :flow
has_many :node_functions, class_name: 'NodeFunction', inverse_of: :flow

has_many :flow_data_type_links, inverse_of: :flow
has_many :referenced_data_types, through: :flow_data_type_links, source: :referenced_data_type

validates :validation_status,
presence: true,
inclusion: {
in: VALIDATION_STATUS.keys.map(&:to_s),
}

validates :name, presence: true,
allow_blank: false,
uniqueness: { case_sensitive: false, scope: :project_id }
Expand Down
4 changes: 4 additions & 0 deletions app/models/function_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ class FunctionDefinition < ApplicationRecord
has_many :display_messages, -> { by_purpose(:display_message) },
class_name: 'Translation', as: :owner, inverse_of: :owner
has_many :aliases, -> { by_purpose(:alias) }, class_name: 'Translation', as: :owner, inverse_of: :owner

delegate :to_grpc, to: :runtime_function_definition

scope :by_node_function, ->(node_functions) { where(node_functions: node_functions) }
end
17 changes: 17 additions & 0 deletions app/models/runtime_function_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,21 @@ def validate_version
def parsed_version
Gem::Version.new(version)
end

def to_grpc
Tucana::Shared::RuntimeFunctionDefinition.new(
runtime_name: runtime_name,
runtime_parameter_definitions: parameters.map(&:to_grpc),
signature: signature,
throws_error: throws_error,
name: names.map(&:to_grpc),
description: descriptions.map(&:to_grpc),
documentation: documentations.map(&:to_grpc),
deprecation_message: deprecation_messages.map(&:to_grpc),
display_message: display_messages.map(&:to_grpc),
alias: aliases.map(&:to_grpc),
linked_data_type_identifiers: referenced_data_types.map(&:identifier),
version: version
)
end
end
10 changes: 10 additions & 0 deletions app/models/runtime_parameter_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,14 @@ class RuntimeParameterDefinition < ApplicationRecord

validates :runtime_name, length: { minimum: 3, maximum: 50 }, presence: true,
uniqueness: { case_sensitive: false, scope: :runtime_function_definition_id }

def to_grpc
Tucana::Shared::RuntimeParameterDefinition.new(
runtime_name: runtime_name,
default_value: Tucana::Shared::Value.from_ruby(default_value),
name: names.map(&:to_grpc),
description: descriptions.map(&:to_grpc),
documentation: documentations.map(&:to_grpc)
)
end
end
7 changes: 7 additions & 0 deletions app/models/translation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ class Translation < ApplicationRecord
validates :content, presence: true

scope :by_purpose, ->(purpose) { where(purpose: purpose) }

def to_grpc
Tucana::Shared::Translation.new(
code: code,
content: content
)
end
end
3 changes: 2 additions & 1 deletion app/services/namespaces/projects/flows/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def update_flow(t)
)
end

UpdateRuntimesForProjectJob.perform_later(flow.project.id)
FlowValidationJob.perform_later(flow.id)
end

private
Expand All @@ -50,6 +50,7 @@ def update_flow_attributes
flow.name = flow_input.name
flow.input_type = flow_input.input_type
flow.return_type = flow_input.return_type
flow.validation_status = :unvalidated
end

def update_settings(t)
Expand Down
41 changes: 41 additions & 0 deletions app/services/namespaces/projects/flows/validation_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Namespaces
module Projects
module Flows
class ValidationService
attr_reader :flow

def initialize(flow)
@flow = flow
end

def execute
function_definitions = FunctionDefinition
.by_node_function(flow.node_functions)
.preload(:runtime_function_definition)
data_types = DataTypesFinder.new(
{
runtime_function_definition: function_definitions.map(&:runtime_function_definition),
expand_recursively: true,
}
).execute

result = Triangulum::Validation.new(
flow.to_grpc,
function_definitions.map(&:to_grpc),
data_types.map(&:to_grpc)
).validate

if result.valid?
flow.update!(validation_status: :valid)
else
flow.update!(validation_status: :invalid)
end

UpdateRuntimesForProjectJob.perform_later(flow.project.id)
end
end
end
end
end
7 changes: 7 additions & 0 deletions db/migrate/20260324182151_add_validation_status_to_flows.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddValidationStatusToFlows < Code0::ZeroTrack::Database::Migration[1.0]
def change
add_column :flows, :validation_status, :integer, null: false, default: 0
end
end
1 change: 1 addition & 0 deletions db/schema_migrations/20260324182151
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2ce443053ded376e78875486212da5a7ecdda429d0dd1434749bceffa4fc18dd
1 change: 1 addition & 0 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ CREATE TABLE flows (
updated_at timestamp with time zone NOT NULL,
input_type text,
return_type text,
validation_status integer DEFAULT 0 NOT NULL,
CONSTRAINT check_1c805d704f CHECK ((char_length(input_type) <= 2000)),
CONSTRAINT check_b2f3f83908 CHECK ((char_length(return_type) <= 2000))
);
Expand Down
11 changes: 11 additions & 0 deletions docs/graphql/enum/flowvalidationstatus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: FlowValidationStatus
---

The validation status of a flow.

| Value | Description |
|-------|-------------|
| `INVALID` | The flow has been validated and is invalid. |
| `UNVALIDATED` | The flow has not been validated yet. |
| `VALID` | The flow has been validated and is valid. |
1 change: 1 addition & 0 deletions docs/graphql/object/flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ Represents a flow
| `type` | [`FlowType!`](../object/flowtype.md) | The flow type of the flow |
| `updatedAt` | [`Time!`](../scalar/time.md) | Time when this Flow was last updated |
| `userAbilities` | [`FlowUserAbilities!`](../object/flowuserabilities.md) | Abilities for the current user on this Flow |
| `validationStatus` | [`FlowValidationStatus!`](../enum/flowvalidationstatus.md) | The validation status of the flow |

1 change: 1 addition & 0 deletions spec/factories/flows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
factory :flow do
project factory: :namespace_project
flow_type
validation_status { :unvalidated }
starting_node { nil }
flow_settings { [] }
input_type { 'string' }
Expand Down
29 changes: 29 additions & 0 deletions spec/jobs/flow_validation_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe FlowValidationJob do
include ActiveJob::TestHelper

let(:flow) { create(:flow) }

it 'calls the validation service' do
service = instance_double(Namespaces::Projects::Flows::ValidationService)
allow(Namespaces::Projects::Flows::ValidationService).to receive(:new).with(flow).and_return(service)
allow(service).to receive(:execute)

perform_enqueued_jobs do
described_class.perform_later(flow.id)
end

expect(service).to have_received(:execute)
end

it 'does not raise when flow does not exist' do
expect do
perform_enqueued_jobs do
described_class.perform_later(-1)
end
end.not_to raise_error
end
end
6 changes: 6 additions & 0 deletions spec/models/data_type_rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,10 @@
end
end
end

describe '#to_grpc' do
it 'returns a grpc rule' do
expect(rule.to_grpc).to be_a(Tucana::Shared::DefinitionDataTypeRule)
end
end
end
Loading