mobile_workflow 0.7.3 → 0.7.7

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -0
  3. data/app/controllers/concerns/mobile_workflow/s3_storable.rb +14 -11
  4. data/app/controllers/mobile_workflow/sns_notifications_controller.rb +14 -37
  5. data/app/jobs/mobile_workflow/add_attachment_job.rb +30 -0
  6. data/app/models/concerns/mobile_workflow/attachable.rb +32 -0
  7. data/app/models/concerns/mobile_workflow/displayable/steps/form.rb +70 -0
  8. data/app/models/concerns/mobile_workflow/displayable/steps/list.rb +15 -0
  9. data/app/models/concerns/mobile_workflow/displayable/steps/map.rb +11 -0
  10. data/app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb +11 -0
  11. data/app/models/concerns/mobile_workflow/displayable/steps/question.rb +25 -0
  12. data/app/models/concerns/mobile_workflow/displayable/steps/stack.rb +75 -0
  13. data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb +30 -0
  14. data/app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb +40 -0
  15. data/app/models/concerns/mobile_workflow/displayable.rb +17 -146
  16. data/lib/generators/mobile_workflow/install/install_generator.rb +12 -0
  17. data/lib/generators/mobile_workflow/install/templates/app/models/application_record.rb +1 -0
  18. data/lib/generators/mobile_workflow/install/templates/deserializer.rb.erb +13 -0
  19. data/lib/generators/mobile_workflow/install/templates/deserializer_spec.rb.erb +29 -0
  20. data/lib/generators/mobile_workflow/install/templates/user.rb.erb +4 -0
  21. data/lib/generators/mobile_workflow/model_generator.rb +9 -2
  22. data/lib/generators/mobile_workflow/templates/controller.rb.erb +4 -2
  23. data/lib/generators/mobile_workflow/templates/model.rb.erb +17 -2
  24. data/lib/mobile_workflow/displayable.rb +9 -0
  25. data/lib/mobile_workflow/engine.rb +7 -5
  26. data/lib/mobile_workflow/version.rb +1 -1
  27. data/lib/mobile_workflow.rb +1 -0
  28. metadata +49 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 026fc97366b9da9d1cdd8c27935c9c120a872fc0f666e3e8dec8ebe881e36c8e
4
- data.tar.gz: 2963f07f4173b235411a1256a5ab716805edebc78cd82c60603603316a861284
3
+ metadata.gz: cf9a3d0ae2752b171aea1043b7cc57a8425b80ecf086bb226faadffb936fd5ee
4
+ data.tar.gz: 1d0c4bf77cbbb66844a58a4d94bfb5532f4706a4a5fa2ae02f01e1e0f72e4939
5
5
  SHA512:
6
- metadata.gz: 297c0feb073d5172064eb35168aff8864b7d523e91bc9f0bcbede72ad16c2fb2e4507aea89b52f21fc921dbef299f00b2b9d8765c595f1e1435392bbd84a2693
7
- data.tar.gz: 9d3442386b9f5846dca8124f2bdb878302992bb42142b6ac3601abd3fd14d3c556051c261f210faf98bc24e21adf8edb5ff2b16ead759dccfb6fe0878c7b755c
6
+ metadata.gz: e3c1748bdbe2b84415e434632080968cc02fb4e6e7b5a248bf8d979868b74714ce89c0d04165207d3b67008e45b3f08e4b84f67124a128f971627ad28611591f
7
+ data.tar.gz: 90329d6e78a89c601fa6bb5cc62b4d856b9ba264e56d6bfe83b7e1afc5ea5d1849355ccf1beb272d2474c90b5b5ec2bca191903db9dbe465fa5141b52053dadf
data/README.md CHANGED
@@ -26,6 +26,89 @@ Or install it yourself as:
26
26
  $ gem install mobile_workflow
27
27
  ```
28
28
 
29
+ ## Upgrade to version 0.7.7 or higher
30
+ The following utility methods have changed their parameters:
31
+
32
+ ```ruby
33
+ # app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb#20
34
+ def mw_grid_item(id: self.id, text:, detail_text: nil, preview_url: nil)
35
+ raise 'Missing id' if id.nil?
36
+ raise 'Missing text' if text.nil?
37
+
38
+ { id: id, text: text, type: :item, detailText: detail_text, imageURL: preview_url }.compact
39
+ end
40
+
41
+ # app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb#20
42
+ def mw_stack_list_item(id:, text:, detail_text: nil, preview_url: nil)
43
+ raise 'Missing id' if id.nil?
44
+ raise 'Missing text' if text.nil?
45
+
46
+ { id: id.to_s, text: text, detailText: detail_text, type: :listItem, imageURL: preview_url }.compact
47
+ end
48
+
49
+ # app/models/concerns/mobile_workflow/displayable/steps/list.rb#5
50
+ def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, preview_url: nil)
51
+ { id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name, imageURL: preview_url }.compact
52
+ end
53
+
54
+ # app/models/concerns/mobile_workflow/displayable/steps/stack.rb#26
55
+ def mw_display_video(preview_url:, attachment_url:)
56
+ {type: :video, previewURL: preview_url, url: attachment_url}
57
+ end
58
+
59
+ # app/models/concerns/mobile_workflow/displayable/steps/stack.rb#11
60
+ def mw_display_image(preview_url:, attachment_url:, content_mode: :scale_aspect_fill)
61
+ validate_content_mode!(content_mode)
62
+
63
+ {type: :image, contentMode: camelcase_converter(content_mode.to_s, first_letter: :lower), previewURL: preview_url, url: attachment_url}
64
+ end
65
+ ```
66
+
67
+ All URLs MUST now be explicitly sent as arguments to the above methods, which means they must be previously set. If not, the methods will not work.
68
+
69
+ In order to support projects using `ActiveStorage`, there is a new model concern `MobileWorkflow::Attachable` that provides a few helpers. This is what you can do to upgrade if you use ActiveStorage (otherwise the helpers must be manually created):
70
+
71
+ 1. Include the concern in the `ApplicationRecord` class, together with `MobileWorkflow::Displayable`:
72
+
73
+ ```ruby
74
+ class ApplicationRecord < ActiveRecord::Base
75
+ include MobileWorkflow::Attachable
76
+ include MobileWorkflow::Displayable
77
+ end
78
+ ```
79
+
80
+ 2. Once included, the following helpers will be available, so use them to generate the intended URLs:
81
+
82
+ ```ruby
83
+ def preview_url(attachment, options: { resize_to_fill: [200, 200] })
84
+ return nil unless attachment.attached?
85
+
86
+ if attachment.image?
87
+ rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
88
+ elsif attachment.previewable?
89
+ rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
90
+ else
91
+ return nil
92
+ end
93
+ end
94
+
95
+ def attachment_url(attachment)
96
+ return nil unless attachment.attached?
97
+
98
+ rails_blob_url(attachment, host: heroku_attachment_host)
99
+ end
100
+ ```
101
+
102
+ Example of use:
103
+ ```ruby
104
+ # Old method call
105
+ mw_list_item(text: 'John Doe', detail_text: 'Company Name', image_attachment: <ActiveStorage::Attached::One>, image_url: 'https://test.org/preview')
106
+
107
+ # New method call
108
+ preview_url = preview_url(<ActiveStorage::Attached::One>, options: { resize_to_fill: [200, 200] }) || 'https://test.org/preview'
109
+ mw_list_item(text: 'John Doe', detail_text: 'Company Name', preview_url: preview_url)
110
+ ```
111
+
29
112
  ## Contributing
30
113
  Contribution directions go here.
31
114
 
@@ -1,19 +1,23 @@
1
+ require 'aws-sdk-s3'
2
+
1
3
  module MobileWorkflow
2
4
  module S3Storable
3
5
  if Object.const_defined?("Aws::S3")
4
6
  extend ActiveSupport::Concern
5
7
 
6
8
  def binary_urls(object)
7
- return unless params["binaries"]
9
+ return unless params[:binaries]
8
10
 
9
- params["binaries"].map do |binary|
10
- object_attribute = binary["identifier"]
11
- extension = binary["mimetype"].split('/')[1] # i.e. image/jpg --> jpg, video/mp4 --> mp4
12
-
11
+ params[:binaries].map do |binary|
12
+ identifier = binary[:identifier]
13
+ object_attribute = identifier.split(".")[0] # ensure extension doesnt get added here
14
+ extension = binary[:mimetype].split('/')[1] # i.e. image/jpg --> jpg, video/mp4 --> mp4
15
+ metadata = binary[:metadata]
16
+
13
17
  {
14
- "identifier" => binary["identifier"],
15
- "url" => presigned_url("#{object.class.name.underscore}/#{object.id}/#{object_attribute}/#{s3_object_uuid}.#{extension}"),
16
- "method" => "PUT"
18
+ identifier: identifier,
19
+ url: presigned_url("#{object.class.name.underscore}/#{object.id}/#{object_attribute}/#{s3_object_uuid}.#{extension}", metadata: metadata),
20
+ method: "PUT"
17
21
  }
18
22
  end
19
23
  end
@@ -23,8 +27,8 @@ module MobileWorkflow
23
27
  SecureRandom.uuid
24
28
  end
25
29
 
26
- def presigned_url(key)
27
- presigner.presigned_url(:put_object, bucket: ENV['AWS_BUCKET_NAME'], key: key, metadata: {})
30
+ def presigned_url(key, metadata: nil)
31
+ presigner.presigned_url(:put_object, bucket: ENV['AWS_BUCKET_NAME'], key: key, metadata: metadata)
28
32
  end
29
33
 
30
34
  def presigner
@@ -34,7 +38,6 @@ module MobileWorkflow
34
38
  def s3_client
35
39
  Aws::S3::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
36
40
  end
37
-
38
41
  end
39
42
  end
40
43
  end
@@ -1,3 +1,5 @@
1
+ require 'aws-sdk-sns'
2
+
1
3
  module MobileWorkflow
2
4
  class SnsNotificationsController < ActionController::API
3
5
  if Object.const_defined?("Aws::S3") && Object.const_defined?("Aws::SNS")
@@ -10,26 +12,16 @@ module MobileWorkflow
10
12
  when 'SubscriptionConfirmation'
11
13
  confirm_subscription ? (head :ok) : (head :bad_request)
12
14
  else
13
- add_attachment
15
+ AddAttachmentJob.perform_now(object, object_key, attribute_name)
16
+ head :ok
14
17
  end
18
+ rescue NameError => e
19
+ Rails.logger.warn "Error attaching object: #{e.message}"
20
+ rescue ActiveRecord::RecordNotFound
21
+ head :not_found
15
22
  end
16
23
 
17
24
  private
18
- def add_attachment
19
- begin
20
- @object = find_object
21
- @object.send("#{attribute_name}=",active_record_blob)
22
- if @object.save
23
- head :ok
24
- else
25
- Rails.logger.warn "Error saving object: #{@object} #{object.errors.full_messages}"
26
- head :unprocessable_entity
27
- end
28
- rescue NameError => e
29
- Rails.logger.warn "Error attaching object: #{e.message}"
30
- head :unprocessable_entity
31
- end
32
- end
33
25
 
34
26
  def verify_request_authenticity
35
27
  head :unauthorized if raw_post.blank?
@@ -37,22 +29,11 @@ module MobileWorkflow
37
29
  #head :unauthorized if raw_post.blank? || !message_verifier.authentic?(raw_post) # Not working
38
30
  end
39
31
 
40
- def active_record_blob
41
- s3_object = s3_bucket.object(object_key)
42
- checksum_base64 = checksum_base64(object_key, s3_object)
43
- ActiveStorage::Blob.create! key: s3_object.key, filename: s3_object.key, byte_size: s3_object.size, checksum: checksum_base64, content_type: s3_object.content_type
44
- end
45
-
46
- def checksum_base64(object_key, s3_object)
47
- path = Tempfile.new(object_key).path
48
- s3_object.download_file(path)
49
- file = File.new(path)
50
- Digest::MD5.file(file).base64digest
51
- end
52
-
53
- def find_object
54
- object_class_name, object_id = key_identifiers
55
- object_class_name.camelcase.constantize.find(object_id.to_i)
32
+ def object
33
+ @object ||= begin
34
+ object_class_name, object_id = key_identifiers
35
+ object_class_name.camelcase.constantize.find(object_id.to_i)
36
+ end
56
37
  end
57
38
 
58
39
  def attribute_name
@@ -91,11 +72,7 @@ module MobileWorkflow
91
72
  Rails.logger.warn(e.message)
92
73
  return false
93
74
  end
94
-
95
- def s3_bucket
96
- Aws::S3::Resource.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY']).bucket(ENV['AWS_BUCKET_NAME'])
97
- end
98
-
75
+
99
76
  def sns_client
100
77
  Aws::SNS::Client.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY'])
101
78
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MobileWorkflow
4
+ class AddAttachmentJob < ApplicationJob
5
+ def perform(object, object_key, attribute_name)
6
+ object.send("#{attribute_name}=", active_record_blob_from_s3(object_key))
7
+ Rails.logger.warn "Error saving object: #{object} #{object.errors.full_messages}" unless object.save
8
+ rescue NoMethodError => e
9
+ Rails.logger.warn "Error attaching object: #{e.message}"
10
+ end
11
+
12
+ private
13
+
14
+ def active_record_blob_from_s3(object_key)
15
+ # etag cannot be used as the MD5 checksum when doing multi-part uploads
16
+ s3_object = s3_bucket.object(object_key)
17
+ base64_digest = hex_to_base64_digest(s3_object.etag.delete('"'))
18
+ ActiveStorage::Blob.create! key: s3_object.key, filename: s3_object.key, byte_size: s3_object.size,
19
+ checksum: base64_digest, content_type: s3_object.content_type
20
+ end
21
+
22
+ def s3_bucket
23
+ Aws::S3::Resource.new(region: ENV['AWS_REGION'], access_key_id: ENV['AWS_ACCESS_ID'], secret_access_key: ENV['AWS_SECRET_KEY']).bucket(ENV['AWS_BUCKET_NAME'])
24
+ end
25
+
26
+ def hex_to_base64_digest(hexdigest)
27
+ [[hexdigest].pack('H*')].pack('m0')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module MobileWorkflow
2
+ module Attachable
3
+ extend ActiveSupport::Concern
4
+ include Rails.application.routes.url_helpers
5
+
6
+ def preview_url(attachment, options: { resize_to_fill: [200, 200] })
7
+ return nil unless attachment.attached?
8
+
9
+ if attachment.image?
10
+ rails_representation_url(attachment.variant(options), host: heroku_attachment_host)
11
+ elsif attachment.previewable?
12
+ rails_representation_url(attachment.preview(options), host: heroku_attachment_host)
13
+ else
14
+ return nil
15
+ end
16
+ end
17
+
18
+ def attachment_url(attachment)
19
+ return nil unless attachment.attached?
20
+
21
+ rails_blob_url(attachment, host: heroku_attachment_host)
22
+ end
23
+
24
+ private
25
+
26
+ def heroku_attachment_host
27
+ # TODO: MBS - move this to a configuration property
28
+ app_name = Rails.env.test? ? 'test-app' : ENV.fetch('HEROKU_APP_NAME')
29
+ "https://#{app_name}.herokuapp.com"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Form
5
+ def mw_form_section(label:, identifier:)
6
+ raise 'Missing label' if label.nil?
7
+ raise 'Missing identifier' if identifier.nil?
8
+
9
+ { item_type: :section, label: label, identifier: identifier }
10
+ end
11
+
12
+ def mw_form_multiple_selection(label:, identifier:, multiple_selection_options:, selection_type: :single, optional: false, show_other_option: false)
13
+ raise 'Missing label' if label.nil?
14
+ raise 'Missing identifier' if identifier.nil?
15
+ raise 'Missing multiple selection options' if multiple_selection_options.nil?
16
+
17
+ { item_type: :multiple_selection, label: label, identifier: identifier, multiple_selection_options: multiple_selection_options, selection_type: selection_type, optional: optional, show_other_option: show_other_option }
18
+ end
19
+
20
+ def mw_form_multiple_selection_options(text:, hint: nil, is_pre_selected: false)
21
+ raise 'Missing text' if text.nil?
22
+
23
+ { text: text, hint: hint, isPreSelected: is_pre_selected }
24
+ end
25
+
26
+ def mw_form_number(label:, identifier:, placeholder: nil, optional: false, symbol_position: :leading, default_text_answer: nil, hint: nil)
27
+ raise 'Missing label' if label.nil?
28
+ raise 'Missing identifier' if identifier.nil?
29
+
30
+ { item_type: :number, number_type: :number, label: label, identifier: identifier, placeholder: placeholder, optional: optional, symbol_position: symbol_position, default_text_answer: default_text_answer, hint: hint }
31
+ end
32
+
33
+ def mw_form_text(label:, identifier:, placeholder: nil, optional: false, multiline: false, default_text_answer: nil)
34
+ raise 'Missing label' if label.nil?
35
+ raise 'Missing identifier' if identifier.nil?
36
+
37
+ { item_type: :text, label: label, identifier: identifier, placeholder: placeholder, optional: optional, multiline: multiline, default_text_answer: default_text_answer }
38
+ end
39
+
40
+ def mw_form_date(label:, identifier:, optional: false, default_date_time_answer: nil)
41
+ raise 'Missing label' if label.nil?
42
+ raise 'Missing identifier' if identifier.nil?
43
+
44
+ { item_type: :date, date_type: :calendar, label: label, identifier: identifier, optional: optional, default_date_time_answer: default_date_time_answer }
45
+ end
46
+
47
+ def mw_form_time(label:, identifier:, optional: false, default_date_time_answer: nil)
48
+ raise 'Missing label' if label.nil?
49
+ raise 'Missing identifier' if identifier.nil?
50
+
51
+ { item_type: :time, label: label, identifier: identifier, optional: optional, default_date_time_answer: default_date_time_answer }
52
+ end
53
+
54
+ def mw_form_email(label:, identifier:, placeholder: nil, optional: false, default_text_answer: nil)
55
+ raise 'Missing label' if label.nil?
56
+ raise 'Missing identifier' if identifier.nil?
57
+
58
+ { item_type: :email, label: label, identifier: identifier, placeholder: placeholder, optional: optional, default_text_answer: default_text_answer }
59
+ end
60
+
61
+ def mw_form_password(label:, identifier:, placeholder: nil, optional: false, default_text_answer: nil, hint: nil)
62
+ raise 'Missing label' if label.nil?
63
+ raise 'Missing identifier' if identifier.nil?
64
+
65
+ { item_type: :secure, label: label, identifier: identifier, placeholder: placeholder, optional: optional, default_text_answer: default_text_answer, hint: hint }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module List
5
+ def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, preview_url: nil)
6
+ { id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name, imageURL: preview_url }.compact
7
+ end
8
+
9
+ def mw_list_search_suggestion(id: self.id, text:, section_name:, sf_symbol_name: nil)
10
+ {id: id.to_s, text: text, sectionName: section_name, sfSymbolName: sf_symbol_name}.compact
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Map
5
+ def mw_map_item(id: self.id, text:, detail_text: nil, latitude:, longitude:)
6
+ {id: id.to_s, text: text, detailText: detail_text, latitude: latitude, longitude: longitude}.compact
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module PieChart
5
+ def mw_pie_chart_item(id: self.id, label:, value:)
6
+ {id: id, label: label, value: value}.compact
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Question
5
+ QUESTION_STYLES = [:single_choice, :multiple_choice]
6
+
7
+ def mw_text_choice_question(question:, style:, text_choices:)
8
+ raise 'Missing question' if question.empty?
9
+ raise 'Text Choices should be a hash' unless text_choices.is_a?(Hash)
10
+
11
+ validate_question_style!(style)
12
+
13
+ text_choices_a = text_choices.map{|k, v| {_class: "ORKTextChoice", exclusive: false, text: k, value: v} }.to_a
14
+ { question: question, answerFormat: { _class: "ORKTextChoiceAnswerFormat", style: camelcase_converter(style.to_s, first_letter: :lower), textChoices: text_choices_a}}
15
+ end
16
+
17
+ private
18
+
19
+ def validate_question_style!(style)
20
+ raise 'Unknown style' unless QUESTION_STYLES.include?(style)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module Stack
5
+ CONTENT_MODE_OPTIONS = [:scale_aspect_fill, :scale_aspect_fit]
6
+
7
+ def mw_display_text(text:, label: nil)
8
+ {type: :text, label: label, text: text.to_s}.compact
9
+ end
10
+
11
+ def mw_display_image(preview_url:, attachment_url:, content_mode: :scale_aspect_fill)
12
+ validate_content_mode!(content_mode)
13
+
14
+ {type: :image, contentMode: camelcase_converter(content_mode.to_s, first_letter: :lower), previewURL: preview_url, url: attachment_url}
15
+ end
16
+
17
+ def mw_display_unsplash_image(image_url)
18
+ if image_url.start_with? "https://unsplash.com/photos"
19
+ unsplash_id = image_url.split('/').last
20
+ image_url = "https://source.unsplash.com/#{unsplash_id}/800x600"
21
+ end
22
+
23
+ {type: :image, previewURL: image_url, url: image_url}.compact
24
+ end
25
+
26
+ def mw_display_video(preview_url:, attachment_url:)
27
+ {type: :video, previewURL: preview_url, url: attachment_url}
28
+ end
29
+
30
+ def mw_display_button(label:, style: :primary, on_success: :forward, sf_symbol_name: nil, material_icon_name: nil)
31
+ validate_on_success!(on_success)
32
+ validate_button_style!(style)
33
+
34
+ {type: :button, label: label, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
35
+ end
36
+
37
+ def mw_display_delete_button(url:, label: "Delete", method: :delete, style: :danger, on_success: :backward)
38
+ validate_on_success!(on_success)
39
+ validate_button_style!(style)
40
+
41
+ {type: :button, label: label, url: url, method: method, style: style, onSuccess: on_success, sfSymbolName: 'trash', materialIconName: 'delete'}.compact
42
+ end
43
+
44
+ def mw_display_url_button(url:, label:, method: :put, style: :primary, confirm_title: nil, confirm_text: nil, on_success: :reload, sf_symbol_name: nil, material_icon_name: nil)
45
+ validate_on_success!(on_success)
46
+ validate_button_style!(style)
47
+
48
+ {type: :button, label: label, url: url, method: method, style: style, confirmTitle: confirm_title, confirmText: confirm_text, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
49
+ end
50
+ alias_method :mw_display_button_for_url, :mw_display_url_button
51
+
52
+ def mw_display_system_url_button(label:, apple_system_url: nil, android_deep_link: nil, style: :primary, sf_symbol_name: nil, material_icon_name: nil)
53
+ validate_button_style!(style)
54
+ raise 'Invalid android_deep_link' if android_deep_link && !android_deep_link.start_with?('http')
55
+
56
+ {type: :button, label: label, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, style: style, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
57
+ end
58
+ alias_method :mw_display_button_for_system_url, :mw_display_system_url_button
59
+
60
+ def mw_display_modal_workflow_button(label:, modal_workflow_name:, style: :primary, on_success: :none, sf_symbol_name: nil, material_icon_name: nil)
61
+ validate_on_success!(on_success)
62
+ validate_button_style!(style)
63
+
64
+ {type: :button, label: label, modalWorkflow: modal_workflow_name, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
65
+ end
66
+ alias_method :mw_display_button_for_modal_workflow, :mw_display_modal_workflow_button
67
+
68
+ private
69
+ def validate_content_mode!(on_success)
70
+ raise 'Unknown content_mode' unless CONTENT_MODE_OPTIONS.include?(on_success)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module StyledContent
5
+ module Grid
6
+ def mw_grid_large_section(id:, text:)
7
+ raise 'Missing id' if id.nil?
8
+ raise 'Missing text' if text.nil?
9
+
10
+ { id: id, text: text, type: :largeSection }
11
+ end
12
+
13
+ def mw_grid_small_section(id:, text:)
14
+ raise 'Missing id' if id.nil?
15
+ raise 'Missing text' if text.nil?
16
+
17
+ { id: id, text: text, type: :smallSection }
18
+ end
19
+
20
+ def mw_grid_item(id: self.id, text:, detail_text: nil, preview_url: nil)
21
+ raise 'Missing id' if id.nil?
22
+ raise 'Missing text' if text.nil?
23
+
24
+ { id: id, text: text, type: :item, detailText: detail_text, imageURL: preview_url }.compact
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ module MobileWorkflow
2
+ module Displayable
3
+ module Steps
4
+ module StyledContent
5
+ module Stack
6
+ def mw_stack_title(id:, title:)
7
+ raise 'Missing id' if id.nil?
8
+ raise 'Missing title' if title.nil?
9
+
10
+ { id: id, title: title, type: :title }
11
+ end
12
+
13
+ def mw_stack_text(id:, text:)
14
+ raise 'Missing id' if id.nil?
15
+ raise 'Missing text' if text.nil?
16
+
17
+ { id: id, text: text, type: :text }
18
+ end
19
+
20
+ def mw_stack_list_item(id:, text:, detail_text: nil, preview_url: nil)
21
+ raise 'Missing id' if id.nil?
22
+ raise 'Missing text' if text.nil?
23
+
24
+ { id: id.to_s, text: text, detailText: detail_text, type: :listItem, imageURL: preview_url }.compact
25
+ end
26
+
27
+ def mw_stack_button(id:, label:, url: nil, method: :nil, on_success: :none, style: :primary, modal_workflow_name: nil, link_url: nil, sf_symbol_name: nil, apple_system_url: nil, android_deep_link: nil, confirm_title: nil, confirm_text: nil)
28
+ raise 'Missing id' if id.nil?
29
+ raise 'Missing label' if label.nil?
30
+
31
+ validate_on_success!(on_success)
32
+ validate_button_style!(style)
33
+
34
+ { id: id, type: :button, label: label, url: url, method: method, onSuccess: on_success, style: style, modalWorkflow: modal_workflow_name, linkURL: link_url, sfSymbolName: sf_symbol_name, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, confirmTitle: confirm_title, confirmText: confirm_text }.compact
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,163 +1,34 @@
1
1
  module MobileWorkflow
2
2
  module Displayable
3
- extend ActiveSupport::Concern
4
- include Rails.application.routes.url_helpers
5
-
6
- ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
7
- BUTTON_STYLES = [:primary, :outline, :danger]
8
- CONTENT_MODE_OPTIONS = [:scale_aspect_fill, :scale_aspect_fit]
9
- QUESTION_STYLES = [:single_choice, :multiple_choice]
10
-
11
- def mw_list_item(id: self.id, text:, detail_text: nil, sf_symbol_name: nil, material_icon_name: nil, image_attachment: nil)
12
- mw_list_item = {id: id, text: text, detailText: detail_text, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}
13
- mw_list_item[:imageURL] = preview_url(image_attachment, options: { resize_to_fill: [200, 200] }) if image_attachment
14
- mw_list_item.compact
15
- end
16
-
17
- def mw_list_search_suggestion(id: self.id, text:, section_name:, sf_symbol_name: nil)
18
- {id: id.to_s, text: text, sectionName: section_name, sfSymbolName: sf_symbol_name}.compact
19
- end
20
-
21
- def mw_map_item(id: self.id, text:, detail_text: nil, latitude:, longitude:)
22
- {id: id.to_s, text: text, detailText: detail_text, latitude: latitude, longitude: longitude}.compact
23
- end
24
-
25
- def mw_pie_chart_item(id: self.id, label:, value:)
26
- {id: id, label: label, value: value}.compact
27
- end
28
-
29
- def mw_display_text(text:, label: nil)
30
- {type: :text, label: label, text: text.to_s}.compact
31
- end
32
-
33
- def mw_display_image(attachment, content_mode: :scale_aspect_fill, options: { resize_to_fill: [1200, 600] })
34
- validate_content_mode!(content_mode)
35
-
36
- {type: :image, contentMode: content_mode.to_s.camelize(:lower), previewURL: preview_url(attachment, options: options), url: attachment_url(attachment)}
37
- end
38
-
39
- def mw_display_unsplash_image(image_url)
40
- if image_url.start_with? "https://unsplash.com/photos"
41
- unsplash_id = image_url.split('/').last
42
- image_url = "https://source.unsplash.com/#{unsplash_id}/800x600"
43
- end
44
-
45
- {type: :image, previewURL: image_url, url: image_url}.compact
46
- end
47
-
48
- def mw_display_video(attachment, preview_options: { resize_to_fill: [600, 1200] })
49
- {type: :video, previewURL: preview_url(attachment, options: preview_options), url: attachment_url(attachment)}
50
- end
51
-
52
- def mw_display_button(label:, style: :primary, on_success: :forward, sf_symbol_name: nil, material_icon_name: nil)
53
- validate_on_success!(on_success)
54
- validate_button_style!(style)
55
-
56
- {type: :button, label: label, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
57
- end
58
-
59
- def mw_display_delete_button(url:, label: "Delete", method: :delete, style: :danger, on_success: :backward)
60
- validate_on_success!(on_success)
61
- validate_button_style!(style)
62
-
63
- {type: :button, label: label, url: url, method: method, style: style, onSuccess: on_success, sfSymbolName: 'trash', materialIconName: 'delete'}.compact
64
- end
65
-
66
- def mw_display_url_button(label:, url:, method: :put, style: :primary, confirm_title: nil, confirm_text: nil, on_success: :reload, sf_symbol_name: nil, material_icon_name: nil)
67
- validate_on_success!(on_success)
68
- validate_button_style!(style)
69
-
70
- {type: :button, label: label, url: url, method: method, style: style, confirmTitle: confirm_title, confirmText: confirm_text, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
71
- end
72
- alias_method :mw_display_button_for_url, :mw_display_url_button
73
-
74
- def mw_display_system_url_button(label:, apple_system_url: nil, android_deep_link: nil, style: :primary, sf_symbol_name: nil, material_icon_name: nil)
75
- validate_button_style!(style)
76
- raise 'Invalid android_deep_link' if android_deep_link && !android_deep_link.start_with?('http')
77
-
78
- {type: :button, label: label, appleSystemURL: apple_system_url, androidDeepLink: android_deep_link, style: style, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
79
- end
80
- alias_method :mw_display_button_for_system_url, :mw_display_system_url_button
81
-
82
- def mw_display_modal_workflow_button(label:, modal_workflow_name:, style: :primary, on_success: :none, sf_symbol_name: nil, material_icon_name: nil)
83
- validate_on_success!(on_success)
84
- validate_button_style!(style)
85
-
86
- {type: :button, label: label, modalWorkflow: modal_workflow_name, style: style, onSuccess: on_success, sfSymbolName: sf_symbol_name, materialIconName: material_icon_name}.compact
87
- end
88
- alias_method :mw_display_button_for_modal_workflow, :mw_display_modal_workflow_button
89
-
90
- def mw_text_choice_question(question:, style:, text_choices:)
91
- raise 'Missing question' if question.blank?
92
- raise 'Text Choices should be a hash' unless text_choices.is_a?(Hash)
93
- validate_question_style!(style)
94
-
95
- text_choices_a = text_choices.map{|k, v| {_class: "ORKTextChoice", exclusive: false, text: k, value: v} }.to_a
96
- { question: question, answerFormat: { _class: "ORKTextChoiceAnswerFormat", style: style.to_s.camelize(:lower), textChoices: text_choices_a}}
97
- end
98
-
99
- def mw_grid_large_section(id:, text:)
100
- raise 'Missing id' if id.nil?
101
- raise 'Missing text' if text.nil?
102
-
103
- { id: id, text: text, type: :largeSection }
104
- end
105
-
106
- def mw_grid_small_section(id:, text:)
107
- raise 'Missing id' if id.nil?
108
- raise 'Missing text' if text.nil?
109
-
110
- { id: id, text: text, type: :smallSection }
3
+ def self.included(base)
4
+ base.extend(Steps::Form)
111
5
  end
6
+
7
+ include Steps::List
8
+ include Steps::Map
9
+ include Steps::PieChart
10
+ include Steps::Question
11
+ include Steps::Stack
12
+ include Steps::StyledContent::Grid
13
+ include Steps::StyledContent::Stack
14
+
15
+ BUTTON_STYLES = [:primary, :outline, :danger, :textOnly]
16
+ ON_SUCCESS_OPTIONS = [:none, :reload, :backward, :forward]
112
17
 
113
- def mw_grid_item(id: self.id, text:, image_attachment: nil, options: { resize_to_fill: [1560, 877.5] })
114
- raise 'Missing id' if id.nil?
115
- raise 'Missing text' if text.nil?
116
-
117
- item = { id: id, text: text, type: :item }
118
- item[:imageURL] = preview_url(image_attachment, options: options) if image_attachment
119
- item
120
- end
121
-
122
18
  private
123
19
  def validate_on_success!(on_success)
124
20
  raise 'Unknown on_success action' unless ON_SUCCESS_OPTIONS.include?(on_success)
125
21
  end
126
22
 
127
- def validate_content_mode!(on_success)
128
- raise 'Unknown content_mode' unless CONTENT_MODE_OPTIONS.include?(on_success)
129
- end
130
-
131
23
  def validate_button_style!(style)
132
24
  raise 'Unknown style' unless BUTTON_STYLES.include?(style)
133
25
  end
134
-
135
- def validate_question_style!(style)
136
- raise 'Unknown style' unless QUESTION_STYLES.include?(style)
137
- end
138
-
139
- def preview_url(attachment, options:)
140
- return nil unless attachment.attached?
141
-
142
- if attachment.image?
143
- Rails.application.routes.url_helpers.rails_representation_url(attachment.variant(options), host: attachment_host)
144
- elsif attachment.previewable?
145
- Rails.application.routes.url_helpers.rails_representation_url(attachment.preview(options), host: attachment_host)
146
- else
147
- return nil
148
- end
149
- end
150
26
 
151
- def attachment_url(attachment)
152
- return nil unless attachment.attached?
153
-
154
- Rails.application.routes.url_helpers.rails_blob_url(attachment, host: heroku_attachment_host)
155
- end
27
+ def camelcase_converter(string, first_letter: :upper)
28
+ string = string.split("_").map(&:capitalize).join
29
+ return string unless first_letter == :lower
156
30
 
157
- def heroku_attachment_host
158
- # TODO: MBS - move this to a configuration property
159
- app_name = Rails.env.test? ? 'test' : ENV.fetch('HEROKU_APP_NAME')
160
- "https://#{app_name}.herokuapp.com"
31
+ string[0].downcase + string[1..-1]
161
32
  end
162
33
  end
163
34
  end
@@ -67,6 +67,18 @@ module MobileWorkflow
67
67
  end
68
68
  end
69
69
 
70
+ def generate_deserializers
71
+ say "Generating deserializers"
72
+
73
+ model_name_to_properties.each_pair do |model_name, properties|
74
+ @deserializer_class = model_name
75
+ @deserializer_properties = properties.split(' ').map { |attribute| attribute.split(':').first }
76
+
77
+ template("deserializer.rb.erb", "app/services/#{model_name}_deserializer.rb")
78
+ template("deserializer_spec.rb.erb", "spec/services/#{model_name}_deserializer_spec.rb")
79
+ end
80
+ end
81
+
70
82
  def generate_controllers_and_routes
71
83
  say "Generating controllers"
72
84
  controller_name_to_actions = open_api_spec.controller_name_to_actions
@@ -1,4 +1,5 @@
1
1
  class ApplicationRecord < ActiveRecord::Base
2
+ include MobileWorkflow::Attachable
2
3
  include MobileWorkflow::Displayable
3
4
 
4
5
  self.abstract_class = true
@@ -0,0 +1,13 @@
1
+ class <%= @deserializer_class.capitalize %>Deserializer
2
+ def self.parse(params)
3
+ <% @deserializer_properties.each do |attribute| -%>
4
+ <%= attribute %> = params.dig(:payload, :<%= attribute %>)
5
+ <% end -%>
6
+
7
+ { <%= @deserializer_class %>: {
8
+ <% @deserializer_properties.each do |attribute| -%>
9
+ <%= attribute %>: <%= attribute %>,
10
+ <% end -%>
11
+ }.compact }
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+ require 'json'
5
+
6
+ RSpec.describe <%= @deserializer_class.capitalize %>Deserializer do
7
+ let(:params) { {} }
8
+
9
+ describe 'ok' do
10
+ let(:params) { { payload: payload } }
11
+ let(:parsed_params) { { <%= @deserializer_class %>: <%= @deserializer_class %>_params } }
12
+ let(:payload) {
13
+ {
14
+ <% @deserializer_properties.each do |attribute| -%>
15
+ <%= attribute %>: 'string',
16
+ <% end -%>
17
+ }
18
+ }
19
+ let(:<%= @deserializer_class %>_params) {
20
+ {
21
+ <% @deserializer_properties.each do |attribute| -%>
22
+ <%= attribute %>: 'string',
23
+ <% end -%>
24
+ }
25
+ }
26
+
27
+ it { expect(described_class.parse(params)).to eq parsed_params }
28
+ end
29
+ end
@@ -1,4 +1,8 @@
1
1
  class User < ApplicationRecord
2
2
  has_secure_password
3
+
4
+ has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, dependent: :destroy
5
+ has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, dependent: :destroy
6
+
3
7
  validates :email, presence: true, uniqueness: true
4
8
  end
@@ -5,11 +5,18 @@ module MobileWorkflow
5
5
 
6
6
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
7
7
  source_root File.join(File.dirname(ActiveRecord::Generators::ModelGenerator.instance_method(:create_migration_file).source_location.first), "templates")
8
-
8
+
9
+ class_option :doorkeeper_oauth, type: :boolean, default: false
10
+
9
11
  def create_model_file
10
12
  template File.join(File.dirname(__FILE__), "templates", "model.rb.erb"), File.join('app/models', class_path, "#{file_name}.rb")
11
13
  end
12
-
14
+
15
+ private
16
+
17
+ def doorkeeper_oauth?
18
+ options[:doorkeeper_oauth]
19
+ end
13
20
  end
14
21
  end
15
22
  end
@@ -41,11 +41,13 @@ class <%= controller_class_name %>Controller < ApiController
41
41
  # passport_id = params.dig(:payload, :choose_passport, :selected, :id)
42
42
 
43
43
  Rails.logger.debug "Pre-rewrite params: #{params}"
44
- # Do your rewriting here
44
+
45
+ parsed_params = <%= controller_class_name.singularize %>Deserializer.parse(params)
46
+ params.merge!(parsed_params)
45
47
  end
46
48
 
47
49
  def <%= singular_table_name.underscore %>_params
48
- params.require(:payload).permit(<%= permitted_params %>)
50
+ params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
49
51
  end
50
52
  <% end -%>
51
53
  end
@@ -8,18 +8,33 @@ class <%= class_name %> < <%= parent_class_name.classify %>
8
8
  <% end -%>
9
9
  <% attributes.select(&:attachments?).each do |attribute| -%>
10
10
  has_many_attached :<%= attribute.name %>
11
+ <% end -%>
12
+ <% if class_name == 'User' -%>
13
+ <% if doorkeeper_oauth? -%>
14
+ has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id, dependent: :destroy
15
+ has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id, dependent: :destroy
16
+ <% end -%>
17
+ <% attributes.each do |attribute| -%>
18
+ <% if attribute.name == 'email' -%>
19
+ validates :email, presence: true, uniqueness: { case_sensitive: false }
20
+ before_validation :downcase_email, if: :email_changed?
21
+
22
+ def downcase_email
23
+ self.email = email.downcase
24
+ end
25
+ <% end -%>
26
+ <% end -%>
11
27
  <% end -%>
12
28
 
13
29
  def list_item_as_json
14
30
  mw_list_item(text: <%= attributes.first.name %>)
15
31
  end
16
-
32
+
17
33
  def display_as_json
18
34
  [
19
35
  mw_display_text(label: 'ID', text: id.to_s),
20
36
  mw_display_text(label: 'Text', text: <%= attributes.first.name %>)
21
37
  ]
22
38
  end
23
-
24
39
  end
25
40
  <% end -%>
@@ -0,0 +1,9 @@
1
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb'
2
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb'
3
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/form.rb'
4
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/list.rb'
5
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/map.rb'
6
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb'
7
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/question.rb'
8
+ require_relative '../../app/models/concerns/mobile_workflow/displayable/steps/stack.rb'
9
+ require_relative '../../app/models/concerns/mobile_workflow/displayable.rb'
@@ -1,9 +1,11 @@
1
1
  module MobileWorkflow
2
- class Engine < ::Rails::Engine
3
- isolate_namespace MobileWorkflow
4
-
5
- config.generators do |g|
6
- g.test_framework :rspec
2
+ if defined?(Rails)
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace MobileWorkflow
5
+
6
+ config.generators do |g|
7
+ g.test_framework :rspec
8
+ end
7
9
  end
8
10
  end
9
11
  end
@@ -1,5 +1,5 @@
1
1
  module MobileWorkflow
2
- VERSION = '0.7.3'
2
+ VERSION = '0.7.7'
3
3
  RUBY_VERSION = '2.7.3'
4
4
  RAILS_VERSION = '6.1.3.1'
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require "mobile_workflow/engine"
2
+ require "mobile_workflow/displayable"
2
3
 
3
4
  module MobileWorkflow
4
5
  require 'mobile_workflow/railtie' if defined?(Rails)
metadata CHANGED
@@ -1,15 +1,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobile_workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brooke-Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-01 00:00:00.000000000 Z
11
+ date: 2021-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-s3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.60'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.60.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.60'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.60.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: aws-sdk-sns
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.23'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.23'
13
47
  - !ruby/object:Gem::Dependency
14
48
  name: rails
15
49
  requirement: !ruby/object:Gem::Requirement
@@ -97,9 +131,19 @@ files:
97
131
  - app/controllers/concerns/mobile_workflow/s3_storable.rb
98
132
  - app/controllers/mobile_workflow/sns_notifications_controller.rb
99
133
  - app/helpers/mobile_workflow/application_helper.rb
134
+ - app/jobs/mobile_workflow/add_attachment_job.rb
100
135
  - app/jobs/mobile_workflow/application_job.rb
101
136
  - app/mailers/mobile_workflow/application_mailer.rb
137
+ - app/models/concerns/mobile_workflow/attachable.rb
102
138
  - app/models/concerns/mobile_workflow/displayable.rb
139
+ - app/models/concerns/mobile_workflow/displayable/steps/form.rb
140
+ - app/models/concerns/mobile_workflow/displayable/steps/list.rb
141
+ - app/models/concerns/mobile_workflow/displayable/steps/map.rb
142
+ - app/models/concerns/mobile_workflow/displayable/steps/pie_chart.rb
143
+ - app/models/concerns/mobile_workflow/displayable/steps/question.rb
144
+ - app/models/concerns/mobile_workflow/displayable/steps/stack.rb
145
+ - app/models/concerns/mobile_workflow/displayable/steps/styled_content/grid.rb
146
+ - app/models/concerns/mobile_workflow/displayable/steps/styled_content/stack.rb
103
147
  - app/views/layouts/mobile_workflow/application.html.erb
104
148
  - bin/mwf
105
149
  - config/routes.rb
@@ -116,6 +160,8 @@ files:
116
160
  - lib/generators/mobile_workflow/install/templates/app/views/layouts/application.html.erb
117
161
  - lib/generators/mobile_workflow/install/templates/app/views/sessions/new.html.erb
118
162
  - lib/generators/mobile_workflow/install/templates/create_users.rb
163
+ - lib/generators/mobile_workflow/install/templates/deserializer.rb.erb
164
+ - lib/generators/mobile_workflow/install/templates/deserializer_spec.rb.erb
119
165
  - lib/generators/mobile_workflow/install/templates/seeds.rb.erb
120
166
  - lib/generators/mobile_workflow/install/templates/sessions_controller.rb.erb
121
167
  - lib/generators/mobile_workflow/install/templates/spec/factories/users.rb
@@ -133,6 +179,7 @@ files:
133
179
  - lib/mobile_workflow/cli/aws_backend.rb
134
180
  - lib/mobile_workflow/cli/dokku_backend.rb
135
181
  - lib/mobile_workflow/cli/heroku_backend.rb
182
+ - lib/mobile_workflow/displayable.rb
136
183
  - lib/mobile_workflow/engine.rb
137
184
  - lib/mobile_workflow/open_api_spec/parser.rb
138
185
  - lib/mobile_workflow/railtie.rb