ruby_llm-agents 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
- data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
- data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
- data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
- data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
- data/app/models/ruby_llm/agents/execution.rb +50 -14
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
- data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
- data/app/models/ruby_llm/agents/tenant.rb +146 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
- data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
- data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
- data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
- data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
- data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
- data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
- data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
- data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
- data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
- data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
- data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
- data/config/routes.rb +1 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
- data/lib/ruby_llm/agents/core/configuration.rb +55 -43
- data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
- data/lib/ruby_llm/agents/pipeline.rb +69 -0
- data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
- data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
- data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
- data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
- data/lib/ruby_llm/agents/workflow/result.rb +202 -0
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
- data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
- metadata +43 -6
- data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
- data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
- data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
- data/lib/ruby_llm/agents/workflow/router.rb +0 -429
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
class Workflow
|
|
8
|
+
# Represents an approval request for human-in-the-loop workflows
|
|
9
|
+
#
|
|
10
|
+
# Tracks the state of an approval including who created it, who can approve it,
|
|
11
|
+
# and the final decision with timestamp and reason.
|
|
12
|
+
#
|
|
13
|
+
# @example Creating an approval
|
|
14
|
+
# approval = Approval.new(
|
|
15
|
+
# workflow_id: "order-123",
|
|
16
|
+
# workflow_type: "OrderApprovalWorkflow",
|
|
17
|
+
# name: :manager_approval,
|
|
18
|
+
# metadata: { order_total: 5000 }
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example Approving
|
|
22
|
+
# approval.approve!("manager@example.com")
|
|
23
|
+
#
|
|
24
|
+
# @example Rejecting
|
|
25
|
+
# approval.reject!("manager@example.com", reason: "Budget exceeded")
|
|
26
|
+
#
|
|
27
|
+
# @api public
|
|
28
|
+
class Approval
|
|
29
|
+
STATUSES = %i[pending approved rejected expired].freeze
|
|
30
|
+
|
|
31
|
+
attr_reader :id, :workflow_id, :workflow_type, :name, :status,
|
|
32
|
+
:created_at, :metadata, :approvers, :expires_at
|
|
33
|
+
attr_accessor :approved_by, :approved_at, :rejected_by, :rejected_at,
|
|
34
|
+
:reason, :reminded_at
|
|
35
|
+
|
|
36
|
+
# @param workflow_id [String] The workflow instance ID
|
|
37
|
+
# @param workflow_type [String] The workflow class name
|
|
38
|
+
# @param name [Symbol] The approval point name
|
|
39
|
+
# @param approvers [Array<String>] List of user IDs who can approve
|
|
40
|
+
# @param expires_at [Time, nil] When the approval expires
|
|
41
|
+
# @param metadata [Hash] Additional context for the approval
|
|
42
|
+
def initialize(workflow_id:, workflow_type:, name:, approvers: [], expires_at: nil, metadata: {})
|
|
43
|
+
@id = SecureRandom.uuid
|
|
44
|
+
@workflow_id = workflow_id
|
|
45
|
+
@workflow_type = workflow_type
|
|
46
|
+
@name = name
|
|
47
|
+
@status = :pending
|
|
48
|
+
@approvers = approvers
|
|
49
|
+
@expires_at = expires_at
|
|
50
|
+
@metadata = metadata
|
|
51
|
+
@created_at = Time.now
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Approve the request
|
|
55
|
+
#
|
|
56
|
+
# @param user_id [String] The user approving
|
|
57
|
+
# @param comment [String, nil] Optional comment
|
|
58
|
+
# @return [void]
|
|
59
|
+
def approve!(user_id, comment: nil)
|
|
60
|
+
raise InvalidStateError, "Cannot approve: status is #{status}" unless pending?
|
|
61
|
+
|
|
62
|
+
@status = :approved
|
|
63
|
+
@approved_by = user_id
|
|
64
|
+
@approved_at = Time.now
|
|
65
|
+
@metadata[:approval_comment] = comment if comment
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Reject the request
|
|
69
|
+
#
|
|
70
|
+
# @param user_id [String] The user rejecting
|
|
71
|
+
# @param reason [String, nil] Reason for rejection
|
|
72
|
+
# @return [void]
|
|
73
|
+
def reject!(user_id, reason: nil)
|
|
74
|
+
raise InvalidStateError, "Cannot reject: status is #{status}" unless pending?
|
|
75
|
+
|
|
76
|
+
@status = :rejected
|
|
77
|
+
@rejected_by = user_id
|
|
78
|
+
@rejected_at = Time.now
|
|
79
|
+
@reason = reason
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Expire the approval request
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
def expire!
|
|
86
|
+
raise InvalidStateError, "Cannot expire: status is #{status}" unless pending?
|
|
87
|
+
|
|
88
|
+
@status = :expired
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check if approval is still pending
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def pending?
|
|
95
|
+
status == :pending
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if approval was granted
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
def approved?
|
|
102
|
+
status == :approved
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Check if approval was rejected
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
def rejected?
|
|
109
|
+
status == :rejected
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Check if approval has expired
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def expired?
|
|
116
|
+
status == :expired
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Check if the approval has timed out
|
|
120
|
+
#
|
|
121
|
+
# @return [Boolean]
|
|
122
|
+
def timed_out?
|
|
123
|
+
return false unless expires_at
|
|
124
|
+
|
|
125
|
+
Time.now > expires_at && pending?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check if a user can approve this request
|
|
129
|
+
#
|
|
130
|
+
# @param user_id [String] The user to check
|
|
131
|
+
# @return [Boolean]
|
|
132
|
+
def can_approve?(user_id)
|
|
133
|
+
return true if approvers.empty? # Anyone can approve if no restrictions
|
|
134
|
+
|
|
135
|
+
approvers.include?(user_id)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Duration since creation
|
|
139
|
+
#
|
|
140
|
+
# @return [Float] Seconds since creation
|
|
141
|
+
def age
|
|
142
|
+
Time.now - created_at
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Duration until expiry
|
|
146
|
+
#
|
|
147
|
+
# @return [Float, nil] Seconds until expiry, nil if no expiry
|
|
148
|
+
def time_until_expiry
|
|
149
|
+
return nil unless expires_at
|
|
150
|
+
|
|
151
|
+
expires_at - Time.now
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Mark that a reminder was sent
|
|
155
|
+
#
|
|
156
|
+
# @return [void]
|
|
157
|
+
def mark_reminded!
|
|
158
|
+
@reminded_at = Time.now
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Check if a reminder should be sent
|
|
162
|
+
#
|
|
163
|
+
# @param reminder_after [Integer] Seconds after creation to send reminder
|
|
164
|
+
# @param reminder_interval [Integer, nil] Interval between reminders
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
def should_remind?(reminder_after, reminder_interval: nil)
|
|
167
|
+
return false unless pending?
|
|
168
|
+
return false if age < reminder_after
|
|
169
|
+
|
|
170
|
+
if reminded_at && reminder_interval
|
|
171
|
+
Time.now - reminded_at >= reminder_interval
|
|
172
|
+
else
|
|
173
|
+
reminded_at.nil?
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Convert to hash for serialization
|
|
178
|
+
#
|
|
179
|
+
# @return [Hash]
|
|
180
|
+
def to_h
|
|
181
|
+
{
|
|
182
|
+
id: id,
|
|
183
|
+
workflow_id: workflow_id,
|
|
184
|
+
workflow_type: workflow_type,
|
|
185
|
+
name: name,
|
|
186
|
+
status: status,
|
|
187
|
+
approvers: approvers,
|
|
188
|
+
approved_by: approved_by,
|
|
189
|
+
approved_at: approved_at,
|
|
190
|
+
rejected_by: rejected_by,
|
|
191
|
+
rejected_at: rejected_at,
|
|
192
|
+
reason: reason,
|
|
193
|
+
expires_at: expires_at,
|
|
194
|
+
reminded_at: reminded_at,
|
|
195
|
+
metadata: metadata,
|
|
196
|
+
created_at: created_at
|
|
197
|
+
}.compact
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Error for invalid state transitions
|
|
201
|
+
class InvalidStateError < StandardError; end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class Workflow
|
|
6
|
+
# Abstract base class for approval storage
|
|
7
|
+
#
|
|
8
|
+
# Provides a common interface for storing and retrieving approval requests.
|
|
9
|
+
# Implementations can use in-memory storage, databases, Redis, etc.
|
|
10
|
+
#
|
|
11
|
+
# @example Setting a custom store
|
|
12
|
+
# RubyLLM::Agents::Workflow::ApprovalStore.store = MyRedisStore.new
|
|
13
|
+
#
|
|
14
|
+
# @example Using the default store
|
|
15
|
+
# store = RubyLLM::Agents::Workflow::ApprovalStore.store
|
|
16
|
+
# store.save(approval)
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
class ApprovalStore
|
|
20
|
+
class << self
|
|
21
|
+
# Returns the configured store instance
|
|
22
|
+
#
|
|
23
|
+
# @return [ApprovalStore]
|
|
24
|
+
def store
|
|
25
|
+
@store ||= default_store
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Sets the store instance
|
|
29
|
+
#
|
|
30
|
+
# @param store [ApprovalStore] The store to use
|
|
31
|
+
# @return [void]
|
|
32
|
+
def store=(store)
|
|
33
|
+
@store = store
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Resets to the default store (useful for testing)
|
|
37
|
+
#
|
|
38
|
+
# @return [void]
|
|
39
|
+
def reset!
|
|
40
|
+
@store = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def default_store
|
|
46
|
+
MemoryApprovalStore.new
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Save an approval
|
|
51
|
+
#
|
|
52
|
+
# @param approval [Approval] The approval to save
|
|
53
|
+
# @return [Approval] The saved approval
|
|
54
|
+
def save(approval)
|
|
55
|
+
raise NotImplementedError, "#{self.class}#save must be implemented"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Find an approval by ID
|
|
59
|
+
#
|
|
60
|
+
# @param id [String] The approval ID
|
|
61
|
+
# @return [Approval, nil]
|
|
62
|
+
def find(id)
|
|
63
|
+
raise NotImplementedError, "#{self.class}#find must be implemented"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find all approvals for a workflow
|
|
67
|
+
#
|
|
68
|
+
# @param workflow_id [String] The workflow ID
|
|
69
|
+
# @return [Array<Approval>]
|
|
70
|
+
def find_by_workflow(workflow_id)
|
|
71
|
+
raise NotImplementedError, "#{self.class}#find_by_workflow must be implemented"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Find pending approvals for a user
|
|
75
|
+
#
|
|
76
|
+
# @param user_id [String] The user ID
|
|
77
|
+
# @return [Array<Approval>]
|
|
78
|
+
def pending_for_user(user_id)
|
|
79
|
+
raise NotImplementedError, "#{self.class}#pending_for_user must be implemented"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Find all pending approvals
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Approval>]
|
|
85
|
+
def all_pending
|
|
86
|
+
raise NotImplementedError, "#{self.class}#all_pending must be implemented"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Delete an approval
|
|
90
|
+
#
|
|
91
|
+
# @param id [String] The approval ID
|
|
92
|
+
# @return [Boolean] true if deleted
|
|
93
|
+
def delete(id)
|
|
94
|
+
raise NotImplementedError, "#{self.class}#delete must be implemented"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Delete all approvals (useful for testing)
|
|
98
|
+
#
|
|
99
|
+
# @return [void]
|
|
100
|
+
def clear!
|
|
101
|
+
raise NotImplementedError, "#{self.class}#clear! must be implemented"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# In-memory approval store for development and testing
|
|
106
|
+
#
|
|
107
|
+
# Thread-safe storage using a Mutex.
|
|
108
|
+
#
|
|
109
|
+
# @api public
|
|
110
|
+
class MemoryApprovalStore < ApprovalStore
|
|
111
|
+
def initialize
|
|
112
|
+
@approvals = {}
|
|
113
|
+
@mutex = Mutex.new
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @see ApprovalStore#save
|
|
117
|
+
def save(approval)
|
|
118
|
+
@mutex.synchronize do
|
|
119
|
+
@approvals[approval.id] = approval
|
|
120
|
+
end
|
|
121
|
+
approval
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @see ApprovalStore#find
|
|
125
|
+
def find(id)
|
|
126
|
+
@mutex.synchronize do
|
|
127
|
+
@approvals[id]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @see ApprovalStore#find_by_workflow
|
|
132
|
+
def find_by_workflow(workflow_id)
|
|
133
|
+
@mutex.synchronize do
|
|
134
|
+
@approvals.values.select { |a| a.workflow_id == workflow_id }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @see ApprovalStore#pending_for_user
|
|
139
|
+
def pending_for_user(user_id)
|
|
140
|
+
@mutex.synchronize do
|
|
141
|
+
@approvals.values.select do |a|
|
|
142
|
+
a.pending? && a.can_approve?(user_id)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @see ApprovalStore#all_pending
|
|
148
|
+
def all_pending
|
|
149
|
+
@mutex.synchronize do
|
|
150
|
+
@approvals.values.select(&:pending?)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @see ApprovalStore#delete
|
|
155
|
+
def delete(id)
|
|
156
|
+
@mutex.synchronize do
|
|
157
|
+
!!@approvals.delete(id)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @see ApprovalStore#clear!
|
|
162
|
+
def clear!
|
|
163
|
+
@mutex.synchronize do
|
|
164
|
+
@approvals.clear
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns the count of stored approvals
|
|
169
|
+
#
|
|
170
|
+
# @return [Integer]
|
|
171
|
+
def count
|
|
172
|
+
@mutex.synchronize do
|
|
173
|
+
@approvals.size
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|