metadata_presenter 2.4.0 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e32a28102541813aa8d6576cafdaa6fb3b4897daee023f0e52d80b1c98c3266f
4
- data.tar.gz: 2001211cd3395cff6b2dc4d4f806f7055267c0a448d4316a5da6998d1ebe5295
3
+ metadata.gz: d4711e7f1519315b8e0e2bbe067598fedf285ac167b16f47ed48aa73e60710ff
4
+ data.tar.gz: 5ac9f6c8c6ae1831ca3e0fa985fc54c8151cd7ae39d6590ff9e548169c3f1125
5
5
  SHA512:
6
- metadata.gz: 40bbe63a448710146762e167f0abc415e39028d1e279bdf72aa989376abbae4881d56d2cfbcc065805541f6a340a39349a7d5ccb3efd9b6444b803ea5d21ebfc
7
- data.tar.gz: 48902563f8e43de68352ce31c47d554493a4add994649c54d1933062a7cec78c7334c20debf05fea827f84dd825bf16bb1c1b34fa033733e877d964c94f3c2a2
6
+ metadata.gz: 4dc54e3ecbb88210ce1d14211f89ebb063a31413dc733a5376e59b685f8a07dee9db1758c9933fa310cd94d5c953bed6f0aea8e0518bccb0fc75a67f65b422ab
7
+ data.tar.gz: 2824f22e9c5e15d63b59abfbf28c3679115b1430c646115c0c9e336dfba2b1f575e0a538e3e79058d4ae4ff5d917d50ba596bc3913acd549b65c56eb13f9f328
@@ -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
+ 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
@@ -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