Skip to content
Open
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
6 changes: 6 additions & 0 deletions server/app/models/agents/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class Workflow < ApplicationRecord
has_many :workflow_runs, dependent: :destroy
has_many :visual_components, as: :configurable, dependent: :destroy
has_one :workflow_integration, dependent: :destroy
<<<<<<< HEAD
=======
has_many :workflow_sessions, dependent: :destroy
has_many :p2w_sessions, class_name: "P2w::Session", dependent: :destroy
has_many :workflow_files, class_name: "Agents::WorkflowFile", dependent: :destroy
>>>>>>> 46ca3c401 (feat(CE): prompt to workflow v2 models (#1803))

enum status: { draft: 0, published: 1 }
enum trigger_type: { website_chatbot: 0, chat_assistant: 1, scheduled: 2, api_trigger: 3, slack: 4 }
Expand Down
39 changes: 39 additions & 0 deletions server/app/models/p2w/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module P2w
class Session < ApplicationRecord
self.table_name = "prompt_to_workflow_sessions"

TERMINAL_STATUSES = %w[completed failed max_turns expired].freeze

belongs_to :workflow, class_name: "Agents::Workflow"
belongs_to :workspace
has_many :events, class_name: "P2w::SessionEvent",
foreign_key: :prompt_to_workflow_session_id,
inverse_of: :session,
dependent: :destroy

validates :session_id, presence: true, uniqueness: true
validates :workflow_id, :workspace_id, :status, :expires_at, presence: true
validates :status, inclusion: { in: %w[running clarification_pending completed failed max_turns expired] }

scope :active, -> { where(status: %w[running clarification_pending]) }
scope :not_expired, -> { where("expires_at > ?", Time.current) }

def terminal?
status.in?(TERMINAL_STATUSES)
end

def replayable?
expires_at > Time.current
end

def accepts_clarification?
status == "clarification_pending" && !expired?
end

def expired?
expires_at <= Time.current
end
end
end
16 changes: 16 additions & 0 deletions server/app/models/p2w/session_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module P2w
class SessionEvent < ApplicationRecord
self.table_name = "prompt_to_workflow_session_events"

belongs_to :session, class_name: "P2w::Session",
foreign_key: :prompt_to_workflow_session_id,
inverse_of: :events

validates :sequence, presence: true,
uniqueness: { scope: :prompt_to_workflow_session_id },
numericality: { greater_than_or_equal_to: 0 }
validates :event_type, presence: true
end
end
11 changes: 11 additions & 0 deletions server/app/models/workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ class Workspace < ApplicationRecord
has_many :workflow_runs, class_name: "Agents::WorkflowRun", dependent: :destroy
has_many :workflow_logs, class_name: "Agents::WorkflowLog", dependent: :nullify
has_many :workflow_integrations, class_name: "Agents::WorkflowIntegration", dependent: :nullify
<<<<<<< HEAD
=======
has_many :workflow_sessions, class_name: "Agents::WorkflowSession", dependent: :nullify
has_many :p2w_sessions, class_name: "P2w::Session", dependent: :destroy
has_many :hosted_data_stores, dependent: :nullify
has_many :knowledge_bases, class_name: "Agents::KnowledgeBase", dependent: :nullify
has_many :tools, class_name: "Agents::Tool", dependent: :destroy
has_many :llm_routing_logs, dependent: :destroy
has_many :llm_usage_logs, dependent: :destroy
has_many :agentic_coding_apps, class_name: "AgenticCoding::App", dependent: :destroy
>>>>>>> 46ca3c401 (feat(CE): prompt to workflow v2 models (#1803))

belongs_to :organization
has_many :sso_configurations, through: :organization
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

class CreatePromptToWorkflowSessions < ActiveRecord::Migration[7.1]
def change
create_table :prompt_to_workflow_sessions do |t|
t.uuid :session_id, null: false
t.uuid :workflow_id, null: false
t.integer :workspace_id, null: false
t.string :status, null: false, default: "running"
t.uuid :current_clarification_id
t.jsonb :state, null: false, default: {}
t.string :temporal_workflow_id
t.string :temporal_run_id
t.datetime :expires_at, null: false
t.timestamps
end

add_index :prompt_to_workflow_sessions, :session_id, unique: true
add_index :prompt_to_workflow_sessions, :workflow_id
add_index :prompt_to_workflow_sessions, :workspace_id
add_index :prompt_to_workflow_sessions, %i[workspace_id status], name: "idx_p2w_sessions_workspace_status"
add_index :prompt_to_workflow_sessions, %i[status expires_at], name: "idx_p2w_sessions_status_expires"
add_index :prompt_to_workflow_sessions, :expires_at

add_foreign_key :prompt_to_workflow_sessions, :workflows, column: :workflow_id, validate: false
add_foreign_key :prompt_to_workflow_sessions, :workspaces, column: :workspace_id, validate: false
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

class CreatePromptToWorkflowSessionEvents < ActiveRecord::Migration[7.1]
def change
create_table :prompt_to_workflow_session_events do |t|
t.references :prompt_to_workflow_session, null: false, foreign_key: true
t.integer :sequence, null: false
t.string :event_type, null: false
t.jsonb :payload, null: false, default: {}
t.datetime :created_at, null: false
end

add_index :prompt_to_workflow_session_events,
%i[prompt_to_workflow_session_id sequence],
unique: true,
name: "idx_p2w_events_session_sequence"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class AddCurrentTurnToPromptToWorkflowSessions < ActiveRecord::Migration[7.1]
def change
return if column_exists?(:prompt_to_workflow_sessions, :current_turn)

add_column :prompt_to_workflow_sessions, :current_turn, :integer, null: false, default: 0
end
end
47 changes: 47 additions & 0 deletions server/db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions server/spec/factories/p2w_sessions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

FactoryBot.define do
factory :p2w_session, class: "P2w::Session" do
workspace
workflow
session_id { SecureRandom.uuid }
status { "running" }
expires_at { 30.minutes.from_now }
state { {} }
end
end
5 changes: 5 additions & 0 deletions server/spec/models/agents/workflow_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
it { should have_many(:edges).dependent(:destroy) }
it { should have_many(:workflow_runs).dependent(:destroy) }
it { should have_one(:workflow_integration).dependent(:destroy) }
<<<<<<< HEAD
=======
it { should have_many(:p2w_sessions).class_name("P2w::Session").dependent(:destroy) }
it { should have_many(:versions) }
>>>>>>> 46ca3c401 (feat(CE): prompt to workflow v2 models (#1803))
end

describe "enums" do
Expand Down
39 changes: 39 additions & 0 deletions server/spec/models/p2w/session_event_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe P2w::SessionEvent do
let(:workspace) { create(:workspace) }
let(:workflow) { create(:workflow, workspace:) }
let(:session) do
P2w::Session.create!(session_id: SecureRandom.uuid, workflow:,
workspace:, status: "running", expires_at: 30.minutes.from_now)
end

describe "validations" do
it "requires sequence, event_type" do
event = described_class.new(prompt_to_workflow_session_id: session.id)
event.valid?
expect(event.errors[:sequence]).not_to be_empty
expect(event.errors[:event_type]).to include("can't be blank")
end

it "enforces unique sequence per session" do
described_class.create!(prompt_to_workflow_session_id: session.id, sequence: 0, event_type: "test")
dup = described_class.new(prompt_to_workflow_session_id: session.id, sequence: 0, event_type: "test2")
expect(dup).not_to be_valid
end

it "rejects negative sequence" do
event = described_class.new(prompt_to_workflow_session_id: session.id, sequence: -1, event_type: "test")
expect(event).not_to be_valid
end
end

describe "associations" do
it "belongs to session" do
event = described_class.create!(prompt_to_workflow_session_id: session.id, sequence: 0, event_type: "test")
expect(event.session).to eq(session)
end
end
end
88 changes: 88 additions & 0 deletions server/spec/models/p2w/session_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe P2w::Session do
let(:workspace) { create(:workspace) }
let(:workflow) { create(:workflow, workspace:) }

describe "validations" do
it "requires session_id and expires_at" do
session = described_class.new
session.valid?
expect(session.errors[:session_id]).to include("can't be blank")
expect(session.errors[:expires_at]).to include("can't be blank")
end

it "enforces unique session_id" do
uid = SecureRandom.uuid
described_class.create!(session_id: uid, workflow:, workspace:,
expires_at: 30.minutes.from_now)
dup = described_class.new(session_id: uid, workflow:, workspace:,
expires_at: 30.minutes.from_now)
expect(dup).not_to be_valid
expect(dup.errors[:session_id]).to include("has already been taken")
end

it "validates status inclusion" do
session = described_class.new(status: "invalid_status")
session.valid?
expect(session.errors[:status]).to include("is not included in the list")
end
end

describe "scopes" do
let!(:running_session) do
described_class.create!(session_id: SecureRandom.uuid, workflow:, workspace:,
status: "running", expires_at: 30.minutes.from_now)
end
let!(:completed_session) do
described_class.create!(session_id: SecureRandom.uuid, workflow:, workspace:,
status: "completed", expires_at: 30.minutes.from_now)
end
let!(:expired_session) do
described_class.create!(session_id: SecureRandom.uuid, workflow:, workspace:,
status: "running", expires_at: 1.minute.ago)
end

it ".active returns running and clarification_pending" do
expect(described_class.active).to include(running_session)
expect(described_class.active).not_to include(completed_session)
end

it ".not_expired excludes expired sessions" do
expect(described_class.not_expired).to include(running_session)
expect(described_class.not_expired).not_to include(expired_session)
end
end

describe "helper methods" do
let(:session) do
described_class.new(status: "running", expires_at: 30.minutes.from_now)
end

it "#terminal? returns true for terminal statuses" do
expect(described_class.new(status: "completed")).to be_terminal
expect(described_class.new(status: "failed")).to be_terminal
expect(described_class.new(status: "running")).not_to be_terminal
end

it "#expired? checks expires_at" do
expect(described_class.new(expires_at: 1.minute.ago)).to be_expired
expect(described_class.new(expires_at: 30.minutes.from_now)).not_to be_expired
end

it "#replayable? returns true when not expired" do
expect(described_class.new(expires_at: 30.minutes.from_now)).to be_replayable
expect(described_class.new(expires_at: 1.minute.ago)).not_to be_replayable
end

it "#accepts_clarification? requires clarification_pending and not expired" do
session = described_class.new(status: "clarification_pending", expires_at: 30.minutes.from_now)
expect(session.accepts_clarification?).to be true

session.status = "running"
expect(session.accepts_clarification?).to be false
end
end
end
Loading