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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fde1f4b73263207626fb0693406115b7bf5d6c0e5a8f59624260cb25a3e38158
4
- data.tar.gz: 3487fe9c01196f349cc4a84020cfb43246ecce2e123b01a65c727ce296967a53
3
+ metadata.gz: 5bd3891b62938a67dd025d49a96098778ad66221bf62cb2351002906e78aa4b4
4
+ data.tar.gz: 9dba99d36da50beea7a209d27f3371e4e814776d86d609436ac500c6d91cbd14
5
5
  SHA512:
6
- metadata.gz: 6d1e5ec205720fa0a197885e5e5a989891a4566ea9c2a1eddae179352c10a2f6060805306c403a50d007da34a80e556dc73b7b9f1c40b63363d2fd4f65bc96e1
7
- data.tar.gz: 36362beca9e5110383893160eefaafe612dada9a429acc8db94749113c3f921a84ff3581d1074b16f43d0f9bb8cdc91463b20591a8389cd28032415668d23a8a
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: load_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