datashift_journey 0.1.2
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +391 -0
- data/Rakefile +26 -0
- data/app/assets/stylesheets/datashift_journey/partials/_state_jumper_toolbar.scss.erb +27 -0
- data/app/controllers/concerns/datashift_journey/error_renderer.rb +9 -0
- data/app/controllers/concerns/datashift_journey/review_renderer.rb +21 -0
- data/app/controllers/concerns/datashift_journey/token_based_access.rb +23 -0
- data/app/controllers/concerns/datashift_journey/validate_state.rb +59 -0
- data/app/controllers/datashift_journey/abandon_enrollments_controller.rb +14 -0
- data/app/controllers/datashift_journey/abandonments_controller.rb +17 -0
- data/app/controllers/datashift_journey/api/v1/states_controller.rb +49 -0
- data/app/controllers/datashift_journey/application_controller.rb +53 -0
- data/app/controllers/datashift_journey/errors_controller.rb +24 -0
- data/app/controllers/datashift_journey/journey_ends_controller.rb +19 -0
- data/app/controllers/datashift_journey/journey_plans_controller.rb +166 -0
- data/app/controllers/datashift_journey/page_states_controller.rb +49 -0
- data/app/controllers/datashift_journey/reviews_controller.rb +32 -0
- data/app/controllers/datashift_journey/state_jumper_controller.rb +51 -0
- data/app/factories/datashift_journey/form_object_factory.rb +68 -0
- data/app/forms/datashift_journey/collector/base_collector_form.rb +60 -0
- data/app/forms/datashift_journey/concerns/form_mixin.rb +64 -0
- data/app/forms/datashift_journey/null_form.rb +20 -0
- data/app/helpers/datashift_journey/application_helper.rb +50 -0
- data/app/helpers/datashift_journey/back_link_helper.rb +9 -0
- data/app/models/datashift_journey/collector/data_node.rb +18 -0
- data/app/models/datashift_journey/collector/form_definition.rb +35 -0
- data/app/models/datashift_journey/collector/form_field.rb +61 -0
- data/app/models/datashift_journey/journey_review.rb +65 -0
- data/app/models/datashift_journey/review_data_section.rb +32 -0
- data/app/serializers/datashift_journey/collector/page_state_serializer.rb +9 -0
- data/app/serializers/state_machines/state/state_serializer.rb +5 -0
- data/app/views/datashift_journey/collector/_generic_form.html.erb +14 -0
- data/app/views/datashift_journey/errors/401.html.erb +13 -0
- data/app/views/datashift_journey/errors/403.html.erb +13 -0
- data/app/views/datashift_journey/errors/404.html.erb +13 -0
- data/app/views/datashift_journey/errors/422.html.erb +11 -0
- data/app/views/datashift_journey/errors/500.html.erb +13 -0
- data/app/views/datashift_journey/errors/503.html.erb +11 -0
- data/app/views/datashift_journey/errors/invalid_authenticity_token.html.erb +14 -0
- data/app/views/datashift_journey/journey_ends/new.html.erb +5 -0
- data/app/views/datashift_journey/journey_ends/show.html.erb +5 -0
- data/app/views/datashift_journey/journey_plans/_form.html.erb +14 -0
- data/app/views/datashift_journey/journey_plans/_render_fields.html.erb +24 -0
- data/app/views/datashift_journey/journey_plans/edit.html.erb +6 -0
- data/app/views/datashift_journey/journey_plans/new.html.erb +6 -0
- data/app/views/datashift_journey/shared/_default_actions.html.erb +6 -0
- data/app/views/datashift_journey/shared/_errors.html.erb +19 -0
- data/app/views/datashift_journey/shared/_submit_action.html.erb +5 -0
- data/app/views/datashift_journey/state_jumper/_toolbar.html.erb +16 -0
- data/config/brakeman.ignore +42 -0
- data/config/i18n-tasks.yml +103 -0
- data/config/initializers/exceptions_app.rb +3 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/initializers/rswag-api.rb +14 -0
- data/config/initializers/rswag-ui.rb +9 -0
- data/config/locales/en.yml +39 -0
- data/config/routes.rb +44 -0
- data/lib/datashift_journey/collector/field_snippet.rb +12 -0
- data/lib/datashift_journey/collector/page_state_snippet.rb +12 -0
- data/lib/datashift_journey/collector/snippet.rb +14 -0
- data/lib/datashift_journey/configuration.rb +103 -0
- data/lib/datashift_journey/engine.rb +38 -0
- data/lib/datashift_journey/exceptions.rb +26 -0
- data/lib/datashift_journey/helpers/back_link.rb +58 -0
- data/lib/datashift_journey/journey/machine_builder.rb +59 -0
- data/lib/datashift_journey/prepare_data_for_review.rb +219 -0
- data/lib/datashift_journey/reference_generator.rb +51 -0
- data/lib/datashift_journey/state_machines/branch_sequence_map.rb +35 -0
- data/lib/datashift_journey/state_machines/extensions.rb +40 -0
- data/lib/datashift_journey/state_machines/planner.rb +206 -0
- data/lib/datashift_journey/state_machines/sequence.rb +86 -0
- data/lib/datashift_journey/state_machines/state_machine_core_ext.rb +72 -0
- data/lib/datashift_journey/version.rb +3 -0
- data/lib/datashift_journey.rb +57 -0
- data/lib/generators/datashift_journey/collector/collector_generator.rb +43 -0
- data/lib/generators/datashift_journey/collector/install_collector_generator.rb +61 -0
- data/lib/generators/datashift_journey/collector/install_mongo_collector_generator.rb +44 -0
- data/lib/generators/datashift_journey/collector/templates/collector_concern.rb.tt +34 -0
- data/lib/generators/datashift_journey/collector/templates/collector_migration.rb.tt +46 -0
- data/lib/generators/datashift_journey/forms_generator.rb +45 -0
- data/lib/generators/datashift_journey/generate_common.rb +33 -0
- data/lib/generators/datashift_journey/setup/USAGE +12 -0
- data/lib/generators/datashift_journey/setup/setup_generator.rb +44 -0
- data/lib/generators/datashift_journey/setup/templates/initializer.rb.tt +17 -0
- data/lib/generators/datashift_journey/setup/templates/model_concern.rb.tt +37 -0
- data/lib/generators/datashift_journey/templates/base_form.rb.tt +7 -0
- data/lib/generators/datashift_journey/templates/collector_form.rb.tt +18 -0
- data/lib/generators/datashift_journey/templates/collector_view.rb.tt +15 -0
- data/lib/generators/datashift_journey/templates/journey_plan_form.rb.tt +23 -0
- data/lib/generators/datashift_journey/templates/journey_plan_view.rb.tt +15 -0
- data/lib/generators/datashift_journey/views_generator.rb +35 -0
- data/lib/tasks/state_machine.thor +48 -0
- data/spec/datashift_journey/complex_journey_spec.rb +132 -0
- data/spec/datashift_journey/machine_builder_spec.rb +268 -0
- data/spec/datashift_journey/planner_spec.rb +129 -0
- data/spec/datashift_journey/sequence_spec.rb +20 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +16 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/forms/base_form.rb +8 -0
- data/spec/dummy/app/forms/business_details_form.rb +17 -0
- data/spec/dummy/app/forms/business_type_form.rb +17 -0
- data/spec/dummy/app/forms/contact_details_form.rb +17 -0
- data/spec/dummy/app/forms/enter_reg_number_form.rb +17 -0
- data/spec/dummy/app/forms/new_or_renew_form.rb +17 -0
- data/spec/dummy/app/forms/postal_address_form.rb +17 -0
- data/spec/dummy/app/forms/question1_form.rb +9 -0
- data/spec/dummy/app/forms/question2_form.rb +12 -0
- data/spec/dummy/app/forms/sole_trader_name_form.rb +17 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/payment.rb +5 -0
- data/spec/dummy/app/services/datashift_journey/models/collector_journey.rb +51 -0
- data/spec/dummy/app/views/_question1.html.erb +13 -0
- data/spec/dummy/app/views/_question2.html.erb +9 -0
- data/spec/dummy/app/views/layouts/alternative.html.erb +14 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/pages/home.html.erb +6 -0
- data/spec/dummy/app/views/pages/start.html.erb +5 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +39 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +47 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +47 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/datashift_journey.rb +6 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +27 -0
- data/spec/dummy/config/routes.rb +28 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20160101091218_create_dummy_checkout.rb +43 -0
- data/spec/dummy/db/migrate/20161221100703_datashift_journey_create_collector.rb +56 -0
- data/spec/dummy/db/schema.rb +142 -0
- data/spec/dummy/lib/version.rb +1 -0
- data/spec/factories/collector_factory.rb +16 -0
- data/spec/factories/collector_snippet_factory.rb +9 -0
- data/spec/factories/collector_state_page_factory.rb +12 -0
- data/spec/factories/data_node_factory.rb +9 -0
- data/spec/factories/form_factory.rb +6 -0
- data/spec/features/basic_navigation_spec.rb +125 -0
- data/spec/helpers/application_helper_spec.rb +40 -0
- data/spec/integration/collector/page_state_spec.rb +45 -0
- data/spec/models/collector/collector_spec.rb +100 -0
- data/spec/models/collector/page_state_spec.rb +30 -0
- data/spec/rails_helper.rb +73 -0
- data/spec/requests/collector/api/v1/page_state_spec.rb +85 -0
- data/spec/requests/collector/api/v1/states_spec.rb +28 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/support/asserts.rb +27 -0
- data/spec/support/mailer_macros.rb +25 -0
- data/spec/support/page_objects/base_page_object.rb +77 -0
- data/spec/swagger_helper.rb +25 -0
- metadata +425 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
class ApplicationController < ActionController::Base
|
|
4
|
+
|
|
5
|
+
protect_from_forgery with: :exception
|
|
6
|
+
|
|
7
|
+
include TokenBasedAccess
|
|
8
|
+
|
|
9
|
+
layout ->(_) { Configuration.call.layout }
|
|
10
|
+
|
|
11
|
+
# This form does NOT seem to work - helpers cause missing method in views
|
|
12
|
+
# helper DatashiftJourney::Engine.helpers
|
|
13
|
+
# helper "datashift_journey/user"
|
|
14
|
+
|
|
15
|
+
# This form seems to work - helpers now available in views
|
|
16
|
+
# include DatashiftJourney::ApplicationHelper
|
|
17
|
+
# include DatashiftJourney::BackLinkHelper
|
|
18
|
+
|
|
19
|
+
before_action :set_i18n_locale_from_params
|
|
20
|
+
|
|
21
|
+
rescue_from ActionController::InvalidAuthenticityToken, with: :handle_invalid_authenticity_token
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def set_i18n_locale_from_params
|
|
26
|
+
return unless params[:locale]
|
|
27
|
+
|
|
28
|
+
available = I18n.available_locales.map(&:to_s)
|
|
29
|
+
|
|
30
|
+
if available.include?(params[:locale])
|
|
31
|
+
I18n.locale = params[:locale]
|
|
32
|
+
logger.debug "locale set to: #{params[:locale]}"
|
|
33
|
+
else
|
|
34
|
+
flash.now[:notice] = "#{params[:locale]} translation not available"
|
|
35
|
+
logger.error flash.now[:notice]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# http://jacopretorius.net/2014/01/force-page-to-reload-on-browser-back-in-rails.html
|
|
40
|
+
def back_button_cache_buster
|
|
41
|
+
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
|
42
|
+
response.headers['Pragma'] = 'no-cache'
|
|
43
|
+
response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def handle_invalid_authenticity_token(exception)
|
|
47
|
+
Rails.logger.error 'DatashiftJourney::ApplicationController authenticity failed ' \
|
|
48
|
+
"(browser cookies may have been disabled): #{exception}"
|
|
49
|
+
|
|
50
|
+
render 'datashift_journey/errors/invalid_authenticity_token'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
class ErrorsController < ApplicationController
|
|
3
|
+
def show
|
|
4
|
+
@exception = env['action_dispatch.exception']
|
|
5
|
+
action = request.path[1..-1].gsub(/[^0-9]/, '')
|
|
6
|
+
action = 500 if action.blank?
|
|
7
|
+
|
|
8
|
+
status_code =
|
|
9
|
+
if @exception
|
|
10
|
+
trace = Rails.backtrace_cleaner.clean(@exception.backtrace)
|
|
11
|
+
Rails.logger.fatal trace.join("#\n")
|
|
12
|
+
|
|
13
|
+
ActionDispatch::ExceptionWrapper.new(env, @exception).status_code
|
|
14
|
+
else
|
|
15
|
+
action.to_i
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
respond_to do |format|
|
|
19
|
+
format.html { render action: action, status: status_code }
|
|
20
|
+
format.json { render json: { status: status_code, error: @exception.message } }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_dependency 'datashift_journey/application_controller'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
|
|
5
|
+
class JourneyEndsController < ApplicationController
|
|
6
|
+
|
|
7
|
+
def show
|
|
8
|
+
journey_plan_class = params[:journey_plan_class] ? params[:journey_plan_class].constantize : DatashiftJourney.journey_plan_class
|
|
9
|
+
|
|
10
|
+
token = params[:id] || params[:journey_plan_id]
|
|
11
|
+
|
|
12
|
+
@journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
|
|
13
|
+
|
|
14
|
+
@journey_plan.on_journey_end if @journey_plan.respond_to? :on_journey_end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require_dependency 'datashift_journey/application_controller'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
|
|
5
|
+
class JourneyPlansController < ApplicationController
|
|
6
|
+
|
|
7
|
+
include ValidateState
|
|
8
|
+
|
|
9
|
+
# Run BEFORE other filters to ensure the current journey_plan has been selected from the DB
|
|
10
|
+
prepend_before_action :set_journey_plan, only: [:new, :destroy, :back_a_state]
|
|
11
|
+
|
|
12
|
+
prepend_before_action :set_reform_object, only: [:create, :edit, :update]
|
|
13
|
+
|
|
14
|
+
# Validate state and state related params - covers certain edge cases such as browser refresh
|
|
15
|
+
before_action :validate_state, only: [:edit, :update]
|
|
16
|
+
|
|
17
|
+
def new
|
|
18
|
+
# Find and create the form object, backing the current states view
|
|
19
|
+
reform = FormObjectFactory.form_object_for(journey_plan)
|
|
20
|
+
|
|
21
|
+
render locals: { journey_plan: journey_plan, form: reform }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create
|
|
25
|
+
# TOFIX - Validation from params is now broken
|
|
26
|
+
# NoMethodError (undefined method `each_with_index' for #<ActionController::Parameters:0x000055eacfa2c370>):
|
|
27
|
+
# result = form.validate(params)
|
|
28
|
+
# logger.debug("VALIDATION FAILED - Form Errors [#{form.errors.inspect}]") unless result
|
|
29
|
+
journey_plan = journey_plan_class.new
|
|
30
|
+
|
|
31
|
+
form_fields_to_data_nodes
|
|
32
|
+
|
|
33
|
+
move_next(journey_plan)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def back_a_state
|
|
37
|
+
respond_to do |format|
|
|
38
|
+
logger.debug("BACK Requested - current [#{journey_plan.state}] - previous [#{journey_plan.previous_state_name}]")
|
|
39
|
+
|
|
40
|
+
journey_plan.back!
|
|
41
|
+
|
|
42
|
+
if journey_plan.save
|
|
43
|
+
|
|
44
|
+
logger.debug("Successfully back a step - state now [#{journey_plan.state}]")
|
|
45
|
+
|
|
46
|
+
format.html do
|
|
47
|
+
redirect_to(datashift_journey.journey_plan_state_path(journey_plan.state, journey_plan)) && return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
else
|
|
51
|
+
format.html { render :edit }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def edit
|
|
57
|
+
logger.debug "Editing journey_plan [#{journey_plan.inspect}]"
|
|
58
|
+
render locals: { journey_plan: @journey_plan, form: @reform }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def update
|
|
62
|
+
|
|
63
|
+
# result = form.validate(params)
|
|
64
|
+
#
|
|
65
|
+
# redirect_to(form.redirection_url) && return if form.redirect?
|
|
66
|
+
#
|
|
67
|
+
# if result && form.save
|
|
68
|
+
# logger.debug("SUCCESS - Updated #{journey_plan.inspect}")
|
|
69
|
+
#
|
|
70
|
+
# move_next(journey_plan)
|
|
71
|
+
# else
|
|
72
|
+
# logger.debug("FAILED - Form Errors [#{form.errors.inspect}]")
|
|
73
|
+
#
|
|
74
|
+
# render :edit, locals: { journey_plan: journey_plan, form: form }
|
|
75
|
+
# end
|
|
76
|
+
|
|
77
|
+
form_fields_to_data_nodes
|
|
78
|
+
|
|
79
|
+
logger.debug("UPDATED Plan [#{journey_plan.inspect}] - Move to Next")
|
|
80
|
+
puts journey_plan.inspect
|
|
81
|
+
|
|
82
|
+
move_next(journey_plan) && return
|
|
83
|
+
end # UPDATE
|
|
84
|
+
|
|
85
|
+
def form_fields_to_data_nodes
|
|
86
|
+
form_params = params.fetch(@reform.params_key, {})
|
|
87
|
+
|
|
88
|
+
data_nodes = form_params["data_nodes"] # =>{"form_field"=>{"0"=>"name", "1"=>"namespace"}, "field_value"=>{"0"=>"dfsdf", "1"=>"ghfghf"}}}
|
|
89
|
+
|
|
90
|
+
if data_nodes.present?
|
|
91
|
+
|
|
92
|
+
fields = data_nodes["form_field"]
|
|
93
|
+
values = data_nodes["field_value"]
|
|
94
|
+
|
|
95
|
+
fields.each do |idx, name|
|
|
96
|
+
ff = Collector::FormField.where(name: name, form_definition: @reform.definition).first
|
|
97
|
+
next unless ff
|
|
98
|
+
|
|
99
|
+
# Ensure when user goes back and changes a value we reflect the changed value
|
|
100
|
+
Collector::DataNode.find_or_initialize_by(plan: journey_plan, form_field: ff).tap do |node|
|
|
101
|
+
node.field_value = values[idx]
|
|
102
|
+
node.save
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def move_next(journey_plan)
|
|
112
|
+
journey_plan.reload
|
|
113
|
+
|
|
114
|
+
redirect_to(@reform.redirection_url) && return if @reform.redirect?
|
|
115
|
+
|
|
116
|
+
if journey_plan.next_state_name.blank?
|
|
117
|
+
logger.error("JOURNEY ENDED - no next transition - rendering 'journey_end'")
|
|
118
|
+
redirect_to(datashift_journey.journey_end_path(journey_plan)) && return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# if there is no next event, state_machine dynamic helper can_next? not available
|
|
122
|
+
unless journey_plan.respond_to?('can_skip_fwd?') && journey_plan.can_skip_fwd?
|
|
123
|
+
logger.error("JOURNEY Cannot proceed - no next transition - rendering 'journey_end'")
|
|
124
|
+
redirect_to(datashift_journey.journey_end_path(journey_plan)) && return
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
journey_plan.skip_fwd!
|
|
128
|
+
|
|
129
|
+
logger.info("JOURNEY moved to next state - Current Plan :")
|
|
130
|
+
logger.info(journey_plan)
|
|
131
|
+
|
|
132
|
+
redirect_to(datashift_journey.journey_plan_state_path(journey_plan.state, journey_plan)) && return
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def set_journey_plan_class
|
|
136
|
+
@journey_plan_class = params[:journey_plan_class] ? params[:journey_plan_class].constantize : DatashiftJourney.journey_plan_class
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def set_journey_plan
|
|
140
|
+
set_journey_plan_class
|
|
141
|
+
|
|
142
|
+
token = params[:id] || params[:journey_plan_id]
|
|
143
|
+
|
|
144
|
+
# TOFIX: Rails 6 probably has this inbuilt now and makes this much simpler - see ActiveSupport::MessageEncryptor, KeyGenerator
|
|
145
|
+
#
|
|
146
|
+
# https://github.com/robertomiranda/has_secure_token
|
|
147
|
+
# TODO: how to auto insert has_secure_token into the underlying journey plan model
|
|
148
|
+
# and add in migration thats adds the token column
|
|
149
|
+
# @journey_plan = DatashiftJourney.journey_plan_class.find_by_token!(token)
|
|
150
|
+
@journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def set_reform_object
|
|
154
|
+
set_journey_plan_class
|
|
155
|
+
|
|
156
|
+
token = params[:id] || params[:journey_plan_id]
|
|
157
|
+
|
|
158
|
+
journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
|
|
159
|
+
@reform = FormObjectFactory.form_object_for(journey_plan)
|
|
160
|
+
|
|
161
|
+
@journey_plan = @reform.journey_plan
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
attr_reader :journey_plan_class, :journey_plan
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
class FormsController < ActionController::API
|
|
3
|
+
|
|
4
|
+
include DatashiftJourney::ErrorRenderer
|
|
5
|
+
|
|
6
|
+
before_action :set_user, only: [:show, :update, :destroy]
|
|
7
|
+
|
|
8
|
+
# PageState contains details for rendering and storing a Page related to a single State
|
|
9
|
+
#
|
|
10
|
+
def index
|
|
11
|
+
@collector_forms = Collector::FormDefinition.all
|
|
12
|
+
|
|
13
|
+
render json: @collector_forms, status: :ok
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@collector_form = Collector::FormDefinition.new(page_state_params)
|
|
18
|
+
|
|
19
|
+
if @collector_form.save
|
|
20
|
+
render json: @collector_form, status: :created
|
|
21
|
+
else
|
|
22
|
+
render_error(@collector_form, :unprocessable_entity) and return
|
|
23
|
+
#render json: { errors: @collector_form.errors }, status: :unprocessable_entity
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def show
|
|
28
|
+
render json: @collector_form
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Only allow a trusted parameter "white list" through.
|
|
34
|
+
def page_state_params
|
|
35
|
+
params.fetch(:page_state, {}).permit(:form_name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def set_user
|
|
39
|
+
begin
|
|
40
|
+
@collector_form = Collector::FormDefinition.find params[:id]
|
|
41
|
+
rescue ActiveRecord::RecordNotFound
|
|
42
|
+
collector_form = Collector::FormDefinition.new
|
|
43
|
+
collector_form.errors.add(:id, "Wrong ID provided")
|
|
44
|
+
render_error(collector_form, 404) and return
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
class ReviewsController < ApplicationController
|
|
4
|
+
|
|
5
|
+
include DatashiftJourney::ReviewRenderer
|
|
6
|
+
|
|
7
|
+
# We want this to run BEFORE other filters to ensure the current
|
|
8
|
+
# journey_plan object has been selected from the DB
|
|
9
|
+
|
|
10
|
+
prepend_before_filter :set_journey_plan, only: [:edit]
|
|
11
|
+
|
|
12
|
+
def edit
|
|
13
|
+
logger.info("Sent from REVIEW [#{params.inspect}]")
|
|
14
|
+
|
|
15
|
+
respond_to do |format|
|
|
16
|
+
format.html do
|
|
17
|
+
setup_view_data_for_state(params['state'])
|
|
18
|
+
|
|
19
|
+
render_state_under_review(params)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Never trust parameters, only allow the white list through.
|
|
27
|
+
def review_params
|
|
28
|
+
params.permit(:id, :state)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# This is a development tool - for creating and jumping straight to any State
|
|
2
|
+
#
|
|
3
|
+
# So that any data required for previous states can be created, it supports passing in a optional Factory
|
|
4
|
+
# that creates that data for you.
|
|
5
|
+
#
|
|
6
|
+
# The factory should return an instance of your DatashiftJourney.journey_plan_class
|
|
7
|
+
#
|
|
8
|
+
# For a full list of available factories you can call :
|
|
9
|
+
#
|
|
10
|
+
# FactoryGirl.definition_file_paths.inspect
|
|
11
|
+
#
|
|
12
|
+
# FactoryGirl.factories.collect(&:name).inspect
|
|
13
|
+
#
|
|
14
|
+
module DatashiftJourney
|
|
15
|
+
class StateJumperController < ApplicationController
|
|
16
|
+
|
|
17
|
+
unless Rails.env.production?
|
|
18
|
+
|
|
19
|
+
require 'factory_girl'
|
|
20
|
+
require 'faker'
|
|
21
|
+
|
|
22
|
+
def build_and_display
|
|
23
|
+
state = params['state']
|
|
24
|
+
factory = params['factory']
|
|
25
|
+
|
|
26
|
+
plan = if factory
|
|
27
|
+
# Get weird problems with factories with has_many associations, when they've already been used,
|
|
28
|
+
# so while obviously not very efficient, this seems to prevent that issue
|
|
29
|
+
FactoryGirl.reload
|
|
30
|
+
|
|
31
|
+
Rails.logger.debug("Building StateJumper #{DatashiftJourney.journey_plan_class} from [#{factory}]")
|
|
32
|
+
|
|
33
|
+
FactoryGirl.create(factory)
|
|
34
|
+
else
|
|
35
|
+
DatashiftJourney.journey_plan_class.create(state: state)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if plan.state != state
|
|
39
|
+
plan.skip_fwd(state.to_sym) until !plan.can_skip_fwd? || plan.state == state
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
plan.update_attribute(:state, state) if plan.state != state
|
|
43
|
+
|
|
44
|
+
Rails.logger.debug("Jumping to STATE [#{plan.state}]")
|
|
45
|
+
|
|
46
|
+
redirect_to(datashift_journey.journey_plan_state_path(state, plan)) && return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'reform/form'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
class FormObjectFactory
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
|
|
8
|
+
# Create a form object from the current states Form class
|
|
9
|
+
#
|
|
10
|
+
# Each form can provide a factory to drive how its constructed, or can rely on the base classes factory method
|
|
11
|
+
#
|
|
12
|
+
def form_object_for(journey_plan)
|
|
13
|
+
# Get ReForm form for current state
|
|
14
|
+
klass = form_class_for(journey_plan)
|
|
15
|
+
raise(FormObjectError, "Failed to load form class #{form_name(journey_plan.state)} for state #{journey_plan.state}") unless klass
|
|
16
|
+
|
|
17
|
+
# Create new instance of form for current journey instance
|
|
18
|
+
klass.new(journey_plan)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def form_name(state)
|
|
22
|
+
@form_name_mod ||= Configuration.call.forms_module_name
|
|
23
|
+
|
|
24
|
+
"#{@form_name_mod}::#{state.to_s.camelize}Form"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def state_name(form)
|
|
28
|
+
return form.chomp('Form').underscore if form.is_a?(String)
|
|
29
|
+
form.class.name.chomp('Form').underscore
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def null_form_for_state(journey_plan)
|
|
35
|
+
return DatashiftJourney::NullForm if Configuration.call.use_null_form_when_no_form
|
|
36
|
+
|
|
37
|
+
return DatashiftJourney:: NullForm if null_form_requirested?(journey_plan.state)
|
|
38
|
+
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def null_form_requirested?(state)
|
|
43
|
+
null_form_list.include?(state.to_s)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def null_form_list
|
|
47
|
+
@null_form_list ||= Configuration.call.null_form_list.map!(&:to_s)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Find the current state and its associated Form
|
|
51
|
+
def form_class_for(journey_plan)
|
|
52
|
+
form_name(journey_plan.state).constantize
|
|
53
|
+
rescue => x
|
|
54
|
+
null_form = null_form_for_state(journey_plan)
|
|
55
|
+
|
|
56
|
+
unless null_form
|
|
57
|
+
Rails.logger.debug(x.backtrace.first)
|
|
58
|
+
Rails.logger.debug(x.inspect)
|
|
59
|
+
Rails.logger.debug("No Form class found for state #{journey_plan.state} - #{x.message}")
|
|
60
|
+
return nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
null_form
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require_relative '../concerns/form_mixin'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
module Collector
|
|
5
|
+
|
|
6
|
+
# This class represents the View backing Form
|
|
7
|
+
#
|
|
8
|
+
# Reform API :
|
|
9
|
+
#
|
|
10
|
+
# initialize always requires a model that the form represents.
|
|
11
|
+
# validate(params) updates the form's fields with the input data (only the form, not the model) and then runs all validations. The return value is the boolean result of the validations.
|
|
12
|
+
# errors returns validation messages in a classic ActiveModel style.
|
|
13
|
+
# sync writes form data back to the model. This will only use setter methods on the model(s).
|
|
14
|
+
# save (optional) will call #save on the model and nested models. Note that this implies a #sync call.
|
|
15
|
+
# prepopulate! (optional) will run pre-population hooks to "fill out" your form before rendering.
|
|
16
|
+
#
|
|
17
|
+
class BaseCollectorForm < Reform::Form
|
|
18
|
+
|
|
19
|
+
include DatashiftJourney::FormMixin
|
|
20
|
+
|
|
21
|
+
feature Reform::Form::Dry # override the default.
|
|
22
|
+
|
|
23
|
+
attr_accessor :definition
|
|
24
|
+
|
|
25
|
+
# Called from CONTROLLER
|
|
26
|
+
#
|
|
27
|
+
# Creates a form object backed by the current Plan object
|
|
28
|
+
#
|
|
29
|
+
# Data is collected generically from fields defined by FormDefinition and stored
|
|
30
|
+
# in data nodes associated with current JourneyPlan instance (through polymorphic plan association)
|
|
31
|
+
#
|
|
32
|
+
def initialize(journey_plan)
|
|
33
|
+
super(journey_plan)
|
|
34
|
+
|
|
35
|
+
@journey_plan = journey_plan
|
|
36
|
+
|
|
37
|
+
@definition = DatashiftJourney::Collector::FormDefinition.where(klass: self.class.name).first
|
|
38
|
+
|
|
39
|
+
# For brand new forms, add one data node per form field - data nodes hold the COLLECTED VALUES
|
|
40
|
+
# If this page already been visited we should have a completed data node already
|
|
41
|
+
form_definition.form_fields.map(&:id).each do |id|
|
|
42
|
+
next if journey_plan.data_nodes.where('form_field_id = ?', id).exists?
|
|
43
|
+
journey_plan.data_nodes << DatashiftJourney::Collector::DataNode.new(plan: journey_plan, form_field_id: id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Over ride in your form if your view forms have non standard key field.
|
|
49
|
+
#
|
|
50
|
+
# The default naming format for form elements in the view is : "#{params_key}[data_nodes][field_value][#{i}]"
|
|
51
|
+
#
|
|
52
|
+
# For example:
|
|
53
|
+
# <%= select_tag "#{params_key}[data_nodes][field_value][#{i}]".... %>
|
|
54
|
+
def params_key
|
|
55
|
+
DatashiftJourney::FormObjectFactory.state_name(self.class.name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require_dependency 'reform'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
|
|
5
|
+
# Collection of tools to support the Forms
|
|
6
|
+
module FormMixin
|
|
7
|
+
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
# These forms are used to back Views so need to be able to prepare and present data for views
|
|
11
|
+
include ActionView::Helpers::FormOptionsHelper
|
|
12
|
+
|
|
13
|
+
attr_reader :journey_plan
|
|
14
|
+
attr_accessor :redirection_url
|
|
15
|
+
|
|
16
|
+
def validate(params)
|
|
17
|
+
Rails.logger.debug "VALIDATING #{model.inspect} - Params - #{form_params(params)}"
|
|
18
|
+
super form_params(params)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def redirect?
|
|
22
|
+
Rails.logger.debug "Checking for REDIRECTION - [#{redirection_url}]"
|
|
23
|
+
!redirection_url.nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class_methods do
|
|
27
|
+
|
|
28
|
+
def form_definition
|
|
29
|
+
# In this situation self is the class of the including form eg PaymentForm, AddressFrom
|
|
30
|
+
@form_definition ||= DatashiftJourney::Collector::FormDefinition.find_or_create_by!(klass: self.name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Form helper to add fields inside a class definition
|
|
34
|
+
#
|
|
35
|
+
# N.B Currently this will create a permanent entry in the DB,
|
|
36
|
+
# so removing this code will not remove the Field - must be deleted from DB
|
|
37
|
+
#
|
|
38
|
+
# Usage
|
|
39
|
+
#
|
|
40
|
+
# journey_plan_form_field name: :model_uri, category: :string
|
|
41
|
+
# journey_plan_form_field name: :run_time, category: :select_option
|
|
42
|
+
# journey_plan_form_field name: :memory, category: :number
|
|
43
|
+
#
|
|
44
|
+
def journey_plan_form_field(name:, category:)
|
|
45
|
+
DatashiftJourney::Collector::FormField.find_or_create_by!(form_definition: form_definition, name: name, category: category)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
protected
|
|
50
|
+
|
|
51
|
+
def form_params(params)
|
|
52
|
+
params.fetch(params_key, {})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def logger
|
|
56
|
+
Rails.logger
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def form_definition
|
|
60
|
+
@form_definition ||= self.class.form_definition
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require_dependency 'reform'
|
|
2
|
+
|
|
3
|
+
# A specialized Form class for use with states that do not require Form functionality,
|
|
4
|
+
# just render the Partial
|
|
5
|
+
|
|
6
|
+
module DatashiftJourney
|
|
7
|
+
class NullForm < BaseForm
|
|
8
|
+
def self.factory(journey_plan)
|
|
9
|
+
new(journey_plan)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def save
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate(_params)
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
|
|
4
|
+
# This is the main hook to insert a States partial view into the main Form
|
|
5
|
+
def render_if_exists(state, *args)
|
|
6
|
+
lookup_context.prefixes.prepend DatashiftJourney::Configuration.call.partial_location
|
|
7
|
+
|
|
8
|
+
render(state, *args) if lookup_context.exists?(state, lookup_context.prefixes, true)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns true if '_state' partial exists in configured location (Configuration.partial_location)
|
|
12
|
+
|
|
13
|
+
def journey_plan_partial?(state)
|
|
14
|
+
return true if lookup_context.exists?(state, [DatashiftJourney::Configuration.call.partial_location], true)
|
|
15
|
+
|
|
16
|
+
Rails.logger.warn("DSJ - No partial found for [#{state}] in path(s) [#{lookup_context.prefixes.inspect}]")
|
|
17
|
+
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# helper to return the location of a partial for a particular state
|
|
22
|
+
def journey_plan_partial_location(state)
|
|
23
|
+
Rails.logger.debug("DatashiftJourney RENDER #{DatashiftJourney::Configuration.call.partial_location}/#{state}}")
|
|
24
|
+
File.join(DatashiftJourney::Configuration.call.partial_location.to_s, state)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def error_link_id(attribute)
|
|
28
|
+
# with nested attributes can get full path e.g applicant_contact.full_name
|
|
29
|
+
# we only want the last field
|
|
30
|
+
field = attribute.to_s.split(/\./).last
|
|
31
|
+
"form_group_#{field}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def all_errors(record)
|
|
35
|
+
record.class.reflect_on_all_associations.each do |a|
|
|
36
|
+
assoc = @journey_plan.send(a.name)
|
|
37
|
+
next unless assoc && assoc.respond_to?(:errors)
|
|
38
|
+
assoc.errors.full_messages.each do |_message|
|
|
39
|
+
"<li><a href='<%= message %>'></a></li>"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def friendly_date(date)
|
|
45
|
+
formatted_date = date && l(date.to_date, format: :long)
|
|
46
|
+
formatted_date || ''
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
module BackLinkHelper
|
|
3
|
+
|
|
4
|
+
# Helper to create a standard Back link that will jump back to previous state
|
|
5
|
+
def back_a_state_link(journey_plan = nil, css: nil)
|
|
6
|
+
DatashiftJourney::BackLink.new(request, engine_routes: datashift_journey, journey_plan: journey_plan, css: css).tag
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|