ecoportal-api-v2 1.1.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ module Ecoportal
3
3
  class V2
4
4
  class Page < Common::Content::DoubleModel
5
5
  ALLOWED_KEYS = %w[
6
- id patch_ver name template_id
6
+ id external_id patch_ver name template_id
7
7
  base_tags tags
8
8
  time_zone created_at updated_at
9
9
  components sections stages
@@ -14,6 +14,7 @@ module Ecoportal
14
14
  ].freeze
15
15
 
16
16
  passkey :id
17
+ passthrough :external_id
17
18
  passforced :patch_ver, default: 1
18
19
  passthrough :name, :template_id
19
20
  passarray :base_tags, :tags, order_matters: false
@@ -41,12 +42,13 @@ module Ecoportal
41
42
 
42
43
  def as_update
43
44
  super.tap do |hash|
44
- if hash
45
- hash["data"].select! do |key, value|
46
- ALLOWED_KEYS.include?(key)
47
- end
48
- return nil if (hash["data"].keys - ["patch_ver"]).empty?
45
+ next unless hash
46
+
47
+ hash["data"].select! do |key, _value|
48
+ ALLOWED_KEYS.include?(key)
49
49
  end
50
+
51
+ return nil if (hash["data"].keys - ["patch_ver"]).empty?
50
52
  end
51
53
  end
52
54
 
@@ -57,32 +59,33 @@ module Ecoportal
57
59
  # @return [String] with feedback, if for this page instance, there are any of:
58
60
  # 1. components multi-section (fields belonging to more than one section)
59
61
  def validate
60
- msg = ""
61
- if (multi = components.multi_section).length.positive?
62
- msg += "There are fields attached to more than one section:\n • "
63
- msg += multi.map do |fld|
64
- fld.label
65
- end.join("\n • ") + "\n"
66
- end
62
+ multi = components.multi_section
63
+ return true unless multi.length.positive?
67
64
 
68
- msg.empty?? true : msg
65
+ msg = ""
66
+ msg << "There are fields attached to more than one section:"
67
+ msg << "\n • "
68
+ msg << multi.map(&:label).join("\n • ")
69
+ msg << "\n"
70
+ msg
69
71
  end
70
72
 
71
73
  private
72
74
 
73
75
  def _doc_bug_fix(hash)
74
- hash.tap do |hash|
76
+ hash.tap do
75
77
  _fix_doc(hash["stages"], "flow_node_ids", "section_ids") if hash.key?("stages")
76
- if hash.key?("sections")
77
- _fix_doc(hash["sections"], "membrane_ids", "component_ids")
78
- _fix_doc(hash["sections"], "left_membrane_ids", "left_component_ids")
79
- _fix_doc(hash["sections"], "right_membrane_ids", "right_component_ids")
80
- end
78
+
79
+ next unless hash.key?("sections")
80
+
81
+ _fix_doc(hash["sections"], "membrane_ids", "component_ids")
82
+ _fix_doc(hash["sections"], "left_membrane_ids", "left_component_ids")
83
+ _fix_doc(hash["sections"], "right_membrane_ids", "right_component_ids")
81
84
  end
82
85
  end
83
86
 
84
87
  def _fix_doc(value, source, dest)
85
- value.tap do |value|
88
+ value.tap do
86
89
  case value
87
90
  when Array
88
91
  value.each {|v| _fix_doc(v, source, dest)}
@@ -91,7 +94,7 @@ module Ecoportal
91
94
  value[dest] = value[source]
92
95
  value.delete(source)
93
96
  end
94
- else
97
+ else # rubocop:disable Style/EmptyElse
95
98
  # Do nothing!
96
99
  end
97
100
  end
@@ -18,11 +18,10 @@ module Ecoportal
18
18
 
19
19
  # @return [String] unique id
20
20
  def uid
21
- if mould_counter? && counter = mould_counter
22
- counter.render
23
- else
24
- id
25
- end
21
+ return id unless mould_counter?
22
+ return id unless (counter = mould_counter)
23
+
24
+ counter.render
26
25
  end
27
26
 
28
27
  # @return [String] `id` of the stage we got the data of.
@@ -32,43 +31,46 @@ module Ecoportal
32
31
 
33
32
  # @return [Ecoportal::API::V2::Page::Stage]
34
33
  def current_stage
35
- if stage_id = current_stage_id
36
- stages[stage_id]
37
- end
34
+ return false unless (stage_id = current_stage_id)
35
+
36
+ stages[stage_id]
38
37
  end
39
38
 
40
39
  # @return [String] with feedback, if for this page instance, there are any of:
41
40
  # 1. orphaned components (fields not belonging to any section)
42
41
  # 2. orphaned sections (sections not belonging to any stage)
43
- def validate
42
+ def validate # rubocop:disable Metrics/AbcSize
44
43
  msg = super
45
44
  msg = "" unless msg.is_a?(String)
46
45
 
47
46
  orphans = components.unattached.select {|comp| comp.global_binding.to_s.strip.empty?}
48
- if orphans.length > 0
49
- msg += "There are fields not attached to any sections:\n • "
50
- msg += orphans.map do |fld|
51
- fld.label
52
- end.join("\n • ") + "\n"
47
+ if orphans.length.positive?
48
+ msg << "There are fields not attached to any sections:"
49
+ msg << "\n • "
50
+ msg << orphans.map(&:label).join("\n • ")
51
+ msg << "\n"
53
52
  end
54
53
 
55
- if (orphans = sections.unattached).length > 0
56
- msg += "There are sections not attached to any stage:\n • "
57
- msg += orphans.map do |sec|
54
+ if (orphans = sections.unattached).length.positive?
55
+ msg << "There are sections not attached to any stage:"
56
+ msg << "\n • "
57
+ msg << orphans.map do |sec|
58
58
  "'#{sec.heading}' (#{sec.id})"
59
- end.join("\n • ") + "\n"
59
+ end.join("\n • ")
60
+ msg << "\n"
60
61
  end
62
+
61
63
  msg.empty?? true : msg
62
64
  end
63
65
 
64
66
  def mark_as_submit
65
67
  doc["submitted"] = true
66
- doc["type"] = "complete_page"
68
+ doc["type"] = "complete_page"
67
69
  end
68
70
 
69
71
  def mark_as_sign_off
70
72
  doc["sign_off"] = true
71
- doc["type"] = "review_page"
73
+ doc["type"] = "review_page"
72
74
  end
73
75
  end
74
76
  end
@@ -1,16 +1,17 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class V2
4
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
4
+ # @attr_reader client [Common::Client] a `Common::Client` object that
5
+ # holds the configuration of the api connection.
5
6
  class Pages
6
- STAGE_REX = /stages\/(?<sid>.*)/
7
+ STAGE_REX = /stages\/(?<sid>.*)/.freeze
7
8
  extend Common::BaseClass
8
9
  include Common::Content::DocHelpers
9
10
 
10
11
  class_resolver :stages_class, "Ecoportal::API::V2::Pages::Stages"
11
- class_resolver :page_class, "Ecoportal::API::V2::Page"
12
- class_resolver :page_stage_class, "Ecoportal::API::V2::Pages::PageStage"
13
- class_resolver :create_page_response_class, "Ecoportal::API::V2::Pages::PageCreateResponse"
12
+ class_resolver :page_class, "Ecoportal::API::V2::Page"
13
+ class_resolver :page_stage_class, "Ecoportal::API::V2::Pages::PageStage"
14
+ class_resolver :create_page_response_class, "Ecoportal::API::V2::Pages::PageCreateResponse"
14
15
 
15
16
  attr_reader :client
16
17
 
@@ -35,16 +36,19 @@ module Ecoportal
35
36
  # @return [Ecoportal::API::V2::Page, Ecoportal::API::V2::Pages::PageStage] the target page.
36
37
  def get(id, stage_id: nil)
37
38
  return stages.get(id: id, stage_id: stage_id) if stage_id
39
+
38
40
  id = get_id(id)
39
41
  response = client.get("/pages/#{CGI.escape(id)}")
40
42
  wrapped = Common::Content::WrappedResponse.new(response, page_class)
41
43
 
42
44
  return wrapped.result if wrapped.success?
43
- if (response.status == 302) && (url = response.body["data"])
44
- if stage_id = url_to_stage_id(url)
45
- return stages.get(id: id, stage_id: stage_id)
46
- end
47
- end
45
+
46
+ url = nil
47
+ url = response.body["data"] if response.status == 302
48
+ stage_id = url_to_stage_id(url) unless url.nil?
49
+
50
+ return stages.get(id: id, stage_id: stage_id) if stage_id
51
+
48
52
  raise "Could not get page #{id} - Error #{response.status}: #{response.body}"
49
53
  end
50
54
 
@@ -56,7 +60,8 @@ module Ecoportal
56
60
  body = get_body(doc) # , level: "page"
57
61
  # Launch only if there are changes
58
62
  raise "Missing page object" unless body && body["page"]
59
- id = get_id(doc)
63
+
64
+ id = get_id(doc)
60
65
  client.patch("/pages/#{CGI.escape(id)}", data: body)
61
66
  end
62
67
 
@@ -81,9 +86,9 @@ module Ecoportal
81
86
  body = get_body(doc).tap do |hash|
82
87
  unless hash["page"]
83
88
  hash["page"] = {
84
- "id" => "111111111111111111111111",
89
+ "id" => "111111111111111111111111",
85
90
  "operation" => "changed",
86
- "data" => {"patch_ver" => 0}
91
+ "data" => {"patch_ver" => 0}
87
92
  }
88
93
  end
89
94
  end
@@ -98,7 +103,6 @@ module Ecoportal
98
103
  def url_to_stage_id(url)
99
104
  (matches = url.match(STAGE_REX)) && matches[:sid]
100
105
  end
101
-
102
106
  end
103
107
  end
104
108
  end
@@ -1,9 +1,9 @@
1
1
  module Ecoportal
2
2
  module API
3
3
  class V2
4
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
4
+ # @attr_reader client [Common::Client] a `Common::Client` object that
5
+ # holds the configuration of the api connection.
5
6
  class People < API::Internal::People
6
-
7
7
  def batch
8
8
  unavailable_method!(__method__)
9
9
  end
@@ -24,7 +24,6 @@ module Ecoportal
24
24
  def unavailable_method!(str)
25
25
  raise "Unavailable method '#{str}' for api '#{VERSION}'"
26
26
  end
27
-
28
27
  end
29
28
  end
30
29
  end
@@ -2,26 +2,29 @@ require 'base64'
2
2
  module Ecoportal
3
3
  module API
4
4
  class V2
5
- # @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
5
+ # @attr_reader client [Common::Client] a `Common::Client` object that
6
+ # holds the configuration of the api connection.
6
7
  class Registers
7
8
  extend Common::BaseClass
8
9
  include Enumerable
9
10
  include Common::Content::DocHelpers
10
11
 
11
- class_resolver :register_class, "Ecoportal::API::V2::Registers::Register"
12
+ class_resolver :register_class, "Ecoportal::API::V2::Registers::Register"
12
13
  class_resolver :register_search_result_class, "Ecoportal::API::V2::Registers::PageResult"
13
14
  class_resolver :register_search_results, "Ecoportal::API::V2::Registers::SearchResults"
14
15
 
15
16
  attr_reader :client
16
17
 
17
- # @param client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
18
+ # @param client [Common::Client] a `Common::Client` object that
19
+ # holds the configuration of the api connection.
18
20
  # @return [Registers] an instance object ready to make registers api requests.
19
21
  def initialize(client)
20
22
  @client = client
21
23
  end
22
24
 
23
25
  def each(params: {}, &block)
24
- return to_enum(:each) unless block
26
+ return to_enum(:each, params: params) unless block
27
+
25
28
  get.each(&block)
26
29
  end
27
30
 
@@ -29,7 +32,11 @@ module Ecoportal
29
32
  # @return [Enumerable<Register>] an `Enumerable` with all schemas already wrapped as `Register` objects.
30
33
  def get
31
34
  response = client.get("/templates")
32
- Common::Content::WrappedResponse.new(response, register_class, key: "registers")
35
+ Common::Content::WrappedResponse.new(
36
+ response,
37
+ register_class,
38
+ key: "registers"
39
+ )
33
40
  end
34
41
 
35
42
  # Gets all the oozes/pages of `register_id` matching the `options`
@@ -41,18 +48,21 @@ module Ecoportal
41
48
  # @yield [result] something to do with search page-result.
42
49
  # @yieldparam result [Ecoportal::V2::Registers::PageResult] a page result.
43
50
  # @return [Ecoportal::API::V2::Registers, Ecoportal::API::V2::Registers::SearchResults]
44
- def search(register_id, options = {})
51
+ def search(register_id, options = {}) # rubocop:disable Metrics/AbcSize
45
52
  only_first = options.delete(:only_first)
46
53
  options = build_options(options)
47
54
 
48
55
  if only_first
49
56
  response = client.get("/registers/#{register_id}/search", params: options)
50
57
  raise "Request failed - Status #{response.status}: #{response.body}" unless response.success?
58
+
51
59
  return register_search_results.new(response.body["data"])
52
60
  end
53
61
 
54
62
  cursor_id = nil
55
- results = 0; total = nil
63
+ results = 0
64
+ total = nil
65
+
56
66
  loop do
57
67
  options.update(cursor_id: cursor_id) if cursor_id
58
68
  response = client.get("/registers/#{register_id}/search", params: options)
@@ -61,12 +71,14 @@ module Ecoportal
61
71
  data = response.body["data"]
62
72
  total ||= data["total"]
63
73
  if total != data["total"]
64
- msg = "Change of total in search results. Probably due to changes that affect the filter (register: #{register_id}):"
74
+ msg = "Change of total in search results. "
75
+ msg << "Probably due to changes that affect the filter"
76
+ msg << "(register: #{register_id}):"
65
77
  print_search_status(msg, total, results, cursor_id, data, options)
66
78
  #total = data["total"]
67
79
  end
68
80
 
69
- unless total == 0
81
+ unless total&.zero?
70
82
  results += data["results"].length
71
83
  print_progress(results, total)
72
84
  end
@@ -77,12 +89,15 @@ module Ecoportal
77
89
  end
78
90
 
79
91
  break if total <= results
92
+
80
93
  unless data["cursor_id"]
81
- msg = "Possible error... finishing search for lack of cursor_id in response:"
94
+ msg = "Possible error... finishing search for lack of cursor_id in response:"
82
95
  print_search_status(msg, total, results, cursor_id, data, options)
83
96
  end
97
+
84
98
  break unless (cursor_id = data["cursor_id"])
85
99
  end
100
+
86
101
  self
87
102
  end
88
103
 
@@ -96,14 +111,14 @@ module Ecoportal
96
111
  options.each do |key, value|
97
112
  if key == :filters && value.any?
98
113
  ret[key] = {filters: value}.to_json
99
- else
100
- ret[key] = value if key
114
+ elsif key
115
+ ret[key] = value
101
116
  end
102
117
  end
103
118
  end
104
119
  end
105
120
 
106
- def print_search_status(msg, total, results, cursor_id, data, options)
121
+ def print_search_status(msg, total, results, cursor_id, data, options) # rubocop:disable Metrics/ParameterLists
107
122
  msg += "\n"
108
123
  msg += " • Original total: #{total}\n"
109
124
  msg += " • Current total: #{data&.dig("total")}\n"
@@ -0,0 +1,27 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class S3
5
+ class Data < Common::Content::DoubleModel
6
+ passthrough :s3_endpoint, :AWSAccessKeyId
7
+ passthrough :policy, :signature
8
+ passthrough :user_tmpdir
9
+
10
+ def x_amz_server_side_encryption
11
+ doc['x-amz-server-side-encryption']
12
+ end
13
+
14
+ def [](key)
15
+ doc[key.to_s]
16
+ end
17
+
18
+ def user_id
19
+ return unless user_tmpdir
20
+
21
+ user_tmpdir.split('uploads/').last
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,110 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class S3
5
+ class Files
6
+ # Class service to upload multiple files in one go
7
+ class BatchUpload
8
+ include Ecoportal::API::Common::Concerns::Benchmarkable
9
+ include Ecoportal::API::Common::Concerns::Threadable
10
+
11
+ SIZE_UNITS = 1_024 # KB
12
+ MIN_UNIT = 1
13
+
14
+ attr_reader :files, :files_api
15
+
16
+ FileResult = Struct.new(:file) do
17
+ attr_accessor :poll, :s3_file_reference, :error
18
+
19
+ def error?
20
+ !error.nil?
21
+ end
22
+
23
+ def success?
24
+ poll&.success?
25
+ end
26
+
27
+ def container_id
28
+ return unless success?
29
+ poll.container_id
30
+ end
31
+ end
32
+
33
+ # @param files [Array<String>] the files to be uploaded
34
+ # @param files_api [API::S3::Files] the api object.
35
+ def initialize(files, files_api:)
36
+ @files = [files].flatten.compact
37
+ @files_api = files_api
38
+ end
39
+
40
+ # Do the actual upload of the file
41
+ def upload!(benchmarking: false, threads: 1, **kargs) # rubocop:disable Metrics/AbcSize
42
+ bench = benchmarking && benchmark_enabled?
43
+ bench_mth = "#{self.class}##{__method__}"
44
+ thr_children = []
45
+ benchmarking("#{bench_mth}.#{files.count}_files", print: bench) do
46
+ with_preserved_thread_globals(report: false) do
47
+ files.each do |file|
48
+ new_thread(thr_children, max: threads) do
49
+ file_results << (result = FileResult.new(file))
50
+
51
+ s3_ref = nil
52
+
53
+ kbytes = bench ? file_size(file) : 1
54
+
55
+ benchmarking("#{bench_mth}.s3_upload (KB)", units: kbytes, print: bench) do
56
+ s3_ref = result.s3_file_reference = s3_api.upload_file(file)
57
+ end
58
+
59
+ benchmarking("##{bench_mth}.ep_poll (KB)", units: kbytes, print: bench) do
60
+ files_api.poll!(s3_ref, **kargs) do |poll|
61
+ result.poll = poll
62
+ end
63
+ end
64
+ yield(result) if block_given?
65
+ rescue *rescued_errors => err # each_file
66
+ result.error = err
67
+ yield(result) if block_given?
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ thr_children.each(&:join)
74
+ file_results
75
+ ensure
76
+ puts benchmark_summary(:all) if bench
77
+ end
78
+
79
+ private
80
+
81
+ def file_size(file, bytes: SIZE_UNITS)
82
+ return 1 unless File.exist?(file)
83
+
84
+ units = (File.size(file) / bytes).round(10)
85
+ [MIN_UNIT, units].max
86
+ end
87
+
88
+ def s3_api
89
+ files_api.s3_api
90
+ end
91
+
92
+ def file_results
93
+ @file_results ||= []
94
+ end
95
+
96
+ def rescued_errors
97
+ @rescued_errors ||= [
98
+ Ecoportal::API::V2::S3::MissingLocalFile,
99
+ Ecoportal::API::V2::S3::CredentialsGetFailed,
100
+ Ecoportal::API::V2::S3::Files::FailedPollRequest,
101
+ Ecoportal::API::V2::S3::Files::CantCheckStatus,
102
+ Ecoportal::API::Errors::TimeOut
103
+ ].freeze
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,82 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class S3
5
+ class Files
6
+ class Poll < Common::Content::DoubleModel
7
+ passthrough :poll_url
8
+ embeds_one :status, nullable: true, klass: "Ecoportal::API::V2::S3::Files::PollStatus"
9
+
10
+ def poll_id
11
+ return @poll_id if instance_variable_defined?(:@poll_id)
12
+ return @poll_id = nil unless (uri = parsed_poll_url)
13
+
14
+ @poll_id = uri.path.split('/poll/').last
15
+ end
16
+ alias_method :id, :poll_id
17
+
18
+ # The final File eP container id
19
+ def container_id
20
+ return unless status?
21
+
22
+ status.container_id
23
+ end
24
+
25
+ def status?
26
+ !doc['status'].nil?
27
+ end
28
+
29
+ def status=(value)
30
+ case value
31
+ when NilClass
32
+ doc["status"] = nil
33
+ when Ecoportal::API::V2::S3::Files::PollStatus
34
+ doc["status"] = JSON.parse(value.to_json)
35
+ when Hash
36
+ doc["status"] = value
37
+ else
38
+ # TODO
39
+ raise "Invalid set on status: Need nil, PollStatus or Hash; got #{value.class}"
40
+ end
41
+
42
+ remove_instance_variable("@status") if defined?(@status)
43
+ end
44
+
45
+ def pending?
46
+ !complete?
47
+ end
48
+
49
+ def complete?
50
+ status&.complete?
51
+ end
52
+
53
+ def success?
54
+ status&.success?
55
+ end
56
+
57
+ def failed?
58
+ status&.failed?
59
+ end
60
+
61
+ def timeout?
62
+ status&.timeout?
63
+ end
64
+
65
+ private
66
+
67
+ def parsed_poll_url
68
+ return nil unless poll_url
69
+
70
+ require 'uri'
71
+ URI.parse(poll_url)
72
+ end
73
+
74
+ def callbacks
75
+ @callbacks ||= []
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,52 @@
1
+ module Ecoportal
2
+ module API
3
+ class V2
4
+ class S3
5
+ class Files
6
+ class PollStatus < Common::Content::DoubleModel
7
+ class FileContainer < Common::Content::DoubleModel
8
+ passkey :id
9
+ passthrough :file_container_id # same as :id :)
10
+ passthrough :name, :label
11
+ passthrough :file_size, :content_type
12
+ passthrough :tags
13
+ passboolean :archived
14
+ passthrough :active_person, :user_name
15
+ passdate :created_at, :updated_at, :file_update_at
16
+ end
17
+
18
+ # PollStatus
19
+ passthrough :status
20
+ embeds_one :file, klass: FileContainer
21
+
22
+ def container_id
23
+ return unless success?
24
+
25
+ file&.id
26
+ end
27
+
28
+ def complete?
29
+ success? || failed?
30
+ end
31
+
32
+ def timeout?
33
+ statut == 'timeout'
34
+ end
35
+
36
+ def pending?
37
+ status == "pending"
38
+ end
39
+
40
+ def success?
41
+ status == "success"
42
+ end
43
+
44
+ def failed?
45
+ status == "failed"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end