collectionspace-client 0.14.1 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
data/examples/search.rb CHANGED
@@ -1,34 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
- require 'awesome_print'
5
- require 'collectionspace/client'
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require "awesome_print"
5
+ require "collectionspace/client"
6
6
 
7
7
  config = CollectionSpace::Configuration.new(
8
- base_uri: 'https://core.dev.collectionspace.org/cspace-services',
9
- username: 'admin@core.collectionspace.org',
10
- password: 'Administrator'
8
+ base_uri: "https://core.dev.collectionspace.org/cspace-services",
9
+ username: "admin@core.collectionspace.org",
10
+ password: "Administrator"
11
11
  )
12
12
 
13
13
  client = CollectionSpace::Client.new(config)
14
14
 
15
15
  search_args = {
16
- path: 'groups',
17
- namespace: 'groups_common',
18
- field: 'title',
16
+ path: "groups",
17
+ namespace: "groups_common",
18
+ field: "title",
19
19
  expression: "ILIKE '%D%'"
20
20
  }
21
21
 
22
- puts 'Search: %D'
22
+ puts "Search: %D"
23
23
  response = client.search(
24
24
  CollectionSpace::Search.new.from_hash(search_args),
25
- { sortBy: CollectionSpace::Search::DEFAULT_SORT }
25
+ {sortBy: CollectionSpace::Search::DEFAULT_SORT}
26
26
  )
27
27
  if response.result.success?
28
- response.parsed['abstract_common_list']['list_item'].map do |i|
29
- puts i['uri']
28
+ response.parsed["abstract_common_list"]["list_item"].map do |i|
29
+ puts i["uri"]
30
30
  end
31
31
  end
32
32
 
33
- puts 'Object and authority term searches have been moved to spec.'
34
- puts 'See helpers_spec.rb examples describing find'
33
+ puts "Object and authority term searches have been moved to spec."
34
+ puts "See helpers_spec.rb examples describing find"
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- puts 'Update password has been moved to Rake task: `cli:update_password[args...]`'
3
+ puts "Update password has been moved to Rake task: `cli:update_password[args...]`"
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CollectionSpace
4
+ # CollectionSpace batch
5
+ class Batch
6
+ def self.all
7
+ [
8
+ {
9
+ name: "Update Current Location",
10
+ notes: "Recompute the current location of Object records, based on the " \
11
+ "related Location/Movement/Inventory records. Runs on a single record " \
12
+ "or all records.",
13
+ doctype: %w[CollectionObject],
14
+ supports_single_doc: "true",
15
+ supports_doc_list: "false",
16
+ supports_group: "false",
17
+ supports_no_context: "true",
18
+ creates_new_focus: "false",
19
+ classname:
20
+ "org.collectionspace.services.batch.nuxeo.UpdateObjectLocationBatchJob"
21
+ },
22
+ {
23
+ name: "Update Inventory Status",
24
+ notes: "Set the inventory status of selected Object records. Runs on a " \
25
+ "record list only.",
26
+ doctype: %w[CollectionObject],
27
+ supports_single_doc: "false",
28
+ supports_doc_list: "true",
29
+ supports_group: "false",
30
+ supports_no_context: "false",
31
+ creates_new_focus: "false",
32
+ classname:
33
+ "org.collectionspace.services.batch.nuxeo.UpdateInventoryStatusBatchJob"
34
+ },
35
+ {
36
+ name: "Merge Authority Items",
37
+ notes: "Merge an authority item into a target, and update all " \
38
+ "referencing records. Runs on a single record only.",
39
+ doctype: %w[],
40
+ supports_single_doc: "true",
41
+ supports_doc_list: "false",
42
+ supports_group: "false",
43
+ supports_no_context: "false",
44
+ creates_new_focus: "false",
45
+ classname:
46
+ "org.collectionspace.services.batch.nuxeo.MergeAuthorityItemsBatchJob"
47
+ }
48
+ ]
49
+ end
50
+
51
+ def self.find(key, value)
52
+ all.find { |batch| batch[key] == value }
53
+ end
54
+ end
55
+ end
@@ -8,28 +8,39 @@ module CollectionSpace
8
8
 
9
9
  def initialize(config = Configuration.new)
10
10
  unless config.is_a? CollectionSpace::Configuration
11
- raise CollectionSpace::ArgumentError, 'Invalid configuration object'
11
+ raise CollectionSpace::ArgumentError, "Invalid configuration object"
12
12
  end
13
13
 
14
14
  @config = config
15
15
  end
16
16
 
17
17
  def get(path, options = {})
18
- request 'GET', path, options
18
+ request "GET", path, options
19
19
  end
20
20
 
21
21
  def post(path, payload, options = {})
22
22
  check_payload(payload)
23
- request 'POST', path, { body: payload }.merge(options)
23
+ request "POST", path, {body: payload}.merge(options)
24
+ end
25
+
26
+ def post_file(file, options = {})
27
+ file = File.expand_path(file)
28
+ raise ArgumentError, "cannot find file #{file}" unless File.exist? file
29
+
30
+ request "POST", "blobs", {
31
+ body: {
32
+ file: File.open(file)
33
+ }
34
+ }.merge(options)
24
35
  end
25
36
 
26
37
  def put(path, payload, options = {})
27
38
  check_payload(payload)
28
- request 'PUT', path, { body: payload }.merge(options)
39
+ request "PUT", path, {body: payload}.merge(options)
29
40
  end
30
41
 
31
42
  def delete(path)
32
- request 'DELETE', path
43
+ request "DELETE", path
33
44
  end
34
45
 
35
46
  private
@@ -10,10 +10,11 @@ module CollectionSpace
10
10
  page_size: 25,
11
11
  include_deleted: false,
12
12
  throttle: 0,
13
+ verbose: false,
13
14
  verify_ssl: true
14
15
  }.freeze
15
16
 
16
- attr_accessor :base_uri, :username, :password, :page_size, :include_deleted, :throttle, :verify_ssl
17
+ attr_accessor :base_uri, :username, :password, :page_size, :include_deleted, :throttle, :verbose, :verify_ssl
17
18
 
18
19
  def initialize(settings = {})
19
20
  settings = DEFAULTS.merge(settings)
@@ -4,17 +4,35 @@ module CollectionSpace
4
4
  # Helper methods for client requests
5
5
  module Helpers
6
6
  # add / update batch job
7
- def add_batch_job(name, template, data = {}, params = { pgSz: 100 })
8
- payload = Template.process(template, data)
9
- response = get('batch', { query: params })
10
- create_or_update(response, 'batch', 'name', name, payload)
7
+ def add_batch_job(name, template, data = {}, params = {pgSz: 100})
8
+ payload = Template.process(template, data)
9
+ response = get("batch", {query: params})
10
+ create_or_update(response, "batch", "name", name, payload)
11
+ end
12
+
13
+ # add / update batches and data updates
14
+ def add_batch(data = {}, params = {pgSz: 100})
15
+ payload = Template.process("batch", data)
16
+ response = get("batch", {query: params})
17
+ create_or_update(response, "batch", "name", data[:name], payload)
11
18
  end
12
19
 
13
20
  # add / update reports
14
- def add_report(data = {}, params = { pgSz: 100 })
15
- payload = Template.process('report', data)
16
- response = get('reports', { query: params })
17
- create_or_update(response, 'reports', 'name', data[:name], payload)
21
+ def add_report(data = {}, params = {pgSz: 100})
22
+ payload = Template.process("report", data)
23
+ response = get("reports", {query: params})
24
+ create_or_update(response, "reports", "name", data[:name], payload)
25
+ end
26
+
27
+ # returns Array of authority doctypes for use in setting up batches
28
+ def authority_doctypes
29
+ response = get("/servicegroups/authority")
30
+ unless response.result.success?
31
+ raise CollectionSpace::RequestError, response.result.body
32
+ end
33
+
34
+ result = response.result.parsed_response
35
+ result.dig("document", "servicegroups_common", "hasDocTypes", "hasDocType")
18
36
  end
19
37
 
20
38
  # get ALL records at path by paging through record set
@@ -24,10 +42,10 @@ module CollectionSpace
24
42
  return [] unless iterations.positive?
25
43
 
26
44
  Enumerator::Lazy.new(0...iterations) do |yielder, i|
27
- response = request('GET', path, options.merge(query: { pgNum: i }))
45
+ response = request("GET", path, options.merge(query: {pgNum: i}))
28
46
  raise CollectionSpace::RequestError, response.result.body unless response.result.success?
29
47
 
30
- items_in_page = response.parsed[list_type].fetch('itemsInPage', 0).to_i
48
+ items_in_page = response.parsed[list_type].fetch("itemsInPage", 0).to_i
31
49
  list_items = items_in_page.positive? ? response.parsed[list_type][list_item] : []
32
50
  list_items = [list_items] if items_in_page == 1
33
51
 
@@ -37,26 +55,25 @@ module CollectionSpace
37
55
 
38
56
  def count(path)
39
57
  list_type, = get_list_types(path)
40
- response = request('GET', path, query: { pgNum: 0, pgSz: 1 })
58
+ response = request("GET", path, query: {pgNum: 0, pgSz: 1})
41
59
  raise CollectionSpace::RequestError, response.result.body unless response.result.success?
42
60
 
43
- response.parsed[list_type]['totalItems'].to_i
61
+ response.parsed[list_type]["totalItems"].to_i
44
62
  end
45
63
 
46
64
  # get the tenant domain from a system required top level authority (person)
47
65
  def domain
48
- path = 'personauthorities'
49
- response = request('GET', path, query: { pgNum: 0, pgSz: 1 })
66
+ path = "personauthorities"
67
+ response = request("GET", path, query: {pgNum: 0, pgSz: 1})
50
68
  raise CollectionSpace::RequestError, response.result.body unless response.result.success?
51
69
 
52
- refname = response.parsed.dig(*get_list_types(path), 'refName')
70
+ refname = response.parsed.dig(*get_list_types(path), "refName")
53
71
  CollectionSpace::RefName.parse(refname)[:domain]
54
72
  end
55
73
 
56
74
  # find procedure or object by type and id
57
75
  # find authority/vocab term by type, subtype, and refname
58
- # rubocop:disable Metrics/ParameterLists
59
- def find(type:, value:, subtype: nil, field: nil, schema: 'common', sort: nil, operator: '=')
76
+ def find(type:, value:, subtype: nil, field: nil, schema: "common", sort: nil, operator: "=")
60
77
  service = CollectionSpace::Service.get(type: type, subtype: subtype)
61
78
  field ||= service[:term] # this will be set if it is an authority or vocabulary, otherwise nil
62
79
  field ||= service[:identifier]
@@ -64,7 +81,7 @@ module CollectionSpace
64
81
  path: service[:path],
65
82
  namespace: "#{service[:ns_prefix]}_#{schema}",
66
83
  field: field,
67
- expression: "#{operator} '#{value.gsub(/'/, '\\\\\'')}'"
84
+ expression: "#{operator} '#{value.gsub(/'/, "\\\\'")}'"
68
85
  )
69
86
  search(search_args, sortBy: CollectionSpace::Search::DEFAULT_SORT)
70
87
  end
@@ -75,74 +92,118 @@ module CollectionSpace
75
92
  # @param rel_type [String<'affects', 'hasBroader'>, nil] to be searched as `prd` value
76
93
  def find_relation(subject_csid:, object_csid:, rel_type: nil)
77
94
  if rel_type
78
- get('relations', query: { 'sbj' => subject_csid, 'obj' => object_csid, 'prd' => rel_type })
95
+ get("relations", query: {"sbj" => subject_csid, "obj" => object_csid, "prd" => rel_type})
79
96
  else
80
97
  warn(
81
98
  "No rel_type specified, so multiple types of relations between #{subject_csid} and #{object_csid} may be returned",
82
99
  uplevel: 1
83
100
  )
84
- get('relations', query: { 'sbj' => subject_csid, 'obj' => object_csid })
101
+ get("relations", query: {"sbj" => subject_csid, "obj" => object_csid})
85
102
  end
86
103
  end
87
104
 
88
105
  def get_list_types(path)
89
106
  {
90
- 'accounts' => %w[accounts_common_list account_list_item],
91
- 'relations' => %w[relations_common_list relation_list_item]
107
+ "accounts" => %w[accounts_common_list account_list_item],
108
+ "relations" => %w[relations_common_list relation_list_item]
92
109
  }.fetch(path, %w[abstract_common_list list_item])
93
110
  end
94
111
 
95
112
  def reindex_full_text(doctype, csids = [])
96
113
  if csids.any?
97
114
  run_job(
98
- 'Reindex Full Text', :reindex_full_text, :reindex_by_csids, { doctype: doctype, csids: csids }
115
+ "Reindex Full Text", :reindex_full_text, :reindex_by_csids, {doctype: doctype, csids: csids}
99
116
  )
100
117
  else
101
118
  run_job(
102
- 'Reindex Full Text', :reindex_full_text, :reindex_by_doctype, { doctype: doctype }
119
+ "Reindex Full Text", :reindex_full_text, :reindex_by_doctype, {doctype: doctype}
103
120
  )
104
121
  end
105
122
  end
106
123
 
107
- def reset_media_blob(id, url)
108
- raise CollectionSpace::ArgumentError, "Not a valid url #{url}" unless URI.parse(url).instance_of? URI::HTTPS
124
+ # @param id [String] media record's identificationNumber value
125
+ # @param url [String] blobUri value
126
+ # @param verbose [Boolean] whether to put brief report of outcome to STDOUT
127
+ # @param ensure_safe_url [Boolean] set to false if using FILE URIs or
128
+ # other non-HTTPS URIs
129
+ # @param delete_existing_blob [Boolean] set to false if you have already
130
+ # manually deleted blobs
131
+ def reset_media_blob(id:, url:, verbose: false,
132
+ ensure_safe_url: true,
133
+ delete_existing_blob: true)
134
+ if ensure_safe_url
135
+ unless URI.parse(url).instance_of? URI::HTTPS
136
+ raise CollectionSpace::ArgumentError, "Not a valid url #{url}"
137
+ end
138
+ end
109
139
 
110
- response = find(type: 'media', value: id, field: 'identificationNumber')
111
- raise CollectionSpace::RequestError, response.result.body unless response.result.success?
140
+ response = find(type: "media", value: id, field: "identificationNumber")
141
+ unless response.result.success?
142
+ if verbose
143
+ puts "#{id}\tfailure\tAPI request error: #{response.result.body}"
144
+ else
145
+ raise CollectionSpace::RequestError, response.result.body
146
+ end
147
+ end
112
148
 
113
149
  found = response.parsed
114
- total = found['abstract_common_list']['totalItems'].to_i
115
- raise CollectionSpace::NotFoundError, "Media #{id} not found" if total.zero?
116
- raise CollectionSpace::DuplicateIdFound, "Found multiple media records for #{id}" unless total == 1
150
+ total = found["abstract_common_list"]["totalItems"].to_i
151
+
152
+ if total.zero?
153
+ msg = "Media #{id} not found"
154
+ if verbose
155
+ puts "#{id}\tfailure\t#{msg}"
156
+ else
157
+ raise CollectionSpace::NotFoundError, msg
158
+ end
159
+ elsif total > 1
160
+ msg = "Found multiple media records for #{id}"
161
+ if verbose
162
+ puts "#{id}\tfailure\t#{msg}"
163
+ else
164
+ raise CollectionSpace::DuplicateIdFound, msg
165
+ end
166
+ end
117
167
 
118
- media_uri = found['abstract_common_list']['list_item']['uri']
119
- blob_csid = found['abstract_common_list']['list_item']['blobCsid']
168
+ media_uri = found["abstract_common_list"]["list_item"]["uri"]
120
169
 
121
- delete("/blobs/#{blob_csid}") if blob_csid
170
+ if delete_existing_blob
171
+ blob_csid = found["abstract_common_list"]["list_item"]["blobCsid"]
172
+ delete("/blobs/#{blob_csid}") if blob_csid
173
+ end
122
174
 
123
- payload = Template.process(:reset_media_blob, { id: id })
124
- put(media_uri, payload, query: { 'blobUri' => url })
175
+ payload = Template.process(:reset_media_blob, {id: id})
176
+ response = put(media_uri, payload, query: {"blobUri" => url})
177
+ if verbose
178
+ if response.result.success?
179
+ puts "#{id}\tsuccess\t"
180
+ else
181
+ puts "#{id}\tfailure\t#{response.parsed}"
182
+ end
183
+ else
184
+ response
185
+ end
125
186
  end
126
187
 
127
188
  def run_job(name, template, invoke_template, data = {})
128
189
  payload = Template.process(invoke_template, data)
129
- job = add_batch_job(name, template)
130
- path = job.parsed['document']['collectionspace_core']['uri']
190
+ job = add_batch_job(name, template)
191
+ path = job.parsed["document"]["collectionspace_core"]["uri"]
131
192
  post(path, payload)
132
193
  end
133
194
 
134
195
  def search(query, params = {})
135
196
  options = prepare_query(query, params)
136
- request 'GET', query.path, options
197
+ request "GET", query.path, options
137
198
  end
138
199
 
139
200
  def keyword_search(type:, value:, subtype: nil, sort: nil)
140
201
  service = CollectionSpace::Service.get(type: type, subtype: subtype)
141
- options = prepare_keyword_query(value, { sortBy: CollectionSpace::Search::DEFAULT_SORT })
142
- request 'GET', service[:path], options
202
+ options = prepare_keyword_query(value, {sortBy: CollectionSpace::Search::DEFAULT_SORT})
203
+ request "GET", service[:path], options
143
204
  end
144
205
 
145
- def service(type:, subtype: '')
206
+ def service(type:, subtype: "")
146
207
  CollectionSpace::Service.get(type: type, subtype: subtype)
147
208
  end
148
209
 
@@ -151,18 +212,18 @@ module CollectionSpace
151
212
  def create_or_update(response, path, property, value, payload)
152
213
  list_type, item_type = get_list_types(path)
153
214
  item = response.find(list_type, item_type, property, value)
154
- path = item ? "#{path}/#{item['csid']}" : path
215
+ path = item ? "#{path}/#{item["csid"]}" : path
155
216
  item ? put(path, payload) : post(path, payload)
156
217
  end
157
218
 
158
219
  def prepare_query(query, params = {})
159
220
  query_string = "#{query.namespace}:#{query.field} #{query.expression}"
160
- { query: { as: query_string }.merge(params) }
221
+ {query: {as: query_string}.merge(params)}
161
222
  end
162
223
 
163
224
  def prepare_keyword_query(query, sort = {})
164
- query_string = query.downcase.gsub(' ', '+')
165
- { query: { kw: query_string }.merge(sort) }
225
+ query_string = query.downcase.tr(" ", "+")
226
+ {query: {kw: query_string}.merge(sort)}
166
227
  end
167
228
  end
168
229
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'strscan'
3
+ require "strscan"
4
4
 
5
5
  module CollectionSpace
6
6
  # CollectionSpace RefName
7
7
  #
8
8
  # There are four patterns we need to handle:
9
- #
9
+ #
10
10
  # - urn:cspace:domain:type:name(subtype)'label' : Top level authority/vocabulary
11
11
  # - urn:cspace:domain:type:name(subtype):item:name(identifier)'label' : Authority/vocabulary term
12
12
  # - urn:cspace:domain:type:id(identifier)'label' : Collectionobject
@@ -15,38 +15,38 @@ module CollectionSpace
15
15
  attr_reader :domain, :type, :subtype, :identifier, :label
16
16
 
17
17
  def initialize(refname)
18
- @refname = refname
19
- @domain = nil
20
- @type = nil
21
- @subtype = nil
18
+ @refname = refname
19
+ @domain = nil
20
+ @type = nil
21
+ @subtype = nil
22
22
  @identifier = nil
23
- @label = nil
23
+ @label = nil
24
24
  parse
25
25
  end
26
26
 
27
27
  def parse
28
28
  scanner = StringScanner.new(@refname)
29
- scanner.skip('urn:cspace:')
30
- @domain = to_next_colon(scanner)
29
+ scanner.skip("urn:cspace:")
30
+ @domain = to_next_colon(scanner)
31
31
  @type = to_next_colon(scanner)
32
32
 
33
33
  case next_segment(scanner)
34
- when 'name'
34
+ when "name"
35
35
  set_subtype(scanner)
36
- when 'id'
36
+ when "id"
37
37
  set_identifier(scanner)
38
38
  end
39
39
 
40
40
  self
41
41
  end
42
-
42
+
43
43
  # Convenience class method, so new instance of RefName does not have to be instantiated in order to parse
44
44
  #
45
45
  # As of v0.13.1, return_class is added and defaults to nil for backward compatibility
46
46
  # Eventually this default will be deprecated, and a parsed RefName object will be returned as the default.
47
47
  # Any new code written using this method should set the return_class parameter to :refname_obj
48
48
  def self.parse(refname, return_class = nil)
49
- return_class == :refname_obj ? new(refname) : new(refname).to_h
49
+ (return_class == :refname_obj) ? new(refname) : new(refname).to_h
50
50
  end
51
51
 
52
52
  # Returns a parsed RefName object as a hash.
@@ -67,12 +67,12 @@ module CollectionSpace
67
67
  def next_segment(scanner)
68
68
  segment = scanner.check_until(/\(/)
69
69
  return nil unless segment
70
-
71
- segment.delete_suffix('(')
70
+
71
+ segment.delete_suffix("(")
72
72
  end
73
73
 
74
74
  def set_identifier(scanner)
75
- scanner.skip('id(')
75
+ scanner.skip("id(")
76
76
  @identifier = to_end_paren(scanner)
77
77
  return if scanner.eos?
78
78
 
@@ -85,30 +85,30 @@ module CollectionSpace
85
85
  end
86
86
 
87
87
  def set_subtype(scanner)
88
- scanner.skip('name(')
88
+ scanner.skip("name(")
89
89
  @subtype = to_end_paren(scanner)
90
90
 
91
91
  case next_segment(scanner)
92
92
  when nil
93
93
  set_label(scanner)
94
- when ':item:name'
94
+ when ":item:name"
95
95
  set_term_identifier(scanner)
96
96
  end
97
97
  end
98
98
 
99
99
  def set_term_identifier(scanner)
100
- scanner.skip(':item:name(')
100
+ scanner.skip(":item:name(")
101
101
  @identifier = to_end_paren(scanner)
102
102
  scanner.skip("'")
103
103
  set_label(scanner)
104
104
  end
105
-
105
+
106
106
  def to_end_paren(scanner)
107
- scanner.scan_until(/\)/).delete_suffix(')')
107
+ scanner.scan_until(/\)/).delete_suffix(")")
108
108
  end
109
109
 
110
110
  def to_next_colon(scanner)
111
- scanner.scan_until(/:/).delete_suffix(':')
111
+ scanner.scan_until(/:/).delete_suffix(":")
112
112
  end
113
113
  end
114
114
  end