metadata_presenter 2.4.2 → 2.6.1
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/models/metadata_presenter/grid.rb +203 -0
- data/app/models/metadata_presenter/page.rb +1 -0
- data/app/models/metadata_presenter/route.rb +76 -0
- data/app/views/metadata_presenter/page/exit.html.erb +29 -0
- data/config/initializers/supported_components.rb +4 -0
- data/default_metadata/page/exit.json +9 -0
- data/fixtures/branching_7.json +11 -19
- data/fixtures/branching_8.json +2 -2
- data/lib/metadata_presenter/version.rb +1 -1
- data/lib/metadata_presenter.rb +1 -0
- data/schemas/page/exit.json +25 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f8ec576a9274943e0bc1bd36901b909e408073718c64e6310ac31035227aa69
|
4
|
+
data.tar.gz: 42be1d66e898e46e79f9292034da70e12d3e2abdd62d98787dc12c9391b34205
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54081e0c1702f9c69fd136714bf24c455a089b1b37675721aecd3ebe7c5c548ca5973c91a1d6ef8c5d8a7987ef5fa22d448cf61f4ed72fe8cd3f7b78cacf8241
|
7
|
+
data.tar.gz: 5baa5506512d0d40ead58378c7ed288400f3a210304ecc89943d26e72b046dc4d1181913d7029936f41b14942a2e95f46fa49538d250b7f8d3977958278bafa2
|
@@ -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
|
+
return @ordered unless @ordered.empty?
|
18
|
+
|
19
|
+
@ordered = make_grid
|
20
|
+
add_columns
|
21
|
+
add_rows
|
22
|
+
add_by_coordinates
|
23
|
+
insert_expression_spacers
|
24
|
+
trim_spacers
|
25
|
+
|
26
|
+
@ordered
|
27
|
+
end
|
28
|
+
|
29
|
+
def ordered_flow
|
30
|
+
@ordered_flow ||=
|
31
|
+
build.flatten.reject { |obj| obj.is_a?(MetadataPresenter::Spacer) }
|
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
|
@@ -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
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<div class="fb-main-grid-wrapper" data-fb-pagetype="<%= @page.type %>">
|
2
|
+
<div class="govuk-grid-row">
|
3
|
+
<div class="govuk-grid-column-two-thirds">
|
4
|
+
|
5
|
+
<%= render 'metadata_presenter/attribute/section_heading' %>
|
6
|
+
|
7
|
+
<h1 class="fb-editable govuk-heading-xl"
|
8
|
+
data-fb-content-id="page[heading]"
|
9
|
+
data-fb-content-type="element">
|
10
|
+
<%= @page.heading %>
|
11
|
+
</h1>
|
12
|
+
|
13
|
+
<%= render 'metadata_presenter/attribute/lede' %>
|
14
|
+
|
15
|
+
<%= form_for @page_answers, as: :answers, url: @page.url, method: :post do |f| %>
|
16
|
+
|
17
|
+
<%= render partial: 'metadata_presenter/component/components',
|
18
|
+
locals: {
|
19
|
+
f: f,
|
20
|
+
components: @page.components,
|
21
|
+
tag: nil,
|
22
|
+
classes: nil,
|
23
|
+
input_components: @page.supported_input_components,
|
24
|
+
content_components: @page.supported_content_components
|
25
|
+
} %>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
@@ -12,6 +12,10 @@ Rails.application.config.supported_components =
|
|
12
12
|
input: %w(),
|
13
13
|
content: %w(content)
|
14
14
|
},
|
15
|
+
exit: {
|
16
|
+
input: %w(),
|
17
|
+
content: %w(content)
|
18
|
+
},
|
15
19
|
multiplequestions: {
|
16
20
|
input: %w(text textarea number date radios checkboxes),
|
17
21
|
content: %w(content)
|
data/fixtures/branching_7.json
CHANGED
@@ -148,7 +148,7 @@
|
|
148
148
|
"body": "Start page body",
|
149
149
|
"_type": "page.start",
|
150
150
|
"_uuid": "cf6dc32f-502c-4215-8c27-1151a45735bb",
|
151
|
-
"heading": "Branching Fixture
|
151
|
+
"heading": "Branching Fixture 7",
|
152
152
|
"before_you_start": "Start page before you start"
|
153
153
|
},
|
154
154
|
{
|
@@ -329,28 +329,20 @@
|
|
329
329
|
{
|
330
330
|
"_id": "page.g",
|
331
331
|
"url": "page-g",
|
332
|
-
"
|
333
|
-
"
|
334
|
-
"_type": "page.singlequestion",
|
332
|
+
"lede": "lede",
|
333
|
+
"_type": "page.exit",
|
335
334
|
"_uuid": "3a584d15-6805-4a21-bc05-b61c3be47857",
|
336
335
|
"heading": "Page G",
|
337
336
|
"components": [
|
338
337
|
{
|
339
|
-
"_id": "
|
340
|
-
"
|
341
|
-
"
|
342
|
-
"
|
343
|
-
"
|
344
|
-
"label": "Page G",
|
345
|
-
"errors": {},
|
346
|
-
"collection": "components",
|
347
|
-
"validation": {
|
348
|
-
"required": true,
|
349
|
-
"max_length": 20,
|
350
|
-
"min_length": 2
|
351
|
-
}
|
338
|
+
"_id": "exit_content_1",
|
339
|
+
"name": "exit_content_1",
|
340
|
+
"_type": "content",
|
341
|
+
"_uuid": "9774d986-135a-4b7f-924d-7527b790e1a0",
|
342
|
+
"content": "This is an exit page"
|
352
343
|
}
|
353
|
-
]
|
344
|
+
],
|
345
|
+
"section_heading": "Section heading"
|
354
346
|
},
|
355
347
|
{
|
356
348
|
"_id": "page.h",
|
@@ -559,7 +551,7 @@
|
|
559
551
|
"created_by": "099d5bf5-5f7b-444c-86ee-9e189cc1a369",
|
560
552
|
"service_id": "ea3bfb09-fc6d-4464-b025-9d6aadafc593",
|
561
553
|
"version_id": "429a8039-75b3-42b8-84dc-230fb371c71d",
|
562
|
-
"service_name": "Branching Fixture
|
554
|
+
"service_name": "Branching Fixture 7",
|
563
555
|
"configuration": {
|
564
556
|
"meta": {
|
565
557
|
"_id": "config.meta",
|
data/fixtures/branching_8.json
CHANGED
@@ -142,7 +142,7 @@
|
|
142
142
|
"body": "Start page body",
|
143
143
|
"_type": "page.start",
|
144
144
|
"_uuid": "cf6dc32f-502c-4215-8c27-1151a45735bb",
|
145
|
-
"heading": "Branching Fixture
|
145
|
+
"heading": "Branching Fixture 8",
|
146
146
|
"before_you_start": "Start page before you start"
|
147
147
|
},
|
148
148
|
{
|
@@ -497,7 +497,7 @@
|
|
497
497
|
"created_by": "099d5bf5-5f7b-444c-86ee-9e189cc1a369",
|
498
498
|
"service_id": "ea3bfb09-fc6d-4464-b025-9d6aadafc593",
|
499
499
|
"version_id": "429a8039-75b3-42b8-84dc-230fb371c71d",
|
500
|
-
"service_name": "Branching Fixture
|
500
|
+
"service_name": "Branching Fixture 8",
|
501
501
|
"configuration": {
|
502
502
|
"meta": {
|
503
503
|
"_id": "config.meta",
|
data/lib/metadata_presenter.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"$id": "http://gov.uk/schema/v1.0.0/page/exit",
|
3
|
+
"_name": "page.exit",
|
4
|
+
"title": "Exit",
|
5
|
+
"description": "Display an exit page to users when they cannot proceed",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"_type": {
|
9
|
+
"const": "page.exit"
|
10
|
+
},
|
11
|
+
"components": {
|
12
|
+
"title": "Content",
|
13
|
+
"description": "The form or content elements used on the page",
|
14
|
+
"type": "array",
|
15
|
+
"items": {
|
16
|
+
"$ref": "component.content"
|
17
|
+
}
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"allOf": [
|
21
|
+
{
|
22
|
+
"$ref": "definition.page.content"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metadata_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MoJ Forms
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: govuk_design_system_formbuilder
|
@@ -283,6 +283,7 @@ files:
|
|
283
283
|
- app/models/metadata_presenter/expression.rb
|
284
284
|
- app/models/metadata_presenter/file_uploader.rb
|
285
285
|
- app/models/metadata_presenter/flow.rb
|
286
|
+
- app/models/metadata_presenter/grid.rb
|
286
287
|
- app/models/metadata_presenter/item.rb
|
287
288
|
- app/models/metadata_presenter/meta.rb
|
288
289
|
- app/models/metadata_presenter/meta_item.rb
|
@@ -292,6 +293,7 @@ files:
|
|
292
293
|
- app/models/metadata_presenter/page.rb
|
293
294
|
- app/models/metadata_presenter/page_answers.rb
|
294
295
|
- app/models/metadata_presenter/previous_page.rb
|
296
|
+
- app/models/metadata_presenter/route.rb
|
295
297
|
- app/models/metadata_presenter/service.rb
|
296
298
|
- app/models/metadata_presenter/traversed_pages.rb
|
297
299
|
- app/models/metadata_presenter/uploaded_file.rb
|
@@ -335,6 +337,7 @@ files:
|
|
335
337
|
- app/views/metadata_presenter/page/checkanswers.html.erb
|
336
338
|
- app/views/metadata_presenter/page/confirmation.html.erb
|
337
339
|
- app/views/metadata_presenter/page/content.html.erb
|
340
|
+
- app/views/metadata_presenter/page/exit.html.erb
|
338
341
|
- app/views/metadata_presenter/page/form.html.erb
|
339
342
|
- app/views/metadata_presenter/page/multiplequestions.html.erb
|
340
343
|
- app/views/metadata_presenter/page/singlequestion.html.erb
|
@@ -365,6 +368,7 @@ files:
|
|
365
368
|
- default_metadata/page/checkanswers.json
|
366
369
|
- default_metadata/page/confirmation.json
|
367
370
|
- default_metadata/page/content.json
|
371
|
+
- default_metadata/page/exit.json
|
368
372
|
- default_metadata/page/multiplequestions.json
|
369
373
|
- default_metadata/page/singlequestion.json
|
370
374
|
- default_metadata/page/standalone.json
|
@@ -458,6 +462,7 @@ files:
|
|
458
462
|
- schemas/page/checkanswers.json
|
459
463
|
- schemas/page/confirmation.json
|
460
464
|
- schemas/page/content.json
|
465
|
+
- schemas/page/exit.json
|
461
466
|
- schemas/page/multiplequestions.json
|
462
467
|
- schemas/page/singlequestion.json
|
463
468
|
- schemas/page/standalone.json
|