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,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
|