ecoportal-api-v2 0.8.4

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 (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'