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,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
datashift_journey:
|
|
4
|
+
shared:
|
|
5
|
+
errors:
|
|
6
|
+
you_have: You have
|
|
7
|
+
global:
|
|
8
|
+
back: Back
|
|
9
|
+
continue: Continue
|
|
10
|
+
journey_plan:
|
|
11
|
+
continue: Continue
|
|
12
|
+
404_notfound:
|
|
13
|
+
heading: Page not found
|
|
14
|
+
message1: Sorry, the page you’re looking for cannot be found.
|
|
15
|
+
message2: Please check the web address you entered was correct.
|
|
16
|
+
422_unprocessable_entity:
|
|
17
|
+
heading: The change you wanted was rejected
|
|
18
|
+
message1: Maybe you tried to change something you didn't have access to?
|
|
19
|
+
500_internal_server_error:
|
|
20
|
+
heading: Something went wrong
|
|
21
|
+
message1: Sorry, there is a technical problem.
|
|
22
|
+
message2: Please try again in a few minutes.
|
|
23
|
+
siteHeader_html: Override global.siteHeader_html in en.yml
|
|
24
|
+
401_unauthorized:
|
|
25
|
+
heading: Unauthorised
|
|
26
|
+
message1: You are not permitted to see this page.
|
|
27
|
+
message2: This page is restricted to certain users only.
|
|
28
|
+
403_forbidden:
|
|
29
|
+
# As per https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
|
|
30
|
+
# We do not wish to make this information available to the client, therefore using messaging for 404-not-found instead.
|
|
31
|
+
heading: Page not found
|
|
32
|
+
message1: Sorry, the page you’re looking for cannot be found.
|
|
33
|
+
message2: Please check the web address you entered was correct.
|
|
34
|
+
503_bad_gateway:
|
|
35
|
+
heading: Service unavailable
|
|
36
|
+
message1: Please try again later. If the problem persists, call the Environment Agency helpline 03708 506 506.
|
|
37
|
+
invalid_authenticity_token:
|
|
38
|
+
heading: The cookies in your browser have been disabled
|
|
39
|
+
message_html: "To carry on with your registration please enable session %{link} in your browser."
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
DatashiftJourney::Engine.routes.draw do
|
|
2
|
+
|
|
3
|
+
resources :journey_plans, only: [:create, :new, :edit, :update]
|
|
4
|
+
|
|
5
|
+
# TODO - Devise generates routes based on module selection .. like we are trying to do with optional Collector
|
|
6
|
+
# https://github.com/plataformatec/devise/blob/88724e10adaf9ffd1d8dbfbaadda2b9d40de756a/lib/devise/rails/routes.rb
|
|
7
|
+
|
|
8
|
+
# TO INVESTIGATE - On an error processing a state user is redirected but this goes to
|
|
9
|
+
# => http://localhost:3000/journey_plans/(:id) via a get- which is index or show
|
|
10
|
+
# This currently fixes the issue so a refresh leaves user on right page
|
|
11
|
+
get '/journey_plans(.:format)', :to => 'journey_plans#create'
|
|
12
|
+
get '/journey_plans/:id', :to => 'journey_plans#edit'
|
|
13
|
+
|
|
14
|
+
# Note on using get rather than patch :
|
|
15
|
+
# We use these to provide nicer links - to jump between states & fwd/backwards,
|
|
16
|
+
# but if the Visitor has JS disabled, link_to falls back to get even if patch, put etc specified
|
|
17
|
+
get 'journey_plans/state/back/*id', :to => '/datashift_journey/journey_plans#back_a_state', :as => :back_a_state
|
|
18
|
+
|
|
19
|
+
# These forms enables us to copy and paste url and see the state in the browser url
|
|
20
|
+
get '/journey_plans/:state/:id', :to => 'journey_plans#edit', :as => :journey_plan_state
|
|
21
|
+
|
|
22
|
+
# factory is optional - without it will create bare bones journey_class object with state set
|
|
23
|
+
get '/state_jumper/:state/(:factory)', :to => 'state_jumper#build_and_display', :as => :build_and_display
|
|
24
|
+
|
|
25
|
+
resources :journey_ends, only: [:show]
|
|
26
|
+
|
|
27
|
+
=begin
|
|
28
|
+
|
|
29
|
+
patch '/journey_plans/update/:state', :to => 'journey_plans#update', :as => :update_journey_plan
|
|
30
|
+
|
|
31
|
+
get '/journey_plans/:id', :to => 'journey_plans#edit'
|
|
32
|
+
|
|
33
|
+
get 'reviews/:state/*id', :to => '/datashift_journey/reviews#edit', :as => :review_state
|
|
34
|
+
|
|
35
|
+
# We have Abandonment derived from high voltage to manage static content
|
|
36
|
+
# related to User's abandoning
|
|
37
|
+
get "/abandonments/*id" => 'abandonments#show', as: :abandonment, format: false
|
|
38
|
+
|
|
39
|
+
# includes the high voltage static page to display
|
|
40
|
+
get "/abandon_journey_plans/:page/:id", to: 'abandon_journey_plans#show', as: :abandon_journey_plan
|
|
41
|
+
=end
|
|
42
|
+
|
|
43
|
+
match '(errors)/:status', to: 'errors#show', via: :all, constraints: { status: /\d{3}/ }
|
|
44
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
module Collector
|
|
3
|
+
|
|
4
|
+
class Snippet < ActiveRecord::Base
|
|
5
|
+
|
|
6
|
+
self.table_name = 'dsj_snippets'
|
|
7
|
+
|
|
8
|
+
def self.to_sentance(snippets)
|
|
9
|
+
snippets.collect { |s| s.I18n_key.present? ? I18n.t(s.I18n_key) : s.raw_text }.join(' ')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
# Helper struct for displaying available account types in view
|
|
4
|
+
Struct.new('StateJumperEntry', :state, :display, :factory)
|
|
5
|
+
|
|
6
|
+
class Configuration
|
|
7
|
+
|
|
8
|
+
# The start page link for journey page Back links
|
|
9
|
+
#
|
|
10
|
+
# @param [String<#call>]
|
|
11
|
+
# @return [String<#call>]
|
|
12
|
+
#
|
|
13
|
+
attr_writer :backto_start_url
|
|
14
|
+
|
|
15
|
+
# The module(s) under which to find Forms
|
|
16
|
+
# Form factory will look for a Form class related to a state called
|
|
17
|
+
#
|
|
18
|
+
# "#{mod}::#{journey_plan.state}Form"
|
|
19
|
+
#
|
|
20
|
+
# @param [String<#call>] module name under which Forms reside
|
|
21
|
+
# @return [String<#call>]
|
|
22
|
+
#
|
|
23
|
+
attr_accessor :forms_module_name
|
|
24
|
+
|
|
25
|
+
# When no Form is required for a specific HTML page, you an specify that a NullForm is to be used
|
|
26
|
+
# by adding that state to this list
|
|
27
|
+
#
|
|
28
|
+
# @param [Array<#call>] List of states that require only a NullForm
|
|
29
|
+
# @return [Array<#call>]
|
|
30
|
+
#
|
|
31
|
+
attr_accessor :null_form_list
|
|
32
|
+
|
|
33
|
+
# Always use a NullForm when no designated Form for a page
|
|
34
|
+
# @param [Boolean<#call>]
|
|
35
|
+
# @return [Boolean<#call>]
|
|
36
|
+
#
|
|
37
|
+
attr_accessor :use_null_form_when_no_form
|
|
38
|
+
|
|
39
|
+
# The location of the View partials, for rendering
|
|
40
|
+
#
|
|
41
|
+
# default is empty, for views stored directly in app/views
|
|
42
|
+
#
|
|
43
|
+
attr_accessor :partial_location
|
|
44
|
+
|
|
45
|
+
attr_accessor :layout
|
|
46
|
+
|
|
47
|
+
# In development, you can add a state jumper toolbar, for jumping straight to any state
|
|
48
|
+
#
|
|
49
|
+
attr_accessor :add_state_jumper_toolbar
|
|
50
|
+
|
|
51
|
+
# Add required details of states to jump to (see StateJumperEntry) to this ARRAY
|
|
52
|
+
#
|
|
53
|
+
# So that any data required for previous states can be created, it supports passing in a Factory
|
|
54
|
+
# that creates that data for you by value. Use nil if no data required
|
|
55
|
+
#
|
|
56
|
+
# The factory should return an instance of your DatashiftJourney.journey_plan_class
|
|
57
|
+
#
|
|
58
|
+
# @param [ StateJumperEntry.new(:state_name, "State Name", :state_factory), .... ]
|
|
59
|
+
#
|
|
60
|
+
attr_accessor :state_jumper_states
|
|
61
|
+
|
|
62
|
+
def initialize
|
|
63
|
+
@forms_module_name = ''
|
|
64
|
+
|
|
65
|
+
@partial_location = ''
|
|
66
|
+
|
|
67
|
+
@use_null_form_when_no_form = false
|
|
68
|
+
@null_form_list = []
|
|
69
|
+
|
|
70
|
+
@layout = 'application'
|
|
71
|
+
@add_state_jumper_toolbar = false
|
|
72
|
+
@state_jumper_states = []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def backto_start_url
|
|
76
|
+
@backto_start_url ||= Rails.application.routes.url_helpers.root_path
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [DatashiftJourney::Configuration] current configuration
|
|
80
|
+
def self.call
|
|
81
|
+
@configuration ||= DatashiftJourney::Configuration.new
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.reset
|
|
85
|
+
@configuration = DatashiftJourney::Configuration.new
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @param config [DatashiftJourney::Configuration]
|
|
89
|
+
class << self
|
|
90
|
+
attr_writer :configuration
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Modify current DatashiftJourney configuration
|
|
94
|
+
# ```
|
|
95
|
+
# DatashiftJourney::Configuration.configure do |config|
|
|
96
|
+
# config.html_only = false
|
|
97
|
+
# end
|
|
98
|
+
# ```
|
|
99
|
+
def self.configure
|
|
100
|
+
yield call
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require_relative 'exceptions'
|
|
3
|
+
require_relative 'configuration'
|
|
4
|
+
require_relative 'journey/machine_builder'
|
|
5
|
+
|
|
6
|
+
require_relative 'reference_generator'
|
|
7
|
+
require_relative 'helpers/back_link'
|
|
8
|
+
|
|
9
|
+
rescue => x
|
|
10
|
+
# TODO: - remove this block once gem stable
|
|
11
|
+
puts x.inspect
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module DatashiftJourney
|
|
15
|
+
|
|
16
|
+
class Engine < ::Rails::Engine
|
|
17
|
+
|
|
18
|
+
isolate_namespace DatashiftJourney
|
|
19
|
+
|
|
20
|
+
# Add a load path for this specific Engine
|
|
21
|
+
config.autoload_paths += Dir["#{config.root}/lib"]
|
|
22
|
+
|
|
23
|
+
# Make Shared examples and Support files available to Apps and other Engines
|
|
24
|
+
# TODO: - make this optional - i.e installable so Apps/Engines can easily pull this in themselves if they wish
|
|
25
|
+
# if Rails.env.test? && defined?(RSpec)
|
|
26
|
+
# initializer 'datashift_journey.shared_examples' do
|
|
27
|
+
# RSpec.configure do
|
|
28
|
+
# Dir[File.join(File.expand_path('../../../spec/shared_examples', __FILE__), '**/*.rb')].each { |f| require f }
|
|
29
|
+
# Dir[File.join(File.expand_path('../../../spec/support', __FILE__), '**/*.rb')].each { |f| require f }
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# config.autoload_paths << File.expand_path('../../../spec/support', __FILE__)
|
|
34
|
+
# end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
class CoreException < StandardError
|
|
4
|
+
|
|
5
|
+
def initialize(msg)
|
|
6
|
+
super
|
|
7
|
+
Rails.logger.error(msg)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.generate(name)
|
|
11
|
+
new_class = Class.new(CoreException) do
|
|
12
|
+
def initialize(msg)
|
|
13
|
+
super(msg)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
DatashiftJourney.const_set(name, new_class)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class FormObjectError < CoreException; end
|
|
23
|
+
class PlannerApiError < CoreException; end
|
|
24
|
+
class PlannerBlockError < CoreException; end
|
|
25
|
+
|
|
26
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Helper class for constructing back links for navigating backward through the journey
|
|
3
|
+
#
|
|
4
|
+
module DatashiftJourney
|
|
5
|
+
|
|
6
|
+
class BackLink
|
|
7
|
+
|
|
8
|
+
include ActionView::Helpers::UrlHelper
|
|
9
|
+
|
|
10
|
+
attr_reader :css, :current_request, :journey_plan, :engine_routes
|
|
11
|
+
|
|
12
|
+
def initialize(request, engine_routes:, journey_plan: nil, css: nil)
|
|
13
|
+
@current_request = request
|
|
14
|
+
@engine_routes = engine_routes
|
|
15
|
+
@journey_plan = journey_plan
|
|
16
|
+
@css = css
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tag(text = nil, html_opts = {})
|
|
20
|
+
if journey_plan && (journey_plan.can_back?)
|
|
21
|
+
title, url = link_arguments(text)
|
|
22
|
+
link_to title, url, html_opts.merge(class: css || 'journey-plan-back-link')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def link_arguments(text = nil)
|
|
29
|
+
[text || link_text, link_url]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def link_text
|
|
33
|
+
I18n.t(journey_plan ? 'global.back' : 'global.back_to_start_link')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def link_url
|
|
37
|
+
# TODO: Implement automatic reviewable
|
|
38
|
+
# return journey_plan_reviewing_path if journey_plan.try! :under_review?
|
|
39
|
+
|
|
40
|
+
return start_url unless journey_plan
|
|
41
|
+
|
|
42
|
+
journey_plan_back_url
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def start_url
|
|
46
|
+
DatashiftJourney::Configuration.call.backto_start_url
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def journey_plan_back_url
|
|
50
|
+
engine_routes.back_a_state_url(journey_plan)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def journey_plan_reviewing_path
|
|
54
|
+
engine_routes.journey_plan_state_path('reviewing', journey_plan)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
module Journey
|
|
3
|
+
|
|
4
|
+
# N.B This gem is based on the AR integration which uses the Machine name for the Column storing the state in
|
|
5
|
+
# The default name and column is :state
|
|
6
|
+
#
|
|
7
|
+
class MachineBuilder
|
|
8
|
+
|
|
9
|
+
# The available API is defined in : ../state_machines/planner.rb
|
|
10
|
+
#
|
|
11
|
+
# DatashiftJourney::Journey::MachineBuilder.create_journey_plan(initial: :deployment) do
|
|
12
|
+
# sequence [:deployment,
|
|
13
|
+
# :default_predicator,
|
|
14
|
+
# :version,
|
|
15
|
+
# :launch]
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# Args
|
|
19
|
+
# :machine_name => :something_different # Requires Class has a string column also called something_different
|
|
20
|
+
# :initial => :first_state
|
|
21
|
+
#
|
|
22
|
+
def self.create_journey_plan(args = {}, &block)
|
|
23
|
+
DatashiftJourney.journey_plan_class.send :include, DatashiftJourney::StateMachines::Extensions
|
|
24
|
+
DatashiftJourney.journey_plan_class.send :extend, DatashiftJourney::StateMachines::Extensions
|
|
25
|
+
|
|
26
|
+
create_journey_plan_for(DatashiftJourney.journey_plan_class, args, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Args
|
|
30
|
+
# :machine_name => :something_different # Requires Class has a column called something_different
|
|
31
|
+
# :initial => :first_state
|
|
32
|
+
#
|
|
33
|
+
def self.create_journey_plan_for(klass, args = {}, &block)
|
|
34
|
+
machine_name = args.key?(:machine_name) ? args.delete(:machine_name) : :state
|
|
35
|
+
|
|
36
|
+
puts "Building journey plan State Machine [#{machine_name}] for [#{klass}]"
|
|
37
|
+
|
|
38
|
+
machine = if block_given?
|
|
39
|
+
klass.class_eval do
|
|
40
|
+
state_machine machine_name, args do
|
|
41
|
+
|
|
42
|
+
init_plan
|
|
43
|
+
|
|
44
|
+
instance_eval(&block) # sets the context to be StateMachine class'
|
|
45
|
+
|
|
46
|
+
# Journey has been pre parsed, we have the states, - now build navigation events and transitions
|
|
47
|
+
instance_eval('build_journey_plan')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
::StateMachines::Machine.new(klass, machine_name, args)
|
|
52
|
+
end
|
|
53
|
+
machine
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
# module PrepareDataForReview
|
|
4
|
+
#
|
|
5
|
+
# extend ActiveSupport::Concern
|
|
6
|
+
#
|
|
7
|
+
# def self.included(base)
|
|
8
|
+
# base.send :include, InstanceMethods
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# module InstanceMethods
|
|
12
|
+
#
|
|
13
|
+
# # You can build your review page within a YAML config file (locale file)
|
|
14
|
+
# # It supports a simple DSL in below format.
|
|
15
|
+
# #
|
|
16
|
+
# # SYNTAX :
|
|
17
|
+
# # Indentation, usually 2 spaces, or a 2 space TAB, is very important
|
|
18
|
+
# # <> are used to illustrate the elements that accept free text
|
|
19
|
+
# #
|
|
20
|
+
# # ROW :
|
|
21
|
+
# #
|
|
22
|
+
# # Each ReviewDataRow has upto 3 elements
|
|
23
|
+
# #
|
|
24
|
+
# # :title: The string for Column 1, the row header
|
|
25
|
+
# #
|
|
26
|
+
# # :method: The actual database data for Column2, method to call on the model, or the association object
|
|
27
|
+
# #
|
|
28
|
+
# # :link_state: The state the change me links, jumps back to.
|
|
29
|
+
# # Optional, if unspecified, uses the section block (state)
|
|
30
|
+
# #
|
|
31
|
+
# # FULL DSL :
|
|
32
|
+
# #
|
|
33
|
+
# # <key>:
|
|
34
|
+
# # sections:
|
|
35
|
+
# # <section_block - usually a state>: # The Link target defaults to this, if no explicit state set
|
|
36
|
+
# # section_heading: "Farming data" # The section title - that spans the 3 columns
|
|
37
|
+
# # direct: # Non association data i.e directly on the parent model
|
|
38
|
+
# # - :title: "Activity is on a farm" # Column title. The - indicates a list, each mini block is a row
|
|
39
|
+
# # :method: on_a_farm # Method called on association to get real (DB) data to display
|
|
40
|
+
# # - :title: "Registrant is a farmer" # Next mini block - Each section can have multiple rows
|
|
41
|
+
# # :method: is_a_farmer
|
|
42
|
+
# # <next section_block - choosing_site_address>:
|
|
43
|
+
# # section_heading: Waste Activity Location
|
|
44
|
+
# # associations:
|
|
45
|
+
# # <association> # As well as direct, supports Rails associations, on supplied model
|
|
46
|
+
# # - :title: "1st Column"
|
|
47
|
+
# # :method: full_name # Ruby method called on association, returns real (DB) data to display
|
|
48
|
+
# # :link_state: state # The link target - i.e state to JUMP to, in order to EDIT the data
|
|
49
|
+
# # site_address: # Again, section can contain multiple rows, from multiple associations
|
|
50
|
+
# # - :title: "Address"
|
|
51
|
+
# # :method: # No method supplied, data will be WHOLE associated object
|
|
52
|
+
# #
|
|
53
|
+
# # EXAMPLE:
|
|
54
|
+
# #
|
|
55
|
+
# # journey_plan_review:
|
|
56
|
+
# # sections:
|
|
57
|
+
# # choosing_farming_data:
|
|
58
|
+
# # section_heading: "Farming data"
|
|
59
|
+
# # direct:
|
|
60
|
+
# # - :title: "Activity is on a farm"
|
|
61
|
+
# # :method: on_a_farm
|
|
62
|
+
# # - :title: "Registrant is a farmer"
|
|
63
|
+
# # :method: is_a_farmer
|
|
64
|
+
# # choosing_site_address:
|
|
65
|
+
# # section_heading: Waste Activity Location
|
|
66
|
+
# # associations:
|
|
67
|
+
# # site_address:
|
|
68
|
+
# # - :title: "Address"
|
|
69
|
+
# # :method: # No state cos Partial can render WHOLE Address object
|
|
70
|
+
# # - :title: "Grid reference"
|
|
71
|
+
# # :method: grid_reference
|
|
72
|
+
# # choosing_exemption:
|
|
73
|
+
# # section_heading: Waste Exemption Codes
|
|
74
|
+
# # associations:
|
|
75
|
+
# # exemption:
|
|
76
|
+
# # - :title: "Waste Exemptions"
|
|
77
|
+
# # :method: code
|
|
78
|
+
# #
|
|
79
|
+
# # NOTES
|
|
80
|
+
# #
|
|
81
|
+
# # Method can be left blank, in this case the data is the association object, so this must be display-able
|
|
82
|
+
# #
|
|
83
|
+
# # For example, this will cause @journey_plan.site_address to be sent to the review partial, which we know has
|
|
84
|
+
# # special use case for Address, which renders a display address partial
|
|
85
|
+
# #
|
|
86
|
+
# # site_address:
|
|
87
|
+
# # - :title: "Address"
|
|
88
|
+
# # :method:
|
|
89
|
+
# #
|
|
90
|
+
# attr_reader :review_data
|
|
91
|
+
#
|
|
92
|
+
# # rubocop:disable Metrics/MethodLength
|
|
93
|
+
#
|
|
94
|
+
# def prepare_from_locale(model, locale_key = '.journey_plan_review')
|
|
95
|
+
# sections = I18n.t("#{locale_key}.sections", default: {})
|
|
96
|
+
#
|
|
97
|
+
# Rails.logger.debug("Review Sections: #{sections.inspect}")
|
|
98
|
+
#
|
|
99
|
+
# unless sections.is_a?(Hash)
|
|
100
|
+
# Rails.logger.error("Bad syntax in your review YAML - Expect a 'sections' Hash element")
|
|
101
|
+
# raise "Bad syntax in your review YAML - Expect a 'sections' Hash element"
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# @model_object = model
|
|
105
|
+
#
|
|
106
|
+
# # Return a collection of Sections (ReviewData), each section has multiple rows
|
|
107
|
+
#
|
|
108
|
+
# sections.collect do |section, data|
|
|
109
|
+
# next unless data[:section_heading]
|
|
110
|
+
#
|
|
111
|
+
# @current_section = section
|
|
112
|
+
#
|
|
113
|
+
# @review_data = ReviewDataSection.new(data[:section_heading])
|
|
114
|
+
#
|
|
115
|
+
# # Possible Enhancement required :
|
|
116
|
+
# # As it stands this will NOT preserve the Order as defined exactly in the YAML as it's a HASH
|
|
117
|
+
# # with keys - for direct & association sections - which are unordered.
|
|
118
|
+
# # So currently had to make arbitrary decision to process direct first, then associations
|
|
119
|
+
#
|
|
120
|
+
# # Direct data on parent Model, where key is - direct:
|
|
121
|
+
# key = "#{locale_key}.sections.#{section}.direct"
|
|
122
|
+
#
|
|
123
|
+
# I18n.t(key).each { |column| row_to_review_data(model_object, column) } if I18n.exists?(key)
|
|
124
|
+
#
|
|
125
|
+
# # Associated data - children of parent
|
|
126
|
+
# key = "#{locale_key}.sections.#{current_section}.associations"
|
|
127
|
+
#
|
|
128
|
+
# if I18n.exists?(key)
|
|
129
|
+
#
|
|
130
|
+
# association_list = I18n.t("#{locale_key}.sections.#{current_section}.associations", default: [])
|
|
131
|
+
#
|
|
132
|
+
# association_list.each do |association_data|
|
|
133
|
+
# unless association_data.size == 2
|
|
134
|
+
# Rails.logger.error('Bad syntax in your review YAML - expect each association to have name and fields')
|
|
135
|
+
# next
|
|
136
|
+
# end
|
|
137
|
+
#
|
|
138
|
+
# # Each association should have a row defined as a list i.e Array
|
|
139
|
+
# # - :title: Business trading name
|
|
140
|
+
# # :name: full_name
|
|
141
|
+
# #
|
|
142
|
+
# unless association_data[1].respond_to?(:each)
|
|
143
|
+
# Rails.logger.error('Bad syntax in review YAML - each row needs a title, method and optional link')
|
|
144
|
+
# next
|
|
145
|
+
# end
|
|
146
|
+
#
|
|
147
|
+
# # The first element is the association name or chain,
|
|
148
|
+
# # i.e method(s) to call on the parent model to reach child with the actual data
|
|
149
|
+
# association_method = association_data[0].to_s
|
|
150
|
+
#
|
|
151
|
+
# review_object = begin
|
|
152
|
+
# find_association(association_method)
|
|
153
|
+
# rescue => e
|
|
154
|
+
# Rails.logger.error(e.message)
|
|
155
|
+
# Rails.logger.error("Bad syntax in review YAML - Could not load associated object #{association_method}")
|
|
156
|
+
# next
|
|
157
|
+
# end
|
|
158
|
+
#
|
|
159
|
+
# unless review_object
|
|
160
|
+
# Rails.logger.error("Nil association for #{association_method} on #{model} - no review data available")
|
|
161
|
+
# next
|
|
162
|
+
# end
|
|
163
|
+
#
|
|
164
|
+
# # The second element is a list of rows, made up of title, method to call on association and the state link
|
|
165
|
+
# association_data[1].each { |column| row_to_review_data(review_object, column) }
|
|
166
|
+
# end
|
|
167
|
+
# end
|
|
168
|
+
#
|
|
169
|
+
# review_data
|
|
170
|
+
# end
|
|
171
|
+
# end
|
|
172
|
+
#
|
|
173
|
+
# private
|
|
174
|
+
#
|
|
175
|
+
# attr_accessor :current_section, :current_review_object, :review_data
|
|
176
|
+
#
|
|
177
|
+
# attr_accessor :model_object
|
|
178
|
+
#
|
|
179
|
+
# def row_to_review_data(review_object, row)
|
|
180
|
+
# # The section name, can be used as the state, for linking whole section, rather than at field level
|
|
181
|
+
# link_state = row[:link_state] || current_section
|
|
182
|
+
# link_title = row[:link_title]
|
|
183
|
+
#
|
|
184
|
+
# @current_review_object = review_object
|
|
185
|
+
#
|
|
186
|
+
# # The review partial can support whole objects, or low level data from method call defined in the DSL
|
|
187
|
+
# if row[:method].blank?
|
|
188
|
+
# review_data.add(row[:title], review_object, link_state.to_s, link_title)
|
|
189
|
+
# else
|
|
190
|
+
# # rubocop:disable Style/IfInsideElse
|
|
191
|
+
# if review_object.respond_to?(:each)
|
|
192
|
+
# review_object.each do |o|
|
|
193
|
+
# @current_review_object = o
|
|
194
|
+
# review_data.add(row[:title], send_chain(row[:method]), link_state.to_s, link_title)
|
|
195
|
+
# end
|
|
196
|
+
# else
|
|
197
|
+
# review_data.add(row[:title], send_chain(row[:method]), link_state.to_s, link_title)
|
|
198
|
+
# end
|
|
199
|
+
#
|
|
200
|
+
# end
|
|
201
|
+
# end
|
|
202
|
+
#
|
|
203
|
+
# def find_association(method_chain)
|
|
204
|
+
# method_chain.to_s.split('.').inject(model_object) { |a, e| a.send(e) }
|
|
205
|
+
# end
|
|
206
|
+
#
|
|
207
|
+
# def send_chain(method_chain)
|
|
208
|
+
# arr = method_chain.to_s.split('.')
|
|
209
|
+
# begin
|
|
210
|
+
# arr.inject(current_review_object) { |a, e| a.send(e) }
|
|
211
|
+
# rescue => e
|
|
212
|
+
# Rails.logger.error("Failed to process method chain #{method_chain} : #{e.message}")
|
|
213
|
+
# return I18n.t('.journey_plan_review.missing_data')
|
|
214
|
+
# end
|
|
215
|
+
# end
|
|
216
|
+
#
|
|
217
|
+
# end
|
|
218
|
+
# end
|
|
219
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module DatashiftJourney
|
|
2
|
+
|
|
3
|
+
class ReferenceGenerator < Module
|
|
4
|
+
BASE = 10
|
|
5
|
+
DEFAULT_LENGTH = 9
|
|
6
|
+
NUMBERS = (0..9).to_a.freeze
|
|
7
|
+
LETTERS = ('A'..'Z').to_a.freeze
|
|
8
|
+
|
|
9
|
+
attr_accessor :prefix, :length
|
|
10
|
+
|
|
11
|
+
def initialize(options)
|
|
12
|
+
@random = Random.new
|
|
13
|
+
@prefix = options.fetch(:prefix)
|
|
14
|
+
@length = options.fetch(:length, DEFAULT_LENGTH)
|
|
15
|
+
@candidates = NUMBERS + (options[:letters] ? LETTERS : [])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def included(host)
|
|
19
|
+
generator_method = method(:generate_permalink)
|
|
20
|
+
generator_instance = self
|
|
21
|
+
|
|
22
|
+
host.class_eval do
|
|
23
|
+
validates(:reference, presence: true, uniqueness: { allow_blank: true })
|
|
24
|
+
|
|
25
|
+
before_validation do |instance|
|
|
26
|
+
instance.reference ||= generator_method.call(host)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
define_singleton_method(:reference_generator) { generator_instance }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def generate_permalink(host)
|
|
36
|
+
length = @length
|
|
37
|
+
|
|
38
|
+
loop do
|
|
39
|
+
candidate = new_candidate(length)
|
|
40
|
+
return candidate unless host.exists?(reference: candidate)
|
|
41
|
+
|
|
42
|
+
# If over half of all possible options are taken add another digit.
|
|
43
|
+
length += 1 if host.count > Rational(BASE**length, 2)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def new_candidate(length)
|
|
48
|
+
@prefix + Array.new(length) { @candidates.sample(random: @random) }.join
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|