metadata_presenter 1.4.0 → 1.7.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: 50e72c6d7db805dc3c408e5be910e7e8622984d6dafe879bdd488857f9ae4d44
4
- data.tar.gz: 61a5d6f5430a9cf8ae6b5a0d9c6ae03f2869c59e96e70919541e031944d7b99f
3
+ metadata.gz: 2e8dfc341688f7dccd859058844f7b475ecc93afb86ab46d1a4d72f89c8f8235
4
+ data.tar.gz: cfee59590ce4834a7c8eda109cd914f1b0b0157c2d48dc97e44351b552b943fe
5
5
  SHA512:
6
- metadata.gz: 83993ca5083a0c54693f7c46d14561c897c881fe9461c5ccef1881726d9e46f9f5ca69abb8faf8d619fa5ca9fcef46dee8b096b1dda30468a86a8c9a876e1350
7
- data.tar.gz: ac17522430316217e5bf6c430b326dae237d60f759d6e25125fa701a265e97082238492b47cb0948fe703da6ed35fc5e5da1f54a11bbc2abd17d535001172258
6
+ metadata.gz: 74d388b6e903201e9d42a71ae17f7457a748b4c7791a5604b8f9ac387b56f3a5f20953a55c84ce0a60efa53ea8ac9580d291001648fa781c0b01c5c56dd43dc1
7
+ data.tar.gz: b4ac8ca7c331b9a4a93acec7ad58036f42d7f73f5098b247cae77b054352bf588424832e615ebad4e76ee8ec4069723cd9edfdaee65a2ef7ae4f47aaca5369b0
data/README.md CHANGED
@@ -83,3 +83,15 @@ service.
83
83
  ## Generate documentation
84
84
 
85
85
  Run `rake doc` and open the doc/index.html
86
+
87
+ ## Flow diagrams
88
+
89
+ You can generate flow diagrams calling a rake task:
90
+
91
+ ```
92
+ brew install graphviz
93
+ SERVICE_METADATA="some-form-metadata" rails metadata:flow
94
+ ```
95
+
96
+ This will generate an image with the flow for that metadata. Open that image
97
+ and profit!
@@ -28,6 +28,7 @@ module MetadataPresenter
28
28
  next_page = NextPage.new(
29
29
  service: service,
30
30
  session: session,
31
+ user_data: load_user_data,
31
32
  current_page_url: page_url
32
33
  ).find
33
34
 
@@ -1,7 +1,7 @@
1
1
  module MetadataPresenter
2
2
  class ChangeAnswerController < EngineController
3
3
  def create
4
- session[:return_to_check_you_answer] = true
4
+ session[:return_to_check_your_answer] = true
5
5
  redirect_to_page params[:url]
6
6
  end
7
7
  end
@@ -6,14 +6,19 @@ module MetadataPresenter
6
6
  default_form_builder GOVUKDesignSystemFormBuilder::FormBuilder
7
7
 
8
8
  def back_link
9
- return if @page.blank?
10
-
11
- previous_page = service.previous_page(
9
+ previous_page = PreviousPage.new(
10
+ service: service,
11
+ user_data: load_user_data,
12
12
  current_page: @page,
13
- referrer: request.referer
14
- )&.url
15
-
16
- @back_link ||= File.join(request.script_name, previous_page) if previous_page
13
+ referrer: request.referrer
14
+ ).page
15
+
16
+ if previous_page
17
+ @back_link ||= File.join(
18
+ request.script_name,
19
+ previous_page.url
20
+ )
21
+ end
17
22
  end
18
23
  helper_method :back_link
19
24
 
@@ -4,24 +4,32 @@ module MetadataPresenter
4
4
  attr_accessor :service, :flow, :user_data
5
5
 
6
6
  def page
7
- results = conditions.map do |condition|
8
- condition.criterias.map do |criteria|
7
+ evaluated_page_uuid = page_uuid || flow.default_next
8
+
9
+ service.find_page_by_uuid(evaluated_page_uuid)
10
+ end
11
+
12
+ def page_uuid
13
+ @results ||= conditions.map do |condition|
14
+ evaluated_criterias = condition.criterias.map do |criteria|
9
15
  criteria.service = service
10
16
 
11
- next unless Operator.new(
17
+ Operator.new(
12
18
  criteria.operator
13
- ).evaluate(criteria.field_label, user_data[criteria.criteria_component.id])
19
+ ).evaluate(
20
+ criteria.field_label,
21
+ user_data[criteria.criteria_component.id]
22
+ )
23
+ end
14
24
 
25
+ if condition.condition_type == 'or' && evaluated_criterias.any?
26
+ condition.next
27
+ elsif evaluated_criterias.all?
15
28
  condition.next
16
29
  end
17
30
  end
18
31
 
19
- page_uuid = results.flatten.uniq.compact
20
- if page_uuid.present?
21
- service.find_page_by_uuid(page_uuid.first)
22
- else
23
- service.find_page_by_uuid(flow.default_next)
24
- end
32
+ @results.flatten.compact.first
25
33
  end
26
34
 
27
35
  delegate :conditions, to: :flow
@@ -13,5 +13,9 @@ module MetadataPresenter
13
13
  Condition.new(condition_metadata)
14
14
  end
15
15
  end
16
+
17
+ def group_by_page
18
+ conditions.group_by(&:next)
19
+ end
16
20
  end
17
21
  end
@@ -1,10 +1,10 @@
1
1
  module MetadataPresenter
2
2
  class NextPage
3
3
  include ActiveModel::Model
4
- attr_accessor :service, :session, :current_page_url
4
+ attr_accessor :service, :session, :user_data, :current_page_url
5
5
 
6
6
  def find
7
- return check_answers_page if return_to_check_you_answer?
7
+ return check_answers_page if return_to_check_your_answer?
8
8
 
9
9
  if conditions?
10
10
  evaluate_conditions
@@ -18,12 +18,12 @@ module MetadataPresenter
18
18
  private
19
19
 
20
20
  def check_answers_page
21
- session[:return_to_check_you_answer] = nil
21
+ session[:return_to_check_your_answer] = nil
22
22
  service.pages.find { |page| page.type == 'page.checkanswers' }
23
23
  end
24
24
 
25
- def return_to_check_you_answer?
26
- session[:return_to_check_you_answer].present?
25
+ def return_to_check_your_answer?
26
+ session[:return_to_check_your_answer].present?
27
27
  end
28
28
 
29
29
  def conditions?
@@ -36,7 +36,7 @@ module MetadataPresenter
36
36
  EvaluateConditions.new(
37
37
  service: service,
38
38
  flow: next_flow,
39
- user_data: session[:user_data]
39
+ user_data: user_data
40
40
  ).page
41
41
  end
42
42
 
@@ -62,6 +62,10 @@ module MetadataPresenter
62
62
  components.select(&:upload?)
63
63
  end
64
64
 
65
+ def standalone?
66
+ type == 'page.standalone'
67
+ end
68
+
65
69
  private
66
70
 
67
71
  def to_components(node_components, collection:)
@@ -2,6 +2,7 @@ module MetadataPresenter
2
2
  class PageAnswers
3
3
  include ActiveModel::Model
4
4
  include ActiveModel::Validations
5
+ include ActionView::Helpers
5
6
  attr_reader :page, :answers, :uploaded_files
6
7
 
7
8
  def initialize(page, answers)
@@ -27,8 +28,10 @@ module MetadataPresenter
27
28
  date_answer(component.id)
28
29
  elsif component && component.type == 'upload'
29
30
  upload_answer(component.id)
31
+ elsif component && component.type == 'checkboxes'
32
+ answers[method_name.to_s].to_a
30
33
  else
31
- answers[method_name.to_s]
34
+ sanitize(answers[method_name.to_s])
32
35
  end
33
36
  end
34
37
 
@@ -38,10 +41,10 @@ module MetadataPresenter
38
41
  return {} unless file_details
39
42
 
40
43
  if file_details.is_a?(Hash) || file_details.is_a?(ActionController::Parameters)
41
- file_details
44
+ file_details.merge('original_filename' => sanitize(file_details['original_filename']))
42
45
  else
43
46
  {
44
- 'original_filename' => file_details.original_filename,
47
+ 'original_filename' => sanitize(file_details.original_filename),
45
48
  'content_type' => file_details.content_type,
46
49
  'tempfile' => file_details.tempfile.path.to_s
47
50
  }
@@ -60,7 +63,7 @@ module MetadataPresenter
60
63
  GOVUKDesignSystemFormBuilder::Elements::Date::SEGMENTS[:month],
61
64
  GOVUKDesignSystemFormBuilder::Elements::Date::SEGMENTS[:year]
62
65
  ].map do |segment|
63
- answers["#{component_id}(#{segment})"]
66
+ sanitize(answers["#{component_id}(#{segment})"])
64
67
  end
65
68
  end
66
69
  end
@@ -0,0 +1,38 @@
1
+ module MetadataPresenter
2
+ class PreviousPage
3
+ include ActiveModel::Model
4
+ attr_accessor :service, :user_data, :current_page, :referrer
5
+
6
+ def page
7
+ # what happens when a user enters in the middle of the flow
8
+ return if no_current_or_referrer_pages? || service.no_back_link?(current_page)
9
+
10
+ if flow.present?
11
+ return referrer_page if return_to_referrer?
12
+
13
+ TraversedPages.new(service, user_data, current_page).last
14
+ else
15
+ service.previous_page(current_page: current_page, referrer: referrer)
16
+ end
17
+ end
18
+
19
+ def flow
20
+ service.metadata['flow']
21
+ end
22
+
23
+ private
24
+
25
+ def referrer_page
26
+ @referrer_page ||= service.find_page_by_url(URI(referrer).path)
27
+ end
28
+
29
+ def return_to_referrer?
30
+ current_page.standalone? ||
31
+ (referrer_page && referrer_page.standalone?)
32
+ end
33
+
34
+ def no_current_or_referrer_pages?
35
+ current_page.blank? || referrer.nil?
36
+ end
37
+ end
38
+ end
@@ -39,6 +39,8 @@ class MetadataPresenter::Service < MetadataPresenter::Metadata
39
39
  end
40
40
 
41
41
  def previous_page(current_page:, referrer:)
42
+ return if current_page.nil? || referrer.nil?
43
+
42
44
  unless no_back_link?(current_page)
43
45
  flow_page(current_page) || referrer_page(referrer)
44
46
  end
@@ -54,16 +56,16 @@ class MetadataPresenter::Service < MetadataPresenter::Metadata
54
56
  MetadataPresenter::Meta.new(configuration['meta'])
55
57
  end
56
58
 
59
+ def no_back_link?(current_page)
60
+ current_page == start_page || current_page == confirmation_page
61
+ end
62
+
57
63
  private
58
64
 
59
65
  def all_pages
60
66
  @all_pages ||= pages + standalone_pages
61
67
  end
62
68
 
63
- def no_back_link?(current_page)
64
- current_page == start_page || current_page == confirmation_page
65
- end
66
-
67
69
  def flow_page(current_page)
68
70
  page_index = pages.index(current_page)
69
71
  pages[page_index - 1] if page_index.present?
@@ -0,0 +1,40 @@
1
+ module MetadataPresenter
2
+ class TraversedPages
3
+ attr_reader :service, :user_data, :current_page
4
+
5
+ def initialize(service, user_data, current_page)
6
+ @service = service
7
+ @user_data = user_data
8
+ @pages = [service.start_page]
9
+ @current_page = current_page
10
+ end
11
+
12
+ delegate :last, to: :all
13
+
14
+ def all
15
+ page_uuid = service.start_page.uuid
16
+
17
+ service.metadata['flow'].size.times do
18
+ break if page_uuid == current_page.uuid
19
+
20
+ flow_object = service.flow(page_uuid)
21
+
22
+ if flow_object.branch?
23
+ page = EvaluateConditions.new(
24
+ service: service,
25
+ flow: flow_object,
26
+ user_data: user_data
27
+ ).page
28
+ page_uuid = page.uuid
29
+ else
30
+ page_uuid = flow_object.default_next
31
+ page = service.find_page_by_uuid(page_uuid)
32
+ end
33
+
34
+ @pages.push(page) if page && page.uuid != current_page.uuid
35
+ end
36
+
37
+ @pages
38
+ end
39
+ end
40
+ end
@@ -6,5 +6,15 @@ module MetadataPresenter
6
6
  @actual = actual
7
7
  @expected = expected
8
8
  end
9
+
10
+ def evaluate?
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # Method signature for collection components (a.k.a checkboxes)
15
+ #
16
+ def evaluate_collection?
17
+ evaluate?
18
+ end
9
19
  end
10
20
  end
@@ -3,5 +3,9 @@ module MetadataPresenter
3
3
  def evaluate?
4
4
  @actual != @expected
5
5
  end
6
+
7
+ def evaluate_collection?
8
+ Array(@expected).exclude?(@actual)
9
+ end
6
10
  end
7
11
  end
@@ -3,5 +3,9 @@ module MetadataPresenter
3
3
  def evaluate?
4
4
  @actual == @expected
5
5
  end
6
+
7
+ def evaluate_collection?
8
+ Array(@expected).include?(@actual)
9
+ end
6
10
  end
7
11
  end
@@ -10,10 +10,13 @@ module MetadataPresenter
10
10
  end
11
11
 
12
12
  def evaluate(actual, expected)
13
- klass
14
- .constantize
15
- .new(actual, expected)
16
- .evaluate?
13
+ operator = klass.constantize.new(actual, expected)
14
+
15
+ if expected.is_a?(Array)
16
+ operator.evaluate_collection?
17
+ else
18
+ operator.evaluate?
19
+ end
17
20
  rescue NameError
18
21
  raise NoOperator,
19
22
  "Operator '#{operator}' is not implemented. You need to create the class #{klass}"
@@ -137,7 +137,7 @@
137
137
  "cf8b3e18-dacf-4e91-92e1-018035961003": {
138
138
  "_type": "branch",
139
139
  "next": {
140
- "default": "e337070b-f636-49a3-a65c-f506675265f0",
140
+ "default": "0c022e95-0748-4dda-8ba5-12fd1d2f596b",
141
141
  "conditions": [
142
142
  {
143
143
  "condition_type": "if",
@@ -154,6 +154,124 @@
154
154
  ]
155
155
  }
156
156
  },
157
+ "b5efc09c-ece7-45ae-b0b3-8a7905e25040": {
158
+ "_type": "page",
159
+ "next": {
160
+ "default": "0c022e95-0748-4dda-8ba5-12fd1d2f596b"
161
+ }
162
+ },
163
+ "0c022e95-0748-4dda-8ba5-12fd1d2f596b": {
164
+ "_type": "page",
165
+ "next": {
166
+ "default": "618b7537-b42b-4551-ae7d-053afa4d9ca9"
167
+ }
168
+ },
169
+ "618b7537-b42b-4551-ae7d-053afa4d9ca9": {
170
+ "_type": "branch",
171
+ "next": {
172
+ "default": "dc7454f9-4186-48d7-b055-684d57bbcdc7",
173
+ "conditions": [
174
+ {
175
+ "condition_type": "if",
176
+ "next": "bc666714-c0a2-4674-afe5-faff2e20d847",
177
+ "criterias": [
178
+ {
179
+ "operator": "is",
180
+ "page": "0c022e95-0748-4dda-8ba5-12fd1d2f596b",
181
+ "component": "4c409737-80bb-48c7-afa7-6e9360b65004",
182
+ "field": "3251098f-c8e0-4ac5-a637-95fd2e1c4dc4"
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "condition_type": "if",
188
+ "next": "e2887f44-5e8d-4dc0-b1de-496ab6039430",
189
+ "criterias": [
190
+ {
191
+ "operator": "is_not",
192
+ "page": "0c022e95-0748-4dda-8ba5-12fd1d2f596b",
193
+ "component": "4c409737-80bb-48c7-afa7-6e9360b65004",
194
+ "field": "3251098f-c8e0-4ac5-a637-95fd2e1c4dc4"
195
+ }
196
+ ]
197
+ }
198
+ ]
199
+ }
200
+ },
201
+ "bc666714-c0a2-4674-afe5-faff2e20d847": {
202
+ "_type": "page",
203
+ "next": {
204
+ "default": "dc7454f9-4186-48d7-b055-684d57bbcdc7"
205
+ }
206
+ },
207
+ "e2887f44-5e8d-4dc0-b1de-496ab6039430": {
208
+ "_type": "page",
209
+ "next": {
210
+ "default": "dc7454f9-4186-48d7-b055-684d57bbcdc7"
211
+ }
212
+ },
213
+ "dc7454f9-4186-48d7-b055-684d57bbcdc7": {
214
+ "_type": "page",
215
+ "next": {
216
+ "default": "84a347fc-8d4b-486a-9996-6a86fa9544c5"
217
+ }
218
+ },
219
+ "84a347fc-8d4b-486a-9996-6a86fa9544c5": {
220
+ "_type": "branch",
221
+ "next": {
222
+ "default": "e337070b-f636-49a3-a65c-f506675265f0",
223
+ "conditions": [
224
+ {
225
+ "condition_type": "or",
226
+ "next": "2cc66e51-2c14-4023-86bf-ded49887cdb2",
227
+ "criterias": [
228
+ {
229
+ "operator": "is",
230
+ "page": "dc7454f9-4186-48d7-b055-684d57bbcdc7",
231
+ "component": "5ca01e56-94de-4c33-b5fc-74697e5b95cc",
232
+ "field": "bd14bf31-427e-4267-9926-a18c167fe604"
233
+ },
234
+ {
235
+ "operator": "is",
236
+ "page": "dc7454f9-4186-48d7-b055-684d57bbcdc7",
237
+ "component": "5ca01e56-94de-4c33-b5fc-74697e5b95cc",
238
+ "field": "eb720ef7-bcaf-4c73-8972-6fc80dca6245"
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ "condition_type": "and",
244
+ "next": "f6c51f88-7be8-4cb7-bbfc-6c905727a051",
245
+ "criterias": [
246
+ {
247
+ "operator": "is",
248
+ "page": "68fbb180-9a2a-48f6-9da6-545e28b8d35a",
249
+ "component": "ac41be35-914e-4b22-8683-f5477716b7d4",
250
+ "field": "c5571937-9388-4411-b5fa-34ddf9bc4ca0"
251
+ },
252
+ {
253
+ "operator": "is",
254
+ "page": "dc7454f9-4186-48d7-b055-684d57bbcdc7",
255
+ "component": "5ca01e56-94de-4c33-b5fc-74697e5b95cc",
256
+ "field": "0c4192eb-1441-4f10-b00a-0f03b756269e"
257
+ }
258
+ ]
259
+ }
260
+ ]
261
+ }
262
+ },
263
+ "2cc66e51-2c14-4023-86bf-ded49887cdb2": {
264
+ "_type": "page",
265
+ "next": {
266
+ "default": "e337070b-f636-49a3-a65c-f506675265f0"
267
+ }
268
+ },
269
+ "f6c51f88-7be8-4cb7-bbfc-6c905727a051": {
270
+ "_type": "page",
271
+ "next": {
272
+ "default": "e337070b-f636-49a3-a65c-f506675265f0"
273
+ }
274
+ },
157
275
  "e337070b-f636-49a3-a65c-f506675265f0": {
158
276
  "_type": "page",
159
277
  "next": {
@@ -608,6 +726,217 @@
608
726
  }
609
727
  ]
610
728
  },
729
+ {
730
+ "_id": "page.burgers",
731
+ "url": "burgers",
732
+ "body": "Body section",
733
+ "lede": "",
734
+ "_type": "page.singlequestion",
735
+ "_uuid": "0c022e95-0748-4dda-8ba5-12fd1d2f596b",
736
+ "heading": "Question",
737
+ "components": [
738
+ {
739
+ "_id": "burgers_checkboxes_1",
740
+ "hint": "",
741
+ "name": "burgers_checkboxes_1",
742
+ "_type": "checkboxes",
743
+ "_uuid": "4c409737-80bb-48c7-afa7-6e9360b65004",
744
+ "items": [
745
+ {
746
+ "_id": "burgers_checkboxes_1_item_1",
747
+ "hint": "",
748
+ "name": "burgers_checkboxes_1",
749
+ "_type": "checkbox",
750
+ "_uuid": "3251098f-c8e0-4ac5-a637-95fd2e1c4dc4",
751
+ "label": "Beef, cheese, tomato",
752
+ "value": "value-1",
753
+ "errors": {},
754
+ "legend": "Question",
755
+ "collection": "components",
756
+ "validation": {
757
+ "required": true
758
+ }
759
+ },
760
+ {
761
+ "_id": "burgers_checkboxes_1_item_2",
762
+ "hint": "",
763
+ "name": "burgers_checkboxes_1",
764
+ "_type": "checkbox",
765
+ "_uuid": "a0717bbd-0b9f-47a0-bf2b-2a9d208e1168",
766
+ "label": "Chicken, cheese, tomato",
767
+ "value": "value-2",
768
+ "errors": {},
769
+ "legend": "Question",
770
+ "collection": "components",
771
+ "validation": {
772
+ "required": true
773
+ }
774
+ },
775
+ {
776
+ "_id": "burgers_checkboxes_1_item_3",
777
+ "hint": "",
778
+ "name": "burgers_checkboxes_1",
779
+ "_type": "checkbox",
780
+ "_uuid": "4c409737-80bb-48c7-afa7-6e9360b65004",
781
+ "label": "Mozzarella, cheddar, feta",
782
+ "value": "value-3",
783
+ "errors": {},
784
+ "legend": "Question",
785
+ "collection": "components",
786
+ "validation": {
787
+ "required": true
788
+ }
789
+ }
790
+ ],
791
+ "errors": {},
792
+ "legend": "What would you like on your burger?",
793
+ "collection": "components",
794
+ "validation": {
795
+ "required": false
796
+ }
797
+ }
798
+ ]
799
+ },
800
+ {
801
+ "_id": "page.global-warming",
802
+ "url": "global-warming",
803
+ "body": "What about the trees?",
804
+ "lede": "",
805
+ "_type": "page.content",
806
+ "_uuid": "bc666714-c0a2-4674-afe5-faff2e20d847",
807
+ "heading": "Global warming",
808
+ "components": [
809
+ {
810
+ "_id": "global-warming_content_1",
811
+ "name": "global-warming_content_1",
812
+ "_type": "content",
813
+ "_uuid": "957917c3-7473-4c30-b713-5aae77a1c22f",
814
+ "content": "They will come for you"
815
+ }
816
+ ],
817
+ "section_heading": ""
818
+ },
819
+ {
820
+ "_id": "page.we-love-chickens",
821
+ "url": "we-love-chickens",
822
+ "body": "Cluck cluck",
823
+ "lede": "",
824
+ "_type": "page.content",
825
+ "_uuid": "e2887f44-5e8d-4dc0-b1de-496ab6039430",
826
+ "heading": "We love chickens",
827
+ "components": [
828
+ {
829
+ "_id": "global-warming_content_1",
830
+ "name": "global-warming_content_1",
831
+ "_type": "content",
832
+ "_uuid": "49ff5c79-2e63-45fe-bbf7-de0bd061b909",
833
+ "content": "Cruella ain't got nothing on Cluckeralla"
834
+ }
835
+ ],
836
+ "section_heading": ""
837
+ },
838
+ {
839
+ "_id": "page.marvel-series",
840
+ "url": "marvel-series",
841
+ "_type": "page.singlequestion",
842
+ "_uuid": "dc7454f9-4186-48d7-b055-684d57bbcdc7",
843
+ "components": [
844
+ {
845
+ "_id": "marvel-series_radios_1",
846
+ "hint": "",
847
+ "name": "marvel-series_radios_1",
848
+ "_type": "radios",
849
+ "_uuid": "5ca01e56-94de-4c33-b5fc-74697e5b95cc",
850
+ "items": [
851
+ {
852
+ "_id": "marvel-series_radios_1_item_1",
853
+ "hint": "",
854
+ "_type": "radio",
855
+ "_uuid": "0c4192eb-1441-4f10-b00a-0f03b756269e",
856
+ "label": "WandaVision"
857
+ },
858
+ {
859
+ "_id": "marvel-series_radios_1_item_2",
860
+ "hint": "",
861
+ "_type": "radio",
862
+ "_uuid": "bd14bf31-427e-4267-9926-a18c167fe604",
863
+ "label": "The Falcon and the Winter Soldier"
864
+ },
865
+ {
866
+ "_id": "marvel-series_radios_1_item_2",
867
+ "hint": "",
868
+ "_type": "radio",
869
+ "_uuid": "eb720ef7-bcaf-4c73-8972-6fc80dca6245",
870
+ "label": "Loki"
871
+ },
872
+ {
873
+ "_id": "marvel-series_radios_1_item_3",
874
+ "hint": "",
875
+ "_type": "radio",
876
+ "_uuid": "d762e328-18dc-4861-bca8-d104609b8299",
877
+ "label": "Hawkeye"
878
+ }
879
+ ],
880
+ "errors": {},
881
+ "legend": "What is the best marvel series?",
882
+ "validation": {
883
+ "required": true
884
+ }
885
+ }
886
+ ]
887
+ },
888
+ {
889
+ "_id": "page.marvel-quotes",
890
+ "url": "marvel-quotes",
891
+ "body": "Marvel quotes",
892
+ "lede": "",
893
+ "_type": "page.content",
894
+ "_uuid": "2cc66e51-2c14-4023-86bf-ded49887cdb2",
895
+ "heading": "Loki",
896
+ "components": [
897
+ {
898
+ "_id": "marvel-quotes_content_1",
899
+ "name": "marvel-quotes_content_1",
900
+ "_type": "content",
901
+ "_uuid": "5cede2fa-5001-4d6a-965c-30addc3c4496",
902
+ "content": "I am Loki of Asgard, and I am burdened with glorious purpose."
903
+ },
904
+ {
905
+ "_id": "marvel-quotes_content_2",
906
+ "name": "marvel-quotes_content_2",
907
+ "_type": "content",
908
+ "_uuid": "816b6afd-4649-455a-b10d-62cb5acb4584",
909
+ "content": "So Who Are You Fighting Now ... Gandalf?"
910
+ }
911
+ ],
912
+ "section_heading": ""
913
+ },
914
+ {
915
+ "_id": "page.other-quotes",
916
+ "url": "other-quotes",
917
+ "body": "Other quotes",
918
+ "lede": "",
919
+ "_type": "page.content",
920
+ "_uuid": "f6c51f88-7be8-4cb7-bbfc-6c905727a051",
921
+ "heading": "Other quotes",
922
+ "components": [
923
+ {
924
+ "_id": "other-quotes_content_1",
925
+ "name": "other-quotes_content_1",
926
+ "_type": "content",
927
+ "_uuid": "2535a867-deab-457b-91aa-c006e3c73d63",
928
+ "content": "- How we doing? - Same as always... - That bad, huh?"
929
+ },
930
+ {
931
+ "_id": "other-quotes_content_2",
932
+ "name": "other-quotes_content_2",
933
+ "_type": "content",
934
+ "_uuid": "816b6afd-4649-455a-b10d-62cb5acb4584",
935
+ "content": "Are you using your night vision, Vision?"
936
+ }
937
+ ],
938
+ "section_heading": ""
939
+ },
611
940
  {
612
941
  "_id": "page.check-answers",
613
942
  "url": "check-answers",
@@ -655,7 +984,7 @@
655
984
  "created_by": "099d5bf5-5f7b-444c-86ee-9e189cc1a369",
656
985
  "service_id": "488edccd-8411-4ffb-a38b-6a96c6ac28d6",
657
986
  "version_id": "27dc30c9-f7b8-4dec-973a-bd153f6797df",
658
- "service_name": "Version Fixture",
987
+ "service_name": "Branching Fixture",
659
988
  "configuration": {
660
989
  "meta": {
661
990
  "_id": "config.meta",
@@ -1,3 +1,3 @@
1
1
  module MetadataPresenter
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '1.7.1'.freeze
3
3
  end
@@ -1,4 +1,89 @@
1
- # desc "Explaining what the task does"
2
- # task :metadata_presenter do
3
- # # Task goes here
4
- # end
1
+ require 'metadata_presenter/test_helpers'
2
+
3
+ namespace :metadata do
4
+ include MetadataPresenter::TestHelpers
5
+
6
+ desc 'Represent the flow objects in human readable form'
7
+ task flow: :environment do
8
+ require 'ruby-graphviz'
9
+ metadata = ENV['SERVICE_METADATA'] || metadata_fixture('branching')
10
+ service = MetadataPresenter::Service.new(metadata)
11
+
12
+ graph = MetadataPresenter::Graph.new(service)
13
+
14
+ graph.draw.generate_image
15
+ puts "Generated file #{graph.filename}"
16
+ system("open #{graph.filename}")
17
+ end
18
+ end
19
+
20
+ module MetadataPresenter
21
+ class Graph
22
+ attr_reader :service, :filename, :nodes
23
+
24
+ delegate :metadata, :start_page, :find_page_by_uuid, :service_slug, to: :service
25
+
26
+ def initialize(service)
27
+ @service = service
28
+ @graphviz = GraphViz.new(:G, type: :digraph)
29
+ @filename = Rails.root.join('tmp', "#{service_slug}.png")
30
+ @nodes = {}
31
+ end
32
+
33
+ def draw
34
+ draw_nodes
35
+ draw_edges
36
+
37
+ self
38
+ end
39
+
40
+ def generate_image
41
+ @graphviz.output(png: filename)
42
+ end
43
+
44
+ private
45
+
46
+ def draw_nodes
47
+ flow.each do |id, _value|
48
+ flow_object = service.flow(id)
49
+
50
+ if flow_object.branch?
51
+ full_description = flow_object.conditions.map do |condition|
52
+ condition.criterias.map do |criteria|
53
+ criteria.service = service
54
+
55
+ "if #{criteria.criteria_component.humanised_title} #{criteria.operator} #{criteria.field_label}"
56
+ end
57
+ end
58
+ nodes[id] = @graphviz.add_nodes(full_description.flatten.join(' - '))
59
+ else
60
+ current_page = find_page_by_uuid(id)
61
+ nodes[id] = @graphviz.add_nodes(current_page.url)
62
+ end
63
+ end
64
+ end
65
+
66
+ def draw_edges
67
+ flow.each do |id, _value|
68
+ flow_object = service.flow(id)
69
+ current_node = nodes[id]
70
+ node_next = nodes[flow_object.default_next]
71
+
72
+ if flow_object.branch?
73
+ @graphviz.add_edges(current_node, node_next, label: 'Conditions are not met', labelfontsize: 8) if node_next
74
+
75
+ flow_object.group_by_page.each do |page_uuid, _conditions|
76
+ conditions_node = nodes[page_uuid]
77
+ @graphviz.add_edges(current_node, conditions_node, label: 'Conditions are met', labelfontsize: 8) if conditions_node
78
+ end
79
+ elsif node_next
80
+ @graphviz.add_edges(current_node, node_next)
81
+ end
82
+ end
83
+ end
84
+
85
+ def flow
86
+ metadata['flow']
87
+ end
88
+ end
89
+ end
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: 1.4.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MoJ Online
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-25 00:00:00.000000000 Z
11
+ date: 2021-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: govuk_design_system_formbuilder
@@ -128,6 +128,20 @@ dependencies:
128
128
  - - ">="
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: ruby-graphviz
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
131
145
  - !ruby/object:Gem::Dependency
132
146
  name: rspec-rails
133
147
  requirement: !ruby/object:Gem::Requirement
@@ -277,7 +291,9 @@ files:
277
291
  - app/models/metadata_presenter/offline_upload_adapter.rb
278
292
  - app/models/metadata_presenter/page.rb
279
293
  - app/models/metadata_presenter/page_answers.rb
294
+ - app/models/metadata_presenter/previous_page.rb
280
295
  - app/models/metadata_presenter/service.rb
296
+ - app/models/metadata_presenter/traversed_pages.rb
281
297
  - app/models/metadata_presenter/uploaded_file.rb
282
298
  - app/operators/metadata_presenter/base_operator.rb
283
299
  - app/operators/metadata_presenter/is_answered_operator.rb