metadata_presenter 2.3.6 → 2.5.0
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 +4 -4
- data/app/controllers/metadata_presenter/answers_controller.rb +4 -2
- data/app/controllers/metadata_presenter/engine_controller.rb +8 -0
- data/app/models/metadata_presenter/flow.rb +4 -0
- data/app/models/metadata_presenter/grid.rb +203 -0
- data/app/models/metadata_presenter/next_page.rb +39 -3
- data/app/models/metadata_presenter/page.rb +7 -2
- data/app/models/metadata_presenter/route.rb +76 -0
- data/fixtures/branching_2.json +686 -0
- data/fixtures/branching_2.png +0 -0
- data/fixtures/branching_3.json +539 -0
- data/fixtures/branching_3.png +0 -0
- data/fixtures/branching_4.json +557 -0
- data/fixtures/branching_4.png +0 -0
- data/fixtures/branching_5.json +622 -0
- data/fixtures/branching_5.png +0 -0
- data/fixtures/branching_6.json +493 -0
- data/fixtures/branching_6.png +0 -0
- data/fixtures/branching_7.json +622 -0
- data/fixtures/branching_7.png +0 -0
- data/fixtures/branching_8.json +560 -0
- data/fixtures/branching_8.png +0 -0
- data/lib/metadata_presenter/version.rb +1 -1
- data/lib/metadata_presenter.rb +1 -0
- metadata +22 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bd3891b62938a67dd025d49a96098778ad66221bf62cb2351002906e78aa4b4
|
4
|
+
data.tar.gz: 9dba99d36da50beea7a209d27f3371e4e814776d86d609436ac500c6d91cbd14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8c2dde3ca3e374e0f50eebc881a64fe676f4d6fa0156ab73e26e79e0c9a19292c4c21d55665171441c52af3c56a6472783365df050246ba7e583da89db112ce
|
7
|
+
data.tar.gz: c114dddbf37af9e800c9eb6c4601d6f250175efd1b35f9159a728edb0c3239357f6f35b281bd1280792101d669885e6f48218ffdeca876edbfbf5af5f0f9051e
|
@@ -3,6 +3,7 @@ module MetadataPresenter
|
|
3
3
|
before_action :check_page_exists
|
4
4
|
|
5
5
|
def create
|
6
|
+
@previous_answers = reload_user_data.deep_dup
|
6
7
|
@page_answers = PageAnswers.new(page, answers_params)
|
7
8
|
|
8
9
|
upload_files if upload?
|
@@ -28,8 +29,9 @@ module MetadataPresenter
|
|
28
29
|
next_page = NextPage.new(
|
29
30
|
service: service,
|
30
31
|
session: session,
|
31
|
-
user_data:
|
32
|
-
current_page_url: page_url
|
32
|
+
user_data: reload_user_data,
|
33
|
+
current_page_url: page_url,
|
34
|
+
previous_answers: @previous_answers
|
33
35
|
).find
|
34
36
|
|
35
37
|
if next_page.present?
|
@@ -5,6 +5,14 @@ module MetadataPresenter
|
|
5
5
|
helper MetadataPresenter::ApplicationHelper
|
6
6
|
default_form_builder GOVUKDesignSystemFormBuilder::FormBuilder
|
7
7
|
|
8
|
+
def reload_user_data
|
9
|
+
if defined? super
|
10
|
+
super
|
11
|
+
else
|
12
|
+
load_user_data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
8
16
|
def back_link
|
9
17
|
previous_page = PreviousPage.new(
|
10
18
|
service: service,
|
@@ -16,6 +16,10 @@ module MetadataPresenter
|
|
16
16
|
metadata['next']['default']
|
17
17
|
end
|
18
18
|
|
19
|
+
def all_destination_uuids
|
20
|
+
conditionals.map(&:next).append(default_next)
|
21
|
+
end
|
22
|
+
|
19
23
|
def conditionals
|
20
24
|
Array(metadata['next']['conditionals']).map do |conditional_metadata|
|
21
25
|
Conditional.new(conditional_metadata)
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module MetadataPresenter
|
2
|
+
class Spacer < OpenStruct
|
3
|
+
end
|
4
|
+
|
5
|
+
class Grid
|
6
|
+
def initialize(service)
|
7
|
+
@service = service
|
8
|
+
@ordered = []
|
9
|
+
@routes = []
|
10
|
+
@traversed = []
|
11
|
+
@coordinates = setup_coordinates
|
12
|
+
end
|
13
|
+
|
14
|
+
ROW_ZERO = 0
|
15
|
+
|
16
|
+
def build
|
17
|
+
@ordered = make_grid
|
18
|
+
add_columns
|
19
|
+
add_rows
|
20
|
+
add_by_coordinates
|
21
|
+
trim_spacers
|
22
|
+
insert_expression_spacers
|
23
|
+
|
24
|
+
@ordered
|
25
|
+
end
|
26
|
+
|
27
|
+
def ordered_flow
|
28
|
+
@ordered_flow ||= begin
|
29
|
+
flow = @ordered.empty? ? build.flatten : @ordered.flatten
|
30
|
+
flow.reject { |obj| obj.is_a?(MetadataPresenter::Spacer) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ordered_pages
|
35
|
+
ordered_flow.reject(&:branch?)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :service
|
41
|
+
attr_accessor :ordered, :traversed, :routes, :coordinates
|
42
|
+
|
43
|
+
def setup_coordinates
|
44
|
+
service.flow.keys.index_with { |_uuid| { row: nil, column: nil } }
|
45
|
+
end
|
46
|
+
|
47
|
+
def route_from_start
|
48
|
+
@route_from_start ||=
|
49
|
+
MetadataPresenter::Route.new(
|
50
|
+
service: service,
|
51
|
+
traverse_from: service.start_page.uuid
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def make_grid
|
56
|
+
traverse_all_routes
|
57
|
+
|
58
|
+
rows = @routes.map(&:row).max
|
59
|
+
columns = @routes.map { |r| r.column + r.flow_uuids.count }.max
|
60
|
+
columns.times.map { rows.times.map { MetadataPresenter::Spacer.new } }
|
61
|
+
end
|
62
|
+
|
63
|
+
def traverse_all_routes
|
64
|
+
# Always traverse the route that begins from the start page first and get
|
65
|
+
# the potential routes from any branching points that exist.
|
66
|
+
route_from_start.traverse
|
67
|
+
@routes.append(route_from_start)
|
68
|
+
traversed_routes = route_from_start.routes
|
69
|
+
|
70
|
+
index = 0
|
71
|
+
until traversed_routes.empty?
|
72
|
+
if index > total_potential_routes
|
73
|
+
ActiveSupport::Notifications.instrument(
|
74
|
+
'exceeded_total_potential_routes',
|
75
|
+
message: 'Exceeded total number of potential routes'
|
76
|
+
)
|
77
|
+
break
|
78
|
+
end
|
79
|
+
|
80
|
+
route = traversed_routes.shift
|
81
|
+
@routes.append(route)
|
82
|
+
|
83
|
+
# Every route exiting a branching point needs to be traversed and any
|
84
|
+
# additional routes from other branching points collected and then also
|
85
|
+
# traversed.
|
86
|
+
route.traverse
|
87
|
+
traversed_routes |= route.routes
|
88
|
+
|
89
|
+
index += 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_columns
|
94
|
+
@routes.each do |route|
|
95
|
+
route.flow_uuids.each.with_index(route.column) do |uuid, column|
|
96
|
+
column_number = @coordinates[uuid][:column]
|
97
|
+
if column_number.nil? || column > column_number
|
98
|
+
@coordinates[uuid][:column] = column
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_rows
|
105
|
+
@routes.each do |route|
|
106
|
+
route.flow_uuids.each do |uuid|
|
107
|
+
next if @traversed.include?(uuid)
|
108
|
+
|
109
|
+
@coordinates[uuid][:row] = route.row if @coordinates[uuid][:row].nil?
|
110
|
+
|
111
|
+
update_route_rows(route, uuid)
|
112
|
+
@traversed.push(uuid)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Each Route object has a starting row. Each Route object has no knowledge
|
118
|
+
# of other potential routes and pages/branches that may or may not exist in
|
119
|
+
# them. The starting row may need to change dependent upon what has been
|
120
|
+
# traversed in other routes.
|
121
|
+
def update_route_rows(route, uuid)
|
122
|
+
flow_object = service.flow_object(uuid)
|
123
|
+
if flow_object.branch? && route.row > ROW_ZERO
|
124
|
+
destinations = routes_exiting_branch(flow_object)
|
125
|
+
destinations.each.with_index(route.row) do |destination_uuid, row|
|
126
|
+
@routes.each do |r|
|
127
|
+
r.row = row if r.traverse_from == destination_uuid
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def add_by_coordinates
|
134
|
+
@coordinates.each do |uuid, position|
|
135
|
+
# If row and column are nil then the object is detached
|
136
|
+
next if position[:row].nil? || position[:column].nil?
|
137
|
+
|
138
|
+
flow_object = service.flow_object(uuid)
|
139
|
+
@ordered[position[:column]][position[:row]] = flow_object
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Find the very last MetadataPresenter::Flow object in every column and
|
144
|
+
# remove any Spacer objects after that.
|
145
|
+
def trim_spacers
|
146
|
+
@ordered.each_with_index do |column, index|
|
147
|
+
last_index_of = column.rindex { |item| item.is_a?(MetadataPresenter::Flow) }
|
148
|
+
@ordered[index] = @ordered[index][0..last_index_of]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Each branch has a certain number of exits that require their own line
|
153
|
+
# and arrow. Insert any spacers into the necessary row in the column after
|
154
|
+
# the one the branch is located in.
|
155
|
+
def insert_expression_spacers
|
156
|
+
service.branches.each do |branch|
|
157
|
+
position = @coordinates[branch.uuid]
|
158
|
+
next if position[:row].nil? || position[:column].nil? # detached branch
|
159
|
+
|
160
|
+
next_column = position[:column] + 1
|
161
|
+
uuids = []
|
162
|
+
exiting_destinations_from_branch(branch).each.with_index(position[:row]) do |uuid, row|
|
163
|
+
if uuids.include?(uuid)
|
164
|
+
@ordered[next_column].insert(row, MetadataPresenter::Spacer.new)
|
165
|
+
end
|
166
|
+
|
167
|
+
uuids.push(uuid) unless uuids.include?(uuid)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# The frontend requires that expressions of type 'or' get there own line and
|
173
|
+
# arrow. 'and' expression types continue to be grouped together.
|
174
|
+
# Return the UUIDs of the destinations exiting a branch and allow duplicates
|
175
|
+
# if the expression type is an 'or'.
|
176
|
+
def exiting_destinations_from_branch(branch)
|
177
|
+
destination_uuids = branch.conditionals.map do |conditional|
|
178
|
+
if conditional.type == 'or'
|
179
|
+
conditional.expressions.map(&:next)
|
180
|
+
else
|
181
|
+
conditional.next
|
182
|
+
end
|
183
|
+
end
|
184
|
+
destination_uuids.flatten
|
185
|
+
end
|
186
|
+
|
187
|
+
# Any destinations exiting the branch that have not already been traversed.
|
188
|
+
def routes_exiting_branch(branch)
|
189
|
+
branch.all_destination_uuids.reject { |uuid| @traversed.include?(uuid) }
|
190
|
+
end
|
191
|
+
|
192
|
+
# Deliberately not including the default next for each branch as when row
|
193
|
+
# zero is created it takes the first available conditional for each branch.
|
194
|
+
# The remaining are then used to create route objects. Therefore the total
|
195
|
+
# number of remaining routes will be the same as the total of all the branch
|
196
|
+
# conditionals.
|
197
|
+
# Add 1 additional route as that represents the route_from_start.
|
198
|
+
def total_potential_routes
|
199
|
+
@total_potential_routes ||=
|
200
|
+
service.branches.sum { |branch| branch.conditionals.size } + 1
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module MetadataPresenter
|
2
2
|
class NextPage
|
3
3
|
include ActiveModel::Model
|
4
|
-
attr_accessor :service, :session, :user_data, :current_page_url
|
4
|
+
attr_accessor :service, :session, :user_data, :current_page_url, :previous_answers
|
5
5
|
|
6
6
|
def find
|
7
7
|
return check_answers_page if return_to_check_your_answer?
|
@@ -11,17 +11,53 @@ module MetadataPresenter
|
|
11
11
|
else
|
12
12
|
service.find_page_by_uuid(current_page_flow.default_next)
|
13
13
|
end
|
14
|
+
ensure
|
15
|
+
session[:return_to_check_your_answer] = nil
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
19
|
|
18
20
|
def check_answers_page
|
19
|
-
session[:return_to_check_your_answer] = nil
|
20
21
|
service.pages.find { |page| page.type == 'page.checkanswers' }
|
21
22
|
end
|
22
23
|
|
23
24
|
def return_to_check_your_answer?
|
24
|
-
session[:return_to_check_your_answer].present?
|
25
|
+
session[:return_to_check_your_answer].present? &&
|
26
|
+
components_not_used_for_branching_and_answers_unchanged?
|
27
|
+
end
|
28
|
+
|
29
|
+
def components_not_used_for_branching_and_answers_unchanged?
|
30
|
+
components_not_used_for_branching? && answers_unchanged?
|
31
|
+
end
|
32
|
+
|
33
|
+
def components_not_used_for_branching?
|
34
|
+
expressions.none? { |expression| component_ids.include?(expression) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def answers_unchanged?
|
38
|
+
components = current_page_components.select do |component|
|
39
|
+
component.uuid.in?(expressions.map(&:component))
|
40
|
+
end
|
41
|
+
|
42
|
+
components.all? do |component|
|
43
|
+
user_data[component.id] == previous_answers[component.id]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def component_ids
|
48
|
+
current_page_components.map(&:id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_page_components
|
52
|
+
current_page.components
|
53
|
+
end
|
54
|
+
|
55
|
+
def expressions
|
56
|
+
collection = service.branches.map do |branch|
|
57
|
+
branch.conditionals.map(&:expressions)
|
58
|
+
end
|
59
|
+
|
60
|
+
collection.flatten
|
25
61
|
end
|
26
62
|
|
27
63
|
def conditionals?
|
@@ -13,6 +13,12 @@ module MetadataPresenter
|
|
13
13
|
add_extra_component
|
14
14
|
].freeze
|
15
15
|
QUESTION_PAGES = %w[page.singlequestion page.multiplequestions].freeze
|
16
|
+
USES_HEADING = %w[
|
17
|
+
page.content
|
18
|
+
page.checkanswers
|
19
|
+
page.confirmation
|
20
|
+
page.multiplequestions
|
21
|
+
].freeze
|
16
22
|
|
17
23
|
def editable_attributes
|
18
24
|
to_h.reject { |k, _| k.in?(NOT_EDITABLE) }
|
@@ -87,8 +93,7 @@ module MetadataPresenter
|
|
87
93
|
private
|
88
94
|
|
89
95
|
def heading?
|
90
|
-
Array(components).size != 1
|
91
|
-
type.in?(['page.content', 'page.checkanswers', 'page.confirmation'])
|
96
|
+
type.in?(USES_HEADING) || Array(components).size != 1
|
92
97
|
end
|
93
98
|
|
94
99
|
def to_components(node_components, collection:)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module MetadataPresenter
|
2
|
+
class Route
|
3
|
+
attr_reader :traverse_from
|
4
|
+
attr_accessor :flow_uuids, :routes, :row, :column
|
5
|
+
|
6
|
+
def initialize(service:, traverse_from:, row: 0, column: 0)
|
7
|
+
@service = service
|
8
|
+
@traverse_from = traverse_from
|
9
|
+
@row = row
|
10
|
+
@column = column
|
11
|
+
@routes = []
|
12
|
+
@flow_uuids = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def traverse
|
16
|
+
@flow_uuid = traverse_from
|
17
|
+
|
18
|
+
index = column
|
19
|
+
until @flow_uuid.blank?
|
20
|
+
if index > service.flow.size
|
21
|
+
ActiveSupport::Notifications.instrument(
|
22
|
+
'exceeded_total_flow_objects',
|
23
|
+
message: 'Exceeded total number of flow objects'
|
24
|
+
)
|
25
|
+
break
|
26
|
+
end
|
27
|
+
|
28
|
+
@flow_uuids.push(@flow_uuid) unless @flow_uuids.include?(@flow_uuid)
|
29
|
+
flow_object = service.flow_object(@flow_uuid)
|
30
|
+
|
31
|
+
if flow_object.branch?
|
32
|
+
destinations = destination_uuids(flow_object)
|
33
|
+
# Take the first conditional destination and follow that until the end
|
34
|
+
# of the route.
|
35
|
+
@flow_uuid = destinations.shift
|
36
|
+
|
37
|
+
# The remaining conditional destinations and the branch's default next
|
38
|
+
# (otherwise) will be the starting point for a new Route object to be
|
39
|
+
# traversed.
|
40
|
+
# The default behaviour is that the next destination will be on the row
|
41
|
+
# below this current route's row. This can be changed under certain
|
42
|
+
# conditions in the Grid model.
|
43
|
+
# Each of the destinations need to be placed in the column after the
|
44
|
+
# current column.
|
45
|
+
row_number = row + 1
|
46
|
+
column_number = index + 1
|
47
|
+
destinations.each do |uuid|
|
48
|
+
@routes.push(
|
49
|
+
MetadataPresenter::Route.new(
|
50
|
+
service: service,
|
51
|
+
traverse_from: uuid,
|
52
|
+
row: row_number,
|
53
|
+
column: column_number
|
54
|
+
)
|
55
|
+
)
|
56
|
+
row_number += 1
|
57
|
+
end
|
58
|
+
else
|
59
|
+
@flow_uuid = flow_object.default_next
|
60
|
+
end
|
61
|
+
|
62
|
+
index += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
@flow_uuids
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :service
|
71
|
+
|
72
|
+
def destination_uuids(flow_object)
|
73
|
+
flow_object.conditionals.map(&:next).push(flow_object.default_next)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|