geneva_drive 0.7.0 → 0.9.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/CHANGELOG.md +21 -0
- data/MANUAL.md +299 -5
- data/README.md +14 -1
- data/Rakefile +28 -1
- data/bin/matrix +76 -0
- data/bin/md2html +114 -0
- data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +5 -9
- data/lib/generators/geneva_drive/install/templates/add_signals_support.rb +5 -10
- data/lib/generators/geneva_drive/install/templates/create_workflows_migration.rb +1 -1
- data/lib/generators/geneva_drive/install/templates/initializer.rb.tt +33 -2
- data/lib/geneva_drive/dispatchers/active_job_dispatcher.rb +33 -0
- data/lib/geneva_drive/dispatchers/sidekiq_dispatcher.rb +112 -0
- data/lib/geneva_drive/engine.rb +13 -0
- data/lib/geneva_drive/executor.rb +23 -3
- data/lib/geneva_drive/job_options.rb +7 -3
- data/lib/geneva_drive/jobs/perform_step_job.rb +2 -35
- data/lib/geneva_drive/jobs/sidekiq_housekeeping_job.rb +32 -0
- data/lib/geneva_drive/jobs/sidekiq_perform_step_job.rb +35 -0
- data/lib/geneva_drive/jobs/step_execution_lookup.rb +42 -0
- data/lib/geneva_drive/json_column.rb +72 -0
- data/lib/geneva_drive/migration_helpers.rb +60 -3
- data/lib/geneva_drive/removed_step_definition.rb +34 -0
- data/lib/geneva_drive/signal.rb +4 -2
- data/lib/geneva_drive/step_collection.rb +31 -7
- data/lib/geneva_drive/step_definition.rb +9 -0
- data/lib/geneva_drive/step_execution.rb +3 -2
- data/lib/geneva_drive/version.rb +1 -1
- data/lib/geneva_drive/workflow.rb +200 -39
- data/lib/geneva_drive.rb +65 -0
- data/test/dummy/config/database.yml +40 -5
- data/test/dummy/config/initializers/geneva_drive.rb +78 -0
- data/test/dummy/db/mysql/schema-8.1.rb +95 -0
- data/test/dummy/db/postgres/schema-8.1.rb +99 -0
- data/test/dummy/db/sqlite/schema-7.2.rb +96 -0
- data/test/dummy/db/sqlite/schema-8.0.rb +96 -0
- data/test/dummy/db/sqlite/schema-8.1.rb +96 -0
- data/test/dummy/db/sqlite/test.sqlite3 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-0 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-1 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-2 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-3 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-4 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-5 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-6 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-7 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-8 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3-9 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_0 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_1 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_2 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_3 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_4 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_5 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_6 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_7 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_8 +0 -0
- data/test/dummy/db/sqlite/test.sqlite3_9 +0 -0
- data/test/dummy/db/sqlite3/schema-7.2.rb +96 -0
- data/test/dummy/db/sqlite3/schema-8.1.rb +96 -0
- data/test/dummy/db/sqlite3/test.sqlite3 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_0 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_1 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_2 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_3 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_4 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_5 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_6 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_7 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_8 +0 -0
- data/test/dummy/db/sqlite3/test.sqlite3_9 +0 -0
- data/test/dummy_install/db/migrate/20260217112949_create_geneva_drive_workflows.rb +76 -0
- data/test/dummy_install/db/migrate/20260217112950_create_geneva_drive_step_executions.rb +99 -0
- data/test/dummy_install/db/migrate/20260217112951_add_finished_at_to_geneva_drive_step_executions.rb +27 -0
- data/test/dummy_install/db/migrate/20260217112952_add_error_class_name_to_geneva_drive_step_executions.rb +9 -0
- data/test/dummy_install/db/migrate/20260217112953_add_resumable_step_support_to_geneva_drive_step_executions.rb +128 -0
- data/test/dummy_install/db/migrate/20260217112954_add_chained_step_executions_to_geneva_drive_step_executions.rb +136 -0
- data/test/dummy_install/db/schema.rb +75 -0
- data/test/dummy_install/log/test.log +1852 -0
- data/test/generators/install_generator_test.rb +56 -0
- data/test/geneva_drive_test.rb +10 -0
- data/test/migration_helpers_test.rb +62 -0
- data/test/sidekiq/sidekiq_dispatcher_test.rb +322 -0
- data/test/sidekiq/sidekiq_redis_test.rb +115 -0
- data/test/test_helper.rb +51 -13
- data/test/workflow/pause_resume_test.rb +15 -0
- data/test/workflow/removed_step_test.rb +294 -0
- data/test/workflow/resume_at_test.rb +170 -0
- metadata +62 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
4
|
+
#
|
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
9
|
+
# migrations use external dependencies or application code.
|
|
10
|
+
#
|
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Schema[8.1].define(version: 2026_10_25_000614) do
|
|
14
|
+
create_table "geneva_drive_signals", force: :cascade do |t|
|
|
15
|
+
t.integer "workflow_id", null: false
|
|
16
|
+
t.string "name", null: false
|
|
17
|
+
t.string "idempotency_key"
|
|
18
|
+
t.string "state", default: "pending", null: false
|
|
19
|
+
t.json "payload"
|
|
20
|
+
t.datetime "claimed_at"
|
|
21
|
+
t.datetime "consumed_at"
|
|
22
|
+
t.bigint "claimed", default: 0, null: false
|
|
23
|
+
t.bigint "consumed", default: 0, null: false
|
|
24
|
+
t.datetime "created_at", null: false
|
|
25
|
+
t.datetime "updated_at", null: false
|
|
26
|
+
t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
|
|
27
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
|
|
28
|
+
t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
create_table "geneva_drive_step_executions", force: :cascade do |t|
|
|
32
|
+
t.integer "workflow_id", null: false
|
|
33
|
+
t.string "step_name", null: false
|
|
34
|
+
t.string "state", default: "scheduled", null: false
|
|
35
|
+
t.string "outcome"
|
|
36
|
+
t.datetime "scheduled_for", null: false
|
|
37
|
+
t.datetime "started_at"
|
|
38
|
+
t.datetime "completed_at"
|
|
39
|
+
t.datetime "failed_at"
|
|
40
|
+
t.datetime "canceled_at"
|
|
41
|
+
t.datetime "skipped_at"
|
|
42
|
+
t.string "error_class_name"
|
|
43
|
+
t.text "error_message"
|
|
44
|
+
t.text "error_backtrace"
|
|
45
|
+
t.string "job_id"
|
|
46
|
+
t.text "metadata"
|
|
47
|
+
t.datetime "created_at", null: false
|
|
48
|
+
t.datetime "updated_at", null: false
|
|
49
|
+
t.datetime "finished_at"
|
|
50
|
+
t.json "cursor"
|
|
51
|
+
t.bigint "continues_from_id"
|
|
52
|
+
t.bigint "signal_id"
|
|
53
|
+
t.datetime "waiting_since"
|
|
54
|
+
t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
|
|
55
|
+
t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
|
|
56
|
+
t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
|
|
57
|
+
t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
|
|
58
|
+
t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "state = 'in_progress'"
|
|
59
|
+
t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
|
|
60
|
+
t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
|
|
61
|
+
t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
|
|
62
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
|
|
63
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
|
|
64
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "state IN ('scheduled', 'in_progress')"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
create_table "geneva_drive_workflows", force: :cascade do |t|
|
|
68
|
+
t.string "type", null: false
|
|
69
|
+
t.string "hero_type"
|
|
70
|
+
t.integer "hero_id"
|
|
71
|
+
t.string "state", default: "ready", null: false
|
|
72
|
+
t.string "current_step_name"
|
|
73
|
+
t.string "next_step_name"
|
|
74
|
+
t.boolean "allow_multiple", default: false, null: false
|
|
75
|
+
t.text "metadata"
|
|
76
|
+
t.datetime "started_at"
|
|
77
|
+
t.datetime "transitioned_at"
|
|
78
|
+
t.datetime "created_at", null: false
|
|
79
|
+
t.datetime "updated_at", null: false
|
|
80
|
+
t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
|
|
81
|
+
t.index ["state"], name: "index_geneva_drive_workflows_on_state"
|
|
82
|
+
t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "state NOT IN ('finished', 'canceled') AND allow_multiple = 0"
|
|
83
|
+
t.index ["type"], name: "index_geneva_drive_workflows_on_type"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_table "users", force: :cascade do |t|
|
|
87
|
+
t.string "email", null: false
|
|
88
|
+
t.string "name"
|
|
89
|
+
t.boolean "active", default: true
|
|
90
|
+
t.datetime "created_at", null: false
|
|
91
|
+
t.datetime "updated_at", null: false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
95
|
+
add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
96
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
4
|
+
#
|
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
9
|
+
# migrations use external dependencies or application code.
|
|
10
|
+
#
|
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Schema[7.2].define(version: 2026_10_25_090462) do
|
|
14
|
+
create_table "geneva_drive_signals", force: :cascade do |t|
|
|
15
|
+
t.integer "workflow_id", null: false
|
|
16
|
+
t.string "name", null: false
|
|
17
|
+
t.string "idempotency_key"
|
|
18
|
+
t.string "state", default: "pending", null: false
|
|
19
|
+
t.json "payload"
|
|
20
|
+
t.datetime "claimed_at"
|
|
21
|
+
t.datetime "consumed_at"
|
|
22
|
+
t.bigint "claimed", default: 0, null: false
|
|
23
|
+
t.bigint "consumed", default: 0, null: false
|
|
24
|
+
t.datetime "created_at", null: false
|
|
25
|
+
t.datetime "updated_at", null: false
|
|
26
|
+
t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
|
|
27
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
|
|
28
|
+
t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
create_table "geneva_drive_step_executions", force: :cascade do |t|
|
|
32
|
+
t.integer "workflow_id", null: false
|
|
33
|
+
t.string "step_name", null: false
|
|
34
|
+
t.string "state", default: "scheduled", null: false
|
|
35
|
+
t.string "outcome"
|
|
36
|
+
t.datetime "scheduled_for", null: false
|
|
37
|
+
t.datetime "started_at"
|
|
38
|
+
t.datetime "completed_at"
|
|
39
|
+
t.datetime "failed_at"
|
|
40
|
+
t.datetime "canceled_at"
|
|
41
|
+
t.datetime "skipped_at"
|
|
42
|
+
t.string "error_class_name"
|
|
43
|
+
t.text "error_message"
|
|
44
|
+
t.text "error_backtrace"
|
|
45
|
+
t.string "job_id"
|
|
46
|
+
t.text "metadata"
|
|
47
|
+
t.datetime "created_at", null: false
|
|
48
|
+
t.datetime "updated_at", null: false
|
|
49
|
+
t.datetime "finished_at"
|
|
50
|
+
t.json "cursor"
|
|
51
|
+
t.bigint "continues_from_id"
|
|
52
|
+
t.bigint "signal_id"
|
|
53
|
+
t.datetime "waiting_since"
|
|
54
|
+
t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
|
|
55
|
+
t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
|
|
56
|
+
t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
|
|
57
|
+
t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
|
|
58
|
+
t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "state = 'in_progress'"
|
|
59
|
+
t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
|
|
60
|
+
t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
|
|
61
|
+
t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
|
|
62
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
|
|
63
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
|
|
64
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "state IN ('scheduled', 'in_progress')"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
create_table "geneva_drive_workflows", force: :cascade do |t|
|
|
68
|
+
t.string "type", null: false
|
|
69
|
+
t.string "hero_type"
|
|
70
|
+
t.integer "hero_id"
|
|
71
|
+
t.string "state", default: "ready", null: false
|
|
72
|
+
t.string "current_step_name"
|
|
73
|
+
t.string "next_step_name"
|
|
74
|
+
t.boolean "allow_multiple", default: false, null: false
|
|
75
|
+
t.text "metadata"
|
|
76
|
+
t.datetime "started_at"
|
|
77
|
+
t.datetime "transitioned_at"
|
|
78
|
+
t.datetime "created_at", null: false
|
|
79
|
+
t.datetime "updated_at", null: false
|
|
80
|
+
t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
|
|
81
|
+
t.index ["state"], name: "index_geneva_drive_workflows_on_state"
|
|
82
|
+
t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "state NOT IN ('finished', 'canceled') AND allow_multiple = 0"
|
|
83
|
+
t.index ["type"], name: "index_geneva_drive_workflows_on_type"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_table "users", force: :cascade do |t|
|
|
87
|
+
t.string "email", null: false
|
|
88
|
+
t.string "name"
|
|
89
|
+
t.boolean "active", default: true
|
|
90
|
+
t.datetime "created_at", null: false
|
|
91
|
+
t.datetime "updated_at", null: false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
95
|
+
add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
96
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
|
4
|
+
#
|
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
9
|
+
# migrations use external dependencies or application code.
|
|
10
|
+
#
|
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Schema[8.1].define(version: 2026_10_25_090462) do
|
|
14
|
+
create_table "geneva_drive_signals", force: :cascade do |t|
|
|
15
|
+
t.integer "workflow_id", null: false
|
|
16
|
+
t.string "name", null: false
|
|
17
|
+
t.string "idempotency_key"
|
|
18
|
+
t.string "state", default: "pending", null: false
|
|
19
|
+
t.json "payload"
|
|
20
|
+
t.datetime "claimed_at"
|
|
21
|
+
t.datetime "consumed_at"
|
|
22
|
+
t.bigint "claimed", default: 0, null: false
|
|
23
|
+
t.bigint "consumed", default: 0, null: false
|
|
24
|
+
t.datetime "created_at", null: false
|
|
25
|
+
t.datetime "updated_at", null: false
|
|
26
|
+
t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
|
|
27
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
|
|
28
|
+
t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
create_table "geneva_drive_step_executions", force: :cascade do |t|
|
|
32
|
+
t.integer "workflow_id", null: false
|
|
33
|
+
t.string "step_name", null: false
|
|
34
|
+
t.string "state", default: "scheduled", null: false
|
|
35
|
+
t.string "outcome"
|
|
36
|
+
t.datetime "scheduled_for", null: false
|
|
37
|
+
t.datetime "started_at"
|
|
38
|
+
t.datetime "completed_at"
|
|
39
|
+
t.datetime "failed_at"
|
|
40
|
+
t.datetime "canceled_at"
|
|
41
|
+
t.datetime "skipped_at"
|
|
42
|
+
t.string "error_class_name"
|
|
43
|
+
t.text "error_message"
|
|
44
|
+
t.text "error_backtrace"
|
|
45
|
+
t.string "job_id"
|
|
46
|
+
t.text "metadata"
|
|
47
|
+
t.datetime "created_at", null: false
|
|
48
|
+
t.datetime "updated_at", null: false
|
|
49
|
+
t.datetime "finished_at"
|
|
50
|
+
t.json "cursor"
|
|
51
|
+
t.bigint "continues_from_id"
|
|
52
|
+
t.bigint "signal_id"
|
|
53
|
+
t.datetime "waiting_since"
|
|
54
|
+
t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
|
|
55
|
+
t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
|
|
56
|
+
t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
|
|
57
|
+
t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
|
|
58
|
+
t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "state = 'in_progress'"
|
|
59
|
+
t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
|
|
60
|
+
t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
|
|
61
|
+
t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
|
|
62
|
+
t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
|
|
63
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
|
|
64
|
+
t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "state IN ('scheduled', 'in_progress')"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
create_table "geneva_drive_workflows", force: :cascade do |t|
|
|
68
|
+
t.string "type", null: false
|
|
69
|
+
t.string "hero_type"
|
|
70
|
+
t.integer "hero_id"
|
|
71
|
+
t.string "state", default: "ready", null: false
|
|
72
|
+
t.string "current_step_name"
|
|
73
|
+
t.string "next_step_name"
|
|
74
|
+
t.boolean "allow_multiple", default: false, null: false
|
|
75
|
+
t.text "metadata"
|
|
76
|
+
t.datetime "started_at"
|
|
77
|
+
t.datetime "transitioned_at"
|
|
78
|
+
t.datetime "created_at", null: false
|
|
79
|
+
t.datetime "updated_at", null: false
|
|
80
|
+
t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
|
|
81
|
+
t.index ["state"], name: "index_geneva_drive_workflows_on_state"
|
|
82
|
+
t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "state NOT IN ('finished', 'canceled') AND allow_multiple = 0"
|
|
83
|
+
t.index ["type"], name: "index_geneva_drive_workflows_on_type"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_table "users", force: :cascade do |t|
|
|
87
|
+
t.string "email", null: false
|
|
88
|
+
t.string "name"
|
|
89
|
+
t.boolean "active", default: true
|
|
90
|
+
t.datetime "created_at", null: false
|
|
91
|
+
t.datetime "updated_at", null: false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
95
|
+
add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
|
|
96
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# license: LGPLv3.0
|
|
4
|
+
|
|
5
|
+
class CreateGenevaDriveWorkflows < ActiveRecord::Migration[7.2]
|
|
6
|
+
include GenevaDrive::MigrationHelpers
|
|
7
|
+
|
|
8
|
+
def change
|
|
9
|
+
create_table :geneva_drive_workflows, **geneva_drive_table_options do |t|
|
|
10
|
+
# Core identification (STI)
|
|
11
|
+
t.string :type, null: false, index: true
|
|
12
|
+
|
|
13
|
+
# Polymorphic association to the hero of the workflow
|
|
14
|
+
t.string :hero_type, null: false
|
|
15
|
+
t.column :hero_id, geneva_drive_key_type, null: false
|
|
16
|
+
|
|
17
|
+
# State machine
|
|
18
|
+
t.string :state, null: false, default: "ready", index: true
|
|
19
|
+
|
|
20
|
+
# Current position in workflow
|
|
21
|
+
t.string :current_step_name
|
|
22
|
+
t.string :next_step_name
|
|
23
|
+
|
|
24
|
+
# Multiple workflows of same type for same hero
|
|
25
|
+
t.boolean :allow_multiple, default: false, null: false
|
|
26
|
+
|
|
27
|
+
# Timestamps
|
|
28
|
+
t.datetime :started_at
|
|
29
|
+
t.datetime :transitioned_at
|
|
30
|
+
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Polymorphic index
|
|
35
|
+
add_index :geneva_drive_workflows, [:hero_type, :hero_id]
|
|
36
|
+
|
|
37
|
+
reversible do |direction|
|
|
38
|
+
direction.up { create_db_specific_indices }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_db_specific_indices
|
|
43
|
+
# Database-specific uniqueness constraint for ongoing workflows
|
|
44
|
+
# Ensures only one ongoing workflow per (type, hero) unless allow_multiple is true
|
|
45
|
+
adapter = connection.adapter_name.downcase
|
|
46
|
+
if adapter.include?("postgresql")
|
|
47
|
+
execute <<-SQL
|
|
48
|
+
CREATE UNIQUE INDEX index_geneva_drive_workflows_unique_ongoing
|
|
49
|
+
ON geneva_drive_workflows (type, hero_type, hero_id)
|
|
50
|
+
WHERE state NOT IN ('finished', 'canceled') AND allow_multiple = false;
|
|
51
|
+
SQL
|
|
52
|
+
elsif adapter.include?("mysql")
|
|
53
|
+
execute <<-SQL
|
|
54
|
+
ALTER TABLE geneva_drive_workflows
|
|
55
|
+
ADD COLUMN ongoing_unique_key VARCHAR(767)
|
|
56
|
+
AS (
|
|
57
|
+
CASE
|
|
58
|
+
WHEN state NOT IN ('finished', 'canceled') AND allow_multiple = 0
|
|
59
|
+
THEN CONCAT(type, '-', hero_type, '-', hero_id)
|
|
60
|
+
ELSE NULL
|
|
61
|
+
END
|
|
62
|
+
) STORED;
|
|
63
|
+
SQL
|
|
64
|
+
execute <<-SQL
|
|
65
|
+
CREATE UNIQUE INDEX index_geneva_drive_workflows_unique_ongoing
|
|
66
|
+
ON geneva_drive_workflows (ongoing_unique_key);
|
|
67
|
+
SQL
|
|
68
|
+
elsif adapter.include?("sqlite")
|
|
69
|
+
execute <<-SQL
|
|
70
|
+
CREATE UNIQUE INDEX index_geneva_drive_workflows_unique_ongoing
|
|
71
|
+
ON geneva_drive_workflows (type, hero_type, hero_id)
|
|
72
|
+
WHERE state NOT IN ('finished', 'canceled') AND allow_multiple = 0;
|
|
73
|
+
SQL
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# license: LGPLv3.0
|
|
4
|
+
|
|
5
|
+
class CreateGenevaDriveStepExecutions < ActiveRecord::Migration[7.2]
|
|
6
|
+
include GenevaDrive::MigrationHelpers
|
|
7
|
+
|
|
8
|
+
def change
|
|
9
|
+
key_type = geneva_drive_key_type
|
|
10
|
+
|
|
11
|
+
create_table :geneva_drive_step_executions, **geneva_drive_table_options do |t|
|
|
12
|
+
# Link to workflow (cascade delete when workflow is deleted)
|
|
13
|
+
t.references :workflow,
|
|
14
|
+
null: false,
|
|
15
|
+
foreign_key: {to_table: :geneva_drive_workflows, on_delete: :cascade},
|
|
16
|
+
index: true,
|
|
17
|
+
type: key_type
|
|
18
|
+
|
|
19
|
+
# Which step this execution represents
|
|
20
|
+
t.string :step_name, null: false
|
|
21
|
+
|
|
22
|
+
# Execution state machine
|
|
23
|
+
t.string :state, null: false, default: "scheduled", index: true
|
|
24
|
+
|
|
25
|
+
# Outcome for audit purposes
|
|
26
|
+
t.string :outcome
|
|
27
|
+
|
|
28
|
+
# Scheduling
|
|
29
|
+
t.datetime :scheduled_for, null: false, index: true
|
|
30
|
+
|
|
31
|
+
# Execution tracking (individual timestamps for audit trail)
|
|
32
|
+
t.datetime :started_at
|
|
33
|
+
t.datetime :completed_at
|
|
34
|
+
t.datetime :failed_at
|
|
35
|
+
t.datetime :canceled_at
|
|
36
|
+
t.datetime :skipped_at
|
|
37
|
+
|
|
38
|
+
# Error tracking
|
|
39
|
+
t.string :error_class_name
|
|
40
|
+
t.text :error_message
|
|
41
|
+
t.text :error_backtrace
|
|
42
|
+
|
|
43
|
+
# Job tracking (for debugging)
|
|
44
|
+
t.string :job_id
|
|
45
|
+
|
|
46
|
+
t.timestamps
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Index for finding scheduled executions
|
|
50
|
+
add_index :geneva_drive_step_executions,
|
|
51
|
+
[:state, :scheduled_for],
|
|
52
|
+
name: "index_geneva_drive_step_executions_scheduled"
|
|
53
|
+
|
|
54
|
+
# Index for workflow execution history
|
|
55
|
+
add_index :geneva_drive_step_executions, [:workflow_id, :created_at]
|
|
56
|
+
|
|
57
|
+
# Index for common query patterns
|
|
58
|
+
add_index :geneva_drive_step_executions, [:workflow_id, :state]
|
|
59
|
+
|
|
60
|
+
reversible do |direction|
|
|
61
|
+
direction.up { create_db_specific_indices }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def create_db_specific_indices
|
|
66
|
+
# Database-specific uniqueness constraint for active step executions
|
|
67
|
+
# Ensures only one active (scheduled/in_progress) step per workflow
|
|
68
|
+
adapter = connection.adapter_name.downcase
|
|
69
|
+
if adapter.include?("postgresql")
|
|
70
|
+
execute <<-SQL
|
|
71
|
+
CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
|
|
72
|
+
ON geneva_drive_step_executions (workflow_id)
|
|
73
|
+
WHERE state IN ('scheduled', 'in_progress');
|
|
74
|
+
SQL
|
|
75
|
+
elsif adapter.include?("mysql")
|
|
76
|
+
execute <<-SQL
|
|
77
|
+
ALTER TABLE geneva_drive_step_executions
|
|
78
|
+
ADD COLUMN active_unique_key VARCHAR(767)
|
|
79
|
+
AS (
|
|
80
|
+
CASE
|
|
81
|
+
WHEN state IN ('scheduled', 'in_progress')
|
|
82
|
+
THEN CAST(workflow_id AS CHAR)
|
|
83
|
+
ELSE NULL
|
|
84
|
+
END
|
|
85
|
+
) STORED;
|
|
86
|
+
SQL
|
|
87
|
+
execute <<-SQL
|
|
88
|
+
CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
|
|
89
|
+
ON geneva_drive_step_executions (active_unique_key);
|
|
90
|
+
SQL
|
|
91
|
+
elsif adapter.include?("sqlite")
|
|
92
|
+
execute <<-SQL
|
|
93
|
+
CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
|
|
94
|
+
ON geneva_drive_step_executions (workflow_id)
|
|
95
|
+
WHERE state IN ('scheduled', 'in_progress');
|
|
96
|
+
SQL
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/test/dummy_install/db/migrate/20260217112951_add_finished_at_to_geneva_drive_step_executions.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# license: LGPLv3.0
|
|
4
|
+
|
|
5
|
+
class AddFinishedAtToGenevaDriveStepExecutions < ActiveRecord::Migration[7.2]
|
|
6
|
+
def change
|
|
7
|
+
add_column :geneva_drive_step_executions, :finished_at, :datetime
|
|
8
|
+
|
|
9
|
+
add_index :geneva_drive_step_executions, :finished_at
|
|
10
|
+
|
|
11
|
+
reversible do |direction|
|
|
12
|
+
direction.up do
|
|
13
|
+
# Backfill finished_at from existing timestamp columns.
|
|
14
|
+
# Uses COALESCE to pick the first non-null value.
|
|
15
|
+
execute <<-SQL.squish
|
|
16
|
+
UPDATE geneva_drive_step_executions
|
|
17
|
+
SET finished_at = COALESCE(completed_at, failed_at, canceled_at, skipped_at)
|
|
18
|
+
WHERE finished_at IS NULL
|
|
19
|
+
AND (completed_at IS NOT NULL
|
|
20
|
+
OR failed_at IS NOT NULL
|
|
21
|
+
OR canceled_at IS NOT NULL
|
|
22
|
+
OR skipped_at IS NOT NULL)
|
|
23
|
+
SQL
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|