Skip to content

Commit

Permalink
introduce discovery_test, finally.
Browse files Browse the repository at this point in the history
  • Loading branch information
apotonick committed Feb 15, 2024
1 parent 674cc52 commit 130e887
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 159 deletions.
30 changes: 17 additions & 13 deletions lib/trailblazer/workflow/discovery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def call(collaboration, start_position:, run_multiple_times: {}, initial_lane_po
# imply we start from a public resume and discover the path?
# we could save work on {run_multiple_times} with this.

collaboration, message_flow, start_position, initial_lane_positions = stub_tasks_for(collaboration, message_flow: message_flow, start_position: start_position, initial_lane_positions: initial_lane_positions)
collaboration, message_flow, start_position, initial_lane_positions, original_activity_2_stub_activity, original_task_2_stub_task = stub_tasks_for(collaboration, message_flow: message_flow, start_position: start_position, initial_lane_positions: initial_lane_positions)

# pp collaboration.to_h[:lanes][:ui].to_h
# raise
Expand All @@ -34,7 +34,7 @@ def call(collaboration, start_position:, run_multiple_times: {}, initial_lane_po
]
]

states = []
discovered_states = []
additional_state_data = {}

already_visited_catch_events = {}
Expand All @@ -60,7 +60,7 @@ def call(collaboration, start_position:, run_multiple_times: {}, initial_lane_po

# register new state.
# Note that we do that before anything is invoked.
states << state = [lane_positions, start_position] # FIXME: we need to add {configuration} here!
discovered_states << state = [lane_positions, start_position] # FIXME: we need to add {configuration} here!

state_data = [ctx.inspect]

Expand Down Expand Up @@ -114,14 +114,14 @@ def call(collaboration, start_position:, run_multiple_times: {}, initial_lane_po
end
end

# {states} is compile-time relevant
# {discovered_states} is compile-time relevant
# {additional_state_data} is runtime

return states, additional_state_data
return discovered_states, additional_state_data
end

def stub_tasks_for(collaboration, ignore_class: Trailblazer::Activity::End, message_flow:, start_position:, initial_lane_positions:)
stubbed_lanes = collaboration.to_h[:lanes].collect do |lane_id, activity|
collected = collaboration.to_h[:lanes].collect do |lane_id, activity|
circuit = activity.to_h[:circuit]
lane_map = circuit.to_h[:map].clone

Expand Down Expand Up @@ -172,23 +172,27 @@ def stub_tasks_for(collaboration, ignore_class: Trailblazer::Activity::End, mess
lane = Activity.new(Activity::Schema.new(new_circuit, activity.to_h[:outputs], new_nodes, activity.to_h[:config])) # FIXME: breaking taskWrap here (which is no problem, actually).

# [lane_id, lane, replaced_tasks]
[lane_id, lane]
end.to_h
[[lane_id, lane], replaced_tasks]
end

original_task_2_stub_task = collected.inject({}) { |memo, (_, replaced_tasks)| memo.merge(replaced_tasks) }

stubbed_lanes = collected.collect { |lane, _| lane }.to_h

old_activity_2_new_activity = collaboration.to_h[:lanes].collect { |lane_id, activity| [activity, stubbed_lanes[lane_id]] }.to_h
original_activity_2_stub_activity = collaboration.to_h[:lanes].collect { |lane_id, activity| [activity, stubbed_lanes[lane_id]] }.to_h

new_message_flow = message_flow.collect { |throw_evt, (activity, catch_evt)| [throw_evt, [old_activity_2_new_activity[activity], catch_evt]] }.to_h
new_message_flow = message_flow.collect { |throw_evt, (activity, catch_evt)| [throw_evt, [original_activity_2_stub_activity[activity], catch_evt]] }.to_h

new_start_position = Collaboration::Position.new(old_activity_2_new_activity.fetch(start_position.activity), start_position.task)
new_start_position = Collaboration::Position.new(original_activity_2_stub_activity.fetch(start_position.activity), start_position.task)

new_initial_lane_positions = initial_lane_positions.collect do |position|
# TODO: make lane_positions {Position} instances, too.
Collaboration::Position.new(old_activity_2_new_activity[position[0]], position[1])
Collaboration::Position.new(original_activity_2_stub_activity[position[0]], position[1])
end

new_initial_lane_positions = Collaboration::Positions.new(new_initial_lane_positions)

return Collaboration::Schema.new(lanes: stubbed_lanes, message_flow: new_message_flow), new_message_flow, new_start_position, new_initial_lane_positions
return Collaboration::Schema.new(lanes: stubbed_lanes, message_flow: new_message_flow), new_message_flow, new_start_position, new_initial_lane_positions, original_activity_2_stub_activity, original_task_2_stub_task
end
end
end
Expand Down
148 changes: 2 additions & 146 deletions test/collaboration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,152 +7,7 @@ class CollaborationTest < Minitest::Spec
include Trailblazer::Test::Assertions # DISCUSS: this is for assert_advance and friends.


def build_schema()
moderation_json = File.read("test/fixtures/v1/moderation.json")
signal, (ctx, _) = Trailblazer::Workflow::Generate.invoke([{json_document: moderation_json}, {}])

article_moderation_intermediate = ctx[:intermediates]["article moderation"]
# pp article_moderation_intermediate

implementing = Trailblazer::Activity::Testing.def_steps(:create, :update, :notify_approver, :reject, :approve, :revise, :publish, :archive, :delete)

lane_activity = Trailblazer::Workflow::Collaboration.Lane(
article_moderation_intermediate,

"Create" => implementing.method(:create),
"Update" => implementing.method(:update),
"Approve" => implementing.method(:approve),
"Notify approver" => implementing.method(:notify_approver),
"Revise" => implementing.method(:revise),
"Reject" => implementing.method(:reject),
"Publish" => implementing.method(:publish),
"Archive" => implementing.method(:archive),
"Delete" => implementing.method(:delete),
)


article_moderation_intermediate = ctx[:intermediates]["<ui> author workflow"]
# pp article_moderation_intermediate

implementing = Trailblazer::Activity::Testing.def_steps(:create_form, :ui_create, :update_form, :ui_update, :notify_approver, :reject, :approve, :revise, :publish, :archive, :delete, :delete_form, :cancel, :revise_form,
:create_form_with_errors, :update_form_with_errors, :revise_form_with_errors)

lane_activity_ui = Trailblazer::Workflow::Collaboration.Lane(
article_moderation_intermediate,

"Create form" => implementing.method(:create_form),
"Create" => implementing.method(:ui_create),
"Update form" => implementing.method(:update_form),
"Update" => implementing.method(:ui_update),
"Notify approver" => implementing.method(:notify_approver),
"Publish" => implementing.method(:publish),
"Delete" => implementing.method(:delete),
"Delete? form" => implementing.method(:delete_form),
"Cancel" => implementing.method(:cancel),
"Revise" => implementing.method(:revise),
"Revise form" => implementing.method(:revise_form),
"Create form with errors" => implementing.method(:create_form_with_errors),
"Update form with errors" => implementing.method(:update_form_with_errors),
"Revise form with errors" => implementing.method(:revise_form_with_errors),
"Archive" => implementing.method(:archive),
# "Approve" => implementing.method(:approve),
# "Reject" => implementing.method(:reject),
)

# TODO: move this into the Schema-build process.
# This is needed to translate the JSON message structure to Ruby,
# where we reference lanes by their {Activity} instance.
json_id_to_lane = {
"article moderation" => lane_activity,
"<ui> author workflow" => lane_activity_ui,
}

# lane_icons: lane_icons = {"UI" => "☝", "lifecycle" => "⛾", "approver" => "☑"},
lanes_cfg = {
"article moderation" => {
label: "lifecycle",
icon: "⛾",
activity: lane_activity, # this is copied here after the activity has been compiled in {Schema.build}.
},
"<ui> author workflow" => {
label: "UI",
icon: "☝",
activity: lane_activity_ui,
},
# TODO: add editor/approver lane.
}

# pp ctx[:structure].lanes
message_flow = Trailblazer::Workflow::Collaboration.Messages(
ctx[:structure].messages,
json_id_to_lane
)

# DISCUSS: {lanes} is always ID to activity?
lanes = {
lifecycle: lane_activity,
ui: lane_activity_ui,
}

approver_activity, extended_message_flow, extended_initial_lane_positions = build_custom_editor_lane(lanes, message_flow)

lanes = lanes.merge(approver: approver_activity)

schema = Trailblazer::Workflow::Collaboration::Schema.new(
lanes: lanes,
message_flow: message_flow,
)

return schema, lanes, extended_message_flow, extended_initial_lane_positions
end

# DISCUSS: this is mostly to play around with the "API" of building a Collaboration.
def build_custom_editor_lane(lanes, message_flow)
approve_id = "Activity_1qrkaz0"
reject_id = "Activity_0d9yewp"

lifecycle_activity = lanes[:lifecycle]

missing_throw_from_notify_approver = Trailblazer::Activity::Introspect.Nodes(lifecycle_activity, id: "throw-after-Activity_0wr78cv").task

decision_is_approve_throw = nil
decision_is_reject_throw = nil

approver_start_suspend = nil
approver_activity = Class.new(Trailblazer::Activity::Railway) do
step task: approver_start_suspend = Trailblazer::Workflow::Event::Suspend.new(semantic: "invented_semantic", "resumes" => ["catch-before-decider-xxx"])

fail task: Trailblazer::Workflow::Event::Catch.new(semantic: "xxx --> decider"), id: "catch-before-decider-xxx", Output(:success) => Track(:failure)
fail :decider, id: "xxx",
Output(:failure) => Trailblazer::Activity::Railway.Id("xxx_reject")
fail task: decision_is_approve_throw = Trailblazer::Workflow::Event::Throw.new(semantic: "xxx_approve")

step task: decision_is_reject_throw = Trailblazer::Workflow::Event::Throw.new(semantic: "xxx_reject"),
magnetic_to: :reject, id: "xxx_reject"

def decider(ctx, decision: true, **)
# raise if !decision

decision
end
end

extended_message_flow = message_flow.merge(
# "throw-after-Activity_0wr78cv"
missing_throw_from_notify_approver => [approver_activity, Trailblazer::Activity::Introspect.Nodes(approver_activity, id: "catch-before-decider-xxx").task],
decision_is_approve_throw => [lifecycle_activity, Trailblazer::Activity::Introspect.Nodes(lifecycle_activity, id: "catch-before-#{approve_id}").task],
decision_is_reject_throw => [lifecycle_activity, Trailblazer::Activity::Introspect.Nodes(lifecycle_activity, id: "catch-before-#{reject_id}").task],
)

initial_lane_positions = Trailblazer::Workflow::Collaboration::Synchronous.initial_lane_positions(lanes) # we need to do this manually here, as initial_lane_positions isn't part of the {Schema.build} process.
extended_initial_lane_positions = initial_lane_positions.merge(
approver_activity => approver_start_suspend
)
extended_initial_lane_positions = Trailblazer::Workflow::Collaboration::Positions.new(extended_initial_lane_positions.collect { |activity, task| Trailblazer::Workflow::Collaboration::Position.new(activity, task) })

return approver_activity, extended_message_flow, extended_initial_lane_positions
end

include BuildSchema

# TODO: remove me, or move me at least!
# DISCUSS: {states} should probably be named {reached_states} as some states appear multiple times in the list.
Expand Down Expand Up @@ -295,6 +150,7 @@ def render_states(states, lanes:, additional_state_data:, task_map:)
start_position: start_position,
message_flow: message_flow,

# TODO: allow translating the original "id" (?) to the stubbed.
run_multiple_times: {
# We're "clicking" the [Notify_approver] button again, this time to get rejected.
Trailblazer::Activity::Introspect.Nodes(lane_activity_ui, id: "catch-before-#{ui_notify_approver}").task => {ctx_merge: {
Expand Down
56 changes: 56 additions & 0 deletions test/discovery_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require "test_helper"

class DiscoveryTest < Minitest::Spec
include BuildSchema

it "Discovery.call" do
ui_create_form = "Activity_0wc2mcq" # TODO: this is from pro-rails tests.
ui_create = "Activity_1psp91r"
ui_update = "Activity_0j78uzd"
ui_notify_approver = "Activity_1dt5di5"

schema, lanes, message_flow, initial_lane_positions = build_schema()

lane_activity = lanes[:lifecycle]
lane_activity_ui = lanes[:ui]
approver_activity = lanes[:approver]


# TODO: do this in the State layer.
start_task = Trailblazer::Activity::Introspect.Nodes(lane_activity_ui, id: "catch-before-#{ui_create_form}").task # catch-before-Activity_0wc2mcq
start_position = Trailblazer::Workflow::Collaboration::Position.new(lane_activity_ui, start_task)


states, additional_state_data = Trailblazer::Workflow::Discovery.(
schema,
initial_lane_positions: initial_lane_positions,
start_position: start_position,
message_flow: message_flow,

# TODO: allow translating the original "id" (?) to the stubbed.
run_multiple_times: {
# We're "clicking" the [Notify_approver] button again, this time to get rejected.
Trailblazer::Activity::Introspect.Nodes(lane_activity_ui, id: "catch-before-#{ui_notify_approver}").task => {ctx_merge: {
# decision: false, # TODO: this is how it should be.
:"approver:xxx" => Trailblazer::Activity::Left, # FIXME: {:decision} must be translated to {:"approver:xxx"}
}, config_payload: {outcome: :failure}},

# Click [UI Create] again, with invalid data.
Trailblazer::Activity::Introspect.Nodes(lane_activity_ui, id: "catch-before-#{ui_create}").task => {ctx_merge: {
# create: false
:"lifecycle:Create" => Trailblazer::Activity::Left,
}, config_payload: {outcome: :failure}}, # lifecycle create is supposed to fail.

# Click [UI Update] again, with invalid data.
Trailblazer::Activity::Introspect.Nodes(lane_activity_ui, id: "catch-before-#{ui_update}").task => {ctx_merge: {
# update: false
:"lifecycle:Update" => Trailblazer::Activity::Left,
}, config_payload: {outcome: :failure}}, # lifecycle create is supposed to fail.
}
)

pp states

raise
end
end
Loading

0 comments on commit 130e887

Please sign in to comment.