ecoportal-api-v2 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +55 -0
  5. data/.travis.yml +5 -0
  6. data/.yardopts +10 -0
  7. data/CHANGELOG.md +171 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +21 -0
  10. data/README.md +22 -0
  11. data/Rakefile +27 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/ecoportal-api-v2.gemspec +34 -0
  15. data/lib/ecoportal/api-v2.rb +10 -0
  16. data/lib/ecoportal/api/common.rb +18 -0
  17. data/lib/ecoportal/api/common/content.rb +18 -0
  18. data/lib/ecoportal/api/common/content/array_model.rb +286 -0
  19. data/lib/ecoportal/api/common/content/class_helpers.rb +146 -0
  20. data/lib/ecoportal/api/common/content/client.rb +40 -0
  21. data/lib/ecoportal/api/common/content/collection_model.rb +279 -0
  22. data/lib/ecoportal/api/common/content/doc_helpers.rb +67 -0
  23. data/lib/ecoportal/api/common/content/double_model.rb +356 -0
  24. data/lib/ecoportal/api/common/content/hash_diff_patch.rb +183 -0
  25. data/lib/ecoportal/api/common/content/string_digest.rb +27 -0
  26. data/lib/ecoportal/api/common/content/wrapped_response.rb +42 -0
  27. data/lib/ecoportal/api/v2.rb +82 -0
  28. data/lib/ecoportal/api/v2/page.rb +42 -0
  29. data/lib/ecoportal/api/v2/page/component.rb +133 -0
  30. data/lib/ecoportal/api/v2/page/component/action.rb +28 -0
  31. data/lib/ecoportal/api/v2/page/component/action_field.rb +54 -0
  32. data/lib/ecoportal/api/v2/page/component/chart_field.rb +54 -0
  33. data/lib/ecoportal/api/v2/page/component/chart_field/frequency.rb +29 -0
  34. data/lib/ecoportal/api/v2/page/component/chart_field/heatmap.rb +27 -0
  35. data/lib/ecoportal/api/v2/page/component/chart_field/indicator.rb +26 -0
  36. data/lib/ecoportal/api/v2/page/component/chart_field/multiseries.rb +31 -0
  37. data/lib/ecoportal/api/v2/page/component/chart_field/sankey.rb +27 -0
  38. data/lib/ecoportal/api/v2/page/component/chart_field/serie.rb +26 -0
  39. data/lib/ecoportal/api/v2/page/component/chart_field/series_config.rb +23 -0
  40. data/lib/ecoportal/api/v2/page/component/chart_fr_field.rb +32 -0
  41. data/lib/ecoportal/api/v2/page/component/checklist_field.rb +49 -0
  42. data/lib/ecoportal/api/v2/page/component/checklist_item.rb +25 -0
  43. data/lib/ecoportal/api/v2/page/component/date_field.rb +34 -0
  44. data/lib/ecoportal/api/v2/page/component/file.rb +16 -0
  45. data/lib/ecoportal/api/v2/page/component/files_field.rb +13 -0
  46. data/lib/ecoportal/api/v2/page/component/gauge_field.rb +36 -0
  47. data/lib/ecoportal/api/v2/page/component/gauge_stop.rb +88 -0
  48. data/lib/ecoportal/api/v2/page/component/geo_field.rb +13 -0
  49. data/lib/ecoportal/api/v2/page/component/image.rb +16 -0
  50. data/lib/ecoportal/api/v2/page/component/images_field.rb +23 -0
  51. data/lib/ecoportal/api/v2/page/component/law_field.rb +12 -0
  52. data/lib/ecoportal/api/v2/page/component/number_field.rb +12 -0
  53. data/lib/ecoportal/api/v2/page/component/people_field.rb +25 -0
  54. data/lib/ecoportal/api/v2/page/component/plain_text_field.rb +15 -0
  55. data/lib/ecoportal/api/v2/page/component/reference_field.rb +16 -0
  56. data/lib/ecoportal/api/v2/page/component/rich_text_field.rb +13 -0
  57. data/lib/ecoportal/api/v2/page/component/selection_field.rb +78 -0
  58. data/lib/ecoportal/api/v2/page/component/selection_option.rb +25 -0
  59. data/lib/ecoportal/api/v2/page/component/signature_field.rb +25 -0
  60. data/lib/ecoportal/api/v2/page/component/tag_field.rb +14 -0
  61. data/lib/ecoportal/api/v2/page/components.rb +42 -0
  62. data/lib/ecoportal/api/v2/page/section.rb +59 -0
  63. data/lib/ecoportal/api/v2/page/sections.rb +47 -0
  64. data/lib/ecoportal/api/v2/page/stage.rb +29 -0
  65. data/lib/ecoportal/api/v2/page/stages.rb +26 -0
  66. data/lib/ecoportal/api/v2/pages.rb +92 -0
  67. data/lib/ecoportal/api/v2/pages/page_stage.rb +16 -0
  68. data/lib/ecoportal/api/v2/pages/stages.rb +55 -0
  69. data/lib/ecoportal/api/v2/people.rb +31 -0
  70. data/lib/ecoportal/api/v2/registers.rb +89 -0
  71. data/lib/ecoportal/api/v2/registers/page_result.rb +21 -0
  72. data/lib/ecoportal/api/v2/registers/register.rb +37 -0
  73. data/lib/ecoportal/api/v2/registers/stage_result.rb +14 -0
  74. data/lib/ecoportal/api/v2/registers/stages_result.rb +13 -0
  75. data/lib/ecoportal/api/v2/registers/template.rb +12 -0
  76. data/lib/ecoportal/api/v2/version.rb +7 -0
  77. metadata +254 -0
@@ -0,0 +1,183 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Content
5
+ module HashDiffPatch
6
+ ID_KEYS = %w[id]
7
+ META_KEYS = %w[id patch_ver]
8
+ NO_CHANGES = "%not-changed!%"
9
+ extend Content::DocHelpers
10
+
11
+ class << self
12
+ # The `patch data` is built as follows:
13
+ # 1. detect changes that have occurred translate into one `operation` of `OP_TYPE`:
14
+ # * `changed`: meaning that the object has changed (existed and has not been removed)
15
+ # * `delete`: the object has been removed
16
+ # * `new`: the object is new
17
+ # 2. at the level of the target object of the model, the object is opened for change
18
+ # with `id` and `operation` as follows:
19
+ #
20
+ # ```json
21
+ # {
22
+ # "id": "objectID",
23
+ # "operation": "OP_TYPE",
24
+ # "data": {
25
+ # "patch_ver": "prev_patch_ver_+1",
26
+ # "property": "value",
27
+ # "...": "..."
28
+ # }
29
+ # }
30
+ # ```
31
+ # 3. the `data` property holds the specific changes of the object
32
+ # - the `patch_ver` (compulsory) is **incremental** (for data integrity)
33
+ # - the properties that have changed
34
+ # @note
35
+ # * there should not be difference between `null` and `""` (empty string)
36
+ # @param a [Hash] current hash model
37
+ # @param b [Hash] previous hash model
38
+ # @return [Hash] a `patch data`
39
+ def patch_diff(a, b)
40
+ case
41
+ when b.is_a?(Hash) && !empty?(b) && empty?(a)
42
+ patch_delete(b)
43
+ when a.is_a?(Hash) && !empty?(a) && empty?(b)
44
+ patch_new(a)
45
+ when a.is_a?(Hash) && b.is_a?(Hash)
46
+ patch_update(a, b)
47
+ when any_array?(a, b)
48
+ patch_data_array(a, b)
49
+ else
50
+ a
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def equal_values(a, b)
57
+ if a.is_a?(String) || b.is_a?(String)
58
+ return true if a.to_s.strip.empty? && b.to_s.strip.empty?
59
+ end
60
+ a == b
61
+ end
62
+
63
+ def patch_data(a, b = nil, delete: false)
64
+ {}.tap do |data_hash|
65
+ if delete
66
+ patch_ver = (a && a["patch_ver"]) || 1
67
+ data_hash["patch_ver"] = patch_ver
68
+ next
69
+ end
70
+ a.each do |key, a_value|
71
+ b_value = b[key] if b_has_key = b && b.key?(key)
72
+ is_meta_key = META_KEYS.include?(key)
73
+ skip_equals = b_has_key && equal_values(a_value, b_value)
74
+ next if is_meta_key || skip_equals
75
+ data_hash[key] = patch_diff(a_value, b_value)
76
+ data_hash.delete(key) if data_hash[key] == NO_CHANGES
77
+ end
78
+ if (data_hash.keys - META_KEYS).empty?
79
+ return NO_CHANGES
80
+ else
81
+ patch_ver = (b && b["patch_ver"]) || 1
82
+ data_hash["patch_ver"] = patch_ver
83
+ end
84
+ end
85
+ end
86
+
87
+ def patch_delete(b)
88
+ return NO_CHANGES unless b.is_a?(Hash) && id = get_id(b, exception: false)
89
+ {
90
+ "id" => id,
91
+ "operation" => "deleted",
92
+ "data" => patch_data(b, delete: true)
93
+ }
94
+ end
95
+
96
+ def patch_new(a)
97
+ return NO_CHANGES unless a.is_a?(Hash) && id = get_id(a, exception: false)
98
+ {
99
+ "id" => id,
100
+ "operation" => "new",
101
+ "data" => patch_data(a)
102
+ }
103
+ end
104
+
105
+ def patch_update(a, b)
106
+ return NO_CHANGES unless a.is_a?(Hash) && id = get_id(a, exception: false)
107
+ {
108
+ "id" => id,
109
+ "operation" => "changed",
110
+ "data" => patch_data(a, b)
111
+ }.tap do |update_hash|
112
+ return nil unless update_hash["data"] != NO_CHANGES
113
+ end
114
+ end
115
+
116
+ def patch_data_array(a, b)
117
+ original_b = b
118
+ a ||= []; b ||= []
119
+ if !nested_array?(a, b)
120
+ if a.length == b.length && (a & b).length == b.length
121
+ if original_b
122
+ NO_CHANGES
123
+ else
124
+ a
125
+ end
126
+ else
127
+ a
128
+ end
129
+ else # array with nested elements
130
+ a_ids = array_ids(a)
131
+ b_ids = array_ids(b)
132
+
133
+ del_ids = b_ids - a_ids
134
+ oth_ids = b_ids & a_ids
135
+ new_ids = a_ids - b_ids
136
+
137
+ arr_delete = del_ids.map do |id|
138
+ patch_delete(array_id_item(b, id))
139
+ end.compact
140
+
141
+ arr_update = oth_ids.map do |id|
142
+ patch_update(array_id_item(a, id), array_id_item(b, id))
143
+ end.compact
144
+
145
+ arr_new = new_ids.map do |id|
146
+ patch_new(array_id_item(a, id))
147
+ end.compact
148
+
149
+ (arr_delete.concat(arr_update).concat(arr_new)).tap do |patch_array|
150
+ # remove data with no `id`
151
+ patch_array.reject! {|item| !item.is_a?(Hash)}
152
+ return NO_CHANGES if patch_array.empty?
153
+ end
154
+ end
155
+ end
156
+
157
+ def nested_array?(*arr)
158
+ case
159
+ when arr.length > 1
160
+ arr.any? {|a| nested_array?(a)}
161
+ when arr.length == 1
162
+ arr = arr.first
163
+ arr.any? {|item| item.is_a?(Hash)}
164
+ else
165
+ false
166
+ end
167
+ end
168
+
169
+ def any_array?(a, b)
170
+ [a, b].any? {|item| item.is_a?(Array)}
171
+ end
172
+
173
+ def empty?(a)
174
+ bool = !a
175
+ bool ||= a.respond_to?(:empty?) && a.empty?
176
+ end
177
+
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,27 @@
1
+ require 'digest'
2
+
3
+ module Ecoportal
4
+ module API
5
+ module Common
6
+ module Content
7
+ module StringDigest
8
+ MAX_HASH_LABEL = 64
9
+
10
+ def indexable_label(str)
11
+ return nil unless str
12
+ lbl = str.downcase.gsub(/[^A-Za-z]+/,"-").slice(0, MAX_HASH_LABEL)
13
+ return nil unless lbl.length >= 3
14
+ lbl
15
+ end
16
+
17
+ # Calculates the Hash of the field based on label
18
+ def hash_label(str)
19
+ return nil unless lbl = indexable_label(str)
20
+ "z" + Digest::MD5.hexdigest(lbl).slice(0, 8);
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module Ecoportal
2
+ module API
3
+ module Common
4
+ module Content
5
+ class WrappedResponse < Common::WrappedResponse
6
+ include Enumerable
7
+
8
+ attr_reader :response, :result
9
+
10
+ def initialize(response, klass, key: nil)
11
+ @response = response
12
+ @klass = klass
13
+ @key = key
14
+
15
+ if @response.success?
16
+ @result =
17
+ if data.is_a?(Array)
18
+ data.map do |doc|
19
+ @klass.new(doc)
20
+ end
21
+ else
22
+ @klass.new(data)
23
+ end
24
+ end
25
+ end
26
+
27
+ def data
28
+ return @data if instance_variable_defined?(:@data)
29
+ @data = (response.body || {})["data"]
30
+ @data = @data[@key] if @key && @data
31
+ @data
32
+ end
33
+
34
+ def body
35
+ data.to_s
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,82 @@
1
+ require 'base64'
2
+
3
+ module Ecoportal
4
+ module API
5
+ # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
6
+ # @attr_reader logger [Logger] the logger.
7
+ class V2
8
+ extend Common::BaseClass
9
+ include Common::Logging
10
+
11
+ VERSION = "v2"
12
+
13
+ class << self
14
+ def v2key (ukey, gkey)
15
+ Base64.urlsafe_encode64({
16
+ organization: gkey,
17
+ user: ukey
18
+ }.to_json)
19
+ end
20
+ end
21
+
22
+ class_resolver :people_class, "Ecoportal::API::V2::People"
23
+ class_resolver :registers_class, "Ecoportal::API::V2::Registers"
24
+ class_resolver :pages_class, "Ecoportal::API::V2::Pages"
25
+
26
+ attr_reader :client, :logger
27
+
28
+ # Creates an `V2` object to scope version specific api requests.
29
+ # @note
30
+ # - You should use either `api_key` or `user_key` and `org_key`
31
+ # - The const `VERSION` determineds the api version that client will query against.
32
+ # - This means that each subclass of `V2` should define their own `VERSION` constant.
33
+ # @param api_key [String] the key version to stablish the api connection.
34
+ # @param user_key [String] the user key used for the api connection (requires `org_key`).
35
+ # @param org_key [String] the org key used for the api connection (requires `user_key`).
36
+ # @param host [String] api server domain.
37
+ # @param logger [Logger] an object with `Logger` interface to generate logs.
38
+ def initialize(api_key = nil, user_key: nil, org_key: nil, host: "live.ecoportal.com", logger: default_logger)
39
+ v2key = get_key(api_key: api_key, user_key: user_key, org_key: org_key)
40
+ @logger = logger
41
+ @client = Common::Content::Client.new(
42
+ api_key: v2key,
43
+ host: host,
44
+ version: self.class::VERSION,
45
+ logger: @logger
46
+ )
47
+ end
48
+
49
+ # Obtain specific object for people api requests.
50
+ # @return [People] an instance object ready to make people api requests.
51
+ def people
52
+ people_class.new(client)
53
+ end
54
+
55
+ # Obtain specific object for schema api requests.
56
+ # @return [Registers] an instance object ready to make registers api requests.
57
+ def registers
58
+ registers_class.new(client)
59
+ end
60
+
61
+ # Obtain specific object for pages api requests.
62
+ # @return [Pages] an instance object ready to make pages api requests.
63
+ def pages
64
+ pages_class.new(client)
65
+ end
66
+
67
+ private
68
+
69
+ def get_key(api_key: nil, user_key: nil, org_key: nil)
70
+ return self.class.v2key(user_key, org_key) if user_key && org_key
71
+ return api_key if api_key #|| ENV['X_ECOPORTAL_API_KEY']
72
+ raise "You need to provide either an api_key or user_key" unless user_key
73
+ raise "You need to provide an org_key as well (not just a user_key)" unless org_key
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+
80
+ require 'ecoportal/api/v2/people'
81
+ require 'ecoportal/api/v2/pages'
82
+ require 'ecoportal/api/v2/registers'
@@ -0,0 +1,42 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class Page < Common::Content::DoubleModel
5
+ ALLOWED_KEYS = %w[id patch_ver name template_id base_tags tags time_zone created_at updated_at can components sections stages]
6
+ passkey :id
7
+ passthrough :patch_ver
8
+ passthrough :name, :template_id
9
+ passarray :base_tags, :tags, order_matters: false
10
+ passthrough :time_zone
11
+ passdate :created_at, :updated_at, read_only: true
12
+ passthrough :can
13
+
14
+ class_resolver :components_class, "Ecoportal::API::V2::Page::Components"
15
+ class_resolver :sections_class, "Ecoportal::API::V2::Page::Sections"
16
+ class_resolver :stages_class, "Ecoportal::API::V2::Page::Stages"
17
+
18
+ embeds_many :components, enum_class: :components_class
19
+ embeds_many :sections, enum_class: :sections_class
20
+ embeds_many :stages, enum_class: :stages_class
21
+
22
+ def as_update
23
+ super.tap do |hash|
24
+ unless !hash
25
+ hash["data"].select! do |key, value|
26
+ ALLOWED_KEYS.include?(key)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ require 'ecoportal/api/v2/page/component'
38
+ require 'ecoportal/api/v2/page/components'
39
+ require 'ecoportal/api/v2/page/section'
40
+ require 'ecoportal/api/v2/page/sections'
41
+ require 'ecoportal/api/v2/page/stage'
42
+ require 'ecoportal/api/v2/page/stages'
@@ -0,0 +1,133 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class Page
5
+ class Component < Common::Content::DoubleModel
6
+ extend Ecoportal::API::Common::Content::StringDigest
7
+
8
+ class_resolver :tag_field_class, "Ecoportal::API::V2::Page::Component::TagField"
9
+ class_resolver :geo_field_class, "Ecoportal::API::V2::Page::Component::GeoField"
10
+ class_resolver :selection_field_class, "Ecoportal::API::V2::Page::Component::SelectionField"
11
+ class_resolver :date_field_class, "Ecoportal::API::V2::Page::Component::DateField"
12
+ class_resolver :number_field_class, "Ecoportal::API::V2::Page::Component::NumberField"
13
+ class_resolver :gauge_field_class, "Ecoportal::API::V2::Page::Component::GaugeField"
14
+ class_resolver :plain_text_field_class, "Ecoportal::API::V2::Page::Component::PlainTextField"
15
+ class_resolver :rich_text_field_class, "Ecoportal::API::V2::Page::Component::RichTextField"
16
+ class_resolver :people_field_class, "Ecoportal::API::V2::Page::Component::PeopleField"
17
+ class_resolver :checklist_field_class, "Ecoportal::API::V2::Page::Component::ChecklistField"
18
+ class_resolver :action_field_class, "Ecoportal::API::V2::Page::Component::ActionField"
19
+ class_resolver :files_field_class, "Ecoportal::API::V2::Page::Component::FilesField"
20
+ class_resolver :images_field_class, "Ecoportal::API::V2::Page::Component::ImagesField"
21
+ class_resolver :signature_field_class, "Ecoportal::API::V2::Page::Component::SignatureField"
22
+ class_resolver :reference_field_class, "Ecoportal::API::V2::Page::Component::ReferenceField"
23
+ class_resolver :law_field_class, "Ecoportal::API::V2::Page::Component::LawField"
24
+ class_resolver :chart_field_class, "Ecoportal::API::V2::Page::Component::ChartField"
25
+ class_resolver :chart_fr_field_class, "Ecoportal::API::V2::Page::Component::ChartFrField"
26
+
27
+ class << self
28
+ def new_doc(type: nil)
29
+ if type
30
+ type_doc = {"type" => type}
31
+ base_doc = get_class(type_doc)&.new_doc || {}
32
+ base_doc.merge!(type_doc)
33
+ end
34
+ return base_doc if base_doc&.key?("id")
35
+ (base_doc || {}).merge("id" => new_uuid)
36
+ end
37
+
38
+ def get_class(doc)
39
+ if doc.is_a?(Hash)
40
+ case doc["type"]
41
+ when "tag_field"
42
+ tag_field_class
43
+ when "geo"
44
+ geo_field_class
45
+ when "select"
46
+ selection_field_class
47
+ when "date"
48
+ date_field_class
49
+ when "number"
50
+ number_field_class
51
+ when "gauge"
52
+ gauge_field_class
53
+ when "plain_text"
54
+ plain_text_field_class
55
+ when "rich_text"
56
+ rich_text_field_class
57
+ when "people"
58
+ people_field_class
59
+ when "checklist"
60
+ checklist_field_class
61
+ when "page_action","checklist_task"
62
+ #doc["type"] = "checklist_task"
63
+ action_field_class
64
+ when "file"
65
+ files_field_class
66
+ when "image_gallery"
67
+ images_field_class
68
+ when "signature"
69
+ signature_field_class
70
+ when "cross_reference"
71
+ reference_field_class
72
+ when "law"
73
+ law_field_class
74
+ when "chart"
75
+ chart_field_class
76
+ when "frequency_rate_chart"
77
+ chart_fr_field_class
78
+ else
79
+ self
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ passkey :id
86
+ passthrough :patch_ver, :undeletable
87
+ passthrough :type, :label, :tooltip, :global_binding
88
+ passthrough :hidden, :accent, :deindex, :required
89
+ passthrough :hide_view, :hidden_on_reports, :hidden_on_mobile
90
+ passarray :refs
91
+
92
+ def ref_backend
93
+ refs.first
94
+ end
95
+
96
+ def ref
97
+ if digest = self.class.hash_label(label)
98
+ [type, digest].join(".")
99
+ end
100
+ end
101
+
102
+ def section
103
+ root.sections.find {|sec| sec.component?(id)}
104
+ end
105
+
106
+ def indexable_label
107
+ self.class.indexable_label(label)
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ require 'ecoportal/api/v2/page/component/tag_field'
117
+ require 'ecoportal/api/v2/page/component/geo_field'
118
+ require 'ecoportal/api/v2/page/component/selection_field'
119
+ require 'ecoportal/api/v2/page/component/date_field'
120
+ require 'ecoportal/api/v2/page/component/number_field'
121
+ require 'ecoportal/api/v2/page/component/gauge_field'
122
+ require 'ecoportal/api/v2/page/component/plain_text_field'
123
+ require 'ecoportal/api/v2/page/component/rich_text_field'
124
+ require 'ecoportal/api/v2/page/component/people_field'
125
+ require 'ecoportal/api/v2/page/component/checklist_field'
126
+ require 'ecoportal/api/v2/page/component/action_field'
127
+ require 'ecoportal/api/v2/page/component/files_field'
128
+ require 'ecoportal/api/v2/page/component/images_field'
129
+ require 'ecoportal/api/v2/page/component/signature_field'
130
+ require 'ecoportal/api/v2/page/component/reference_field'
131
+ require 'ecoportal/api/v2/page/component/law_field'
132
+ require 'ecoportal/api/v2/page/component/chart_field'
133
+ require 'ecoportal/api/v2/page/component/chart_fr_field'