datashift_journey 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|