uc3-dmp-id 0.0.97 → 0.0.98

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b18b3d95b39f2da88fa37724c6fda10a5b52ca9f12c846a931d2437c2d765ba
4
- data.tar.gz: bf97c7cd424030c19c720fff5011f9f6605aa375d6c9bf0f962f4ffd10174742
3
+ metadata.gz: 6e67c682d15718449d9e74b2a63d99a25d702b26530728073df044ab40b8a743
4
+ data.tar.gz: 6e8f4859645c076262d68dd351eb9c27b978932b238734ecfa02c608b5161966
5
5
  SHA512:
6
- metadata.gz: 791986d2bf6e7382ad4f1bf84a7e4c6c7d501609a1dfe4d0d3aec0d3554496a5637231db298a01481ec18f0e1055b82c95158cd203a53a6aecbbf8ba4ddb6109
7
- data.tar.gz: f0baea87de3fb99225c98bbce5df28adf9ef32fc90593c32334d5094acf2314ea5c9d01d53088bbf1bc5529cdd9f496061aa8544b40687f7624cdde7991e59a0
6
+ metadata.gz: dc9e07e81387893b912a62d1e68a28aa35cbc6f25e8d571cd2c05b78780454b40f72cf779e57b2f6b8908ad6df25c81de2fe01f447fd1ca6945801984959f2a4
7
+ data.tar.gz: 5dc220e48c1c46e719e7f6345a2f3712248a0ccbc1591e7eb5dcf09908ab49c78b4bec9b8de5f2807947cdbb02db8272aec4d5aae4bb7420b144d5accb2f51f6
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uc3DmpId
4
+ class AsserterError < StandardError; end
5
+
6
+ class Asserter
7
+ class << self
8
+ # Add assertions to a DMP ID - this is performed by non-provenance systems
9
+ def add(updater:, dmp:, mods:, note: nil, logger: nil)
10
+ # If the updater and provenance are the same just return the :dmp as-is
11
+ return dmp if updater.nil? || !dmp.is_a?(Hash) || !mods.is_a?(Hash) ||
12
+ updater&.gsub('PROVENANCE#', '') == dmp['dmphub_provenance_id']&.gsub('PROVENANCE#', '')
13
+
14
+ contact = mods['contact']
15
+ contributor = mods.fetch('contributor', [])
16
+ project = mods.fetch('project', [])
17
+ # Return the DMP ID as-is if there are no assertable changes
18
+ return dmp if contact.nil? && contributor.empty? && project.empty?
19
+
20
+ # Clone any existing assertions on the current DMP ID so we can manipulate them
21
+ assertions = Helper.deep_copy_dmp(obj: dmp.fetch('dmphub_assertions', []))
22
+ # Return the DMP ID as-is if the assertion is already on the record
23
+ return dmp if assertions.select { |entry| entry['provenance'] == updater && entry['assertions'] == mods }
24
+
25
+ assertions << _generate_assertion(updater: updater, mods: mods, note: note)
26
+ dmp['dmphub_assertions'] = assertions.flatten
27
+ dmp
28
+ end
29
+
30
+ # Splice together assertions made while the user was updating the DMP ID
31
+ def splice(latest_version:, modified_version:, logger: nil)
32
+ # Return the modified_version if the timestamps are the same (meaning no new assertions were made while the
33
+ # user was working on the DMP ID)
34
+ return modified_version if latest_version['dmphub_updated_at'] == modified_version['dmphub_updated_at']
35
+
36
+ # Clone any existing assertions on the current DMP ID so we can manipulate them
37
+ existing_assertions = Helper.deep_copy_dmp(obj: latest_version.fetch('dmphub_assertions', []))
38
+ incoming_assertions = Helper.deep_copy_dmp(obj: modified_version.fetch('dmphub_assertions', []))
39
+ logger.debug(message: "Existing assertions", details: existing_assertions) if logger.respond_to?(:debug)
40
+ logger.debug(message: "Incoming modifications", details: incoming_assertions) if logger.respond_to?(:debug)
41
+
42
+ # Keep any assetions that were made after the dmphub_updated_at on the incoming changes
43
+ latest_version['dmphub_assertions'] = existing_assertions.select do |entry|
44
+ !entry['timestamp'].nil? && Time.parse(entry['timestamp']) > Time.parse(modified_version['dmphub_updated_at'])
45
+ end
46
+ return latest_version unless incoming_assertions.any?
47
+
48
+ # Add any of the assertions still on the incoming record back to the latest record
49
+ incoming_assertions.each { |entry| latest_version['dmphub_assertions'] << entry }
50
+ latest_version
51
+ end
52
+
53
+ private
54
+
55
+ # Generate an assertion entry. For example:
56
+ #
57
+ # {
58
+ # "id": "ABCD1234",
59
+ # "provenance": "dmphub",
60
+ # "timestamp": "2023-07-07T14:50:23+00:00",
61
+ # "note": "data received from the NIH API",
62
+ # "assertions": {
63
+ # "contact": {
64
+ # "name": "Wrong Person"
65
+ # },
66
+ # "contributor": [
67
+ # {
68
+ # "name": "Jane Doe",
69
+ # "role": ["Investigation"]
70
+ # }
71
+ # ],
72
+ # "project": [
73
+ # {
74
+ # "start": "2024-01-01T00:00:00+07:00",
75
+ # "end": "2025-12-31T23:59:59+07:00"
76
+ # }
77
+ # ],
78
+ # "funding": [
79
+ # {
80
+ # "funder_id": {
81
+ # "identifier": "https://doi.org/10.13039/501100001807",
82
+ # "type": "fundref"
83
+ # },
84
+ # "funding_status": "granted",
85
+ # "grant_id": {
86
+ # "identifier": "2019/22702-3",
87
+ # "type": "other"
88
+ # }
89
+ # }
90
+ # ]
91
+ # }
92
+ # }
93
+ def _generate_assertion(updater:, mods:, note: '')
94
+ return nil if updater.nil? || !mod.is_a?(Hash)
95
+
96
+ JSON.parse({
97
+ id: SecureRandom.hex(4).upcase,
98
+ provenance: updater.gsub('PROVENANCE#', ''),
99
+ timestamp: Time.now.iso8601,
100
+ status: 'new',
101
+ note: note,
102
+ assertions: mods
103
+ }.to_json)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -77,8 +77,7 @@ module Uc3DmpId
77
77
  end
78
78
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
79
79
 
80
- # Once the DMP has been created, we need to register it's DMP ID and download any
81
- # PDF if applicable
80
+ # Once the DMP has been created, we need to register it's DMP ID
82
81
  # -------------------------------------------------------------------------
83
82
  def _post_process(json:, logger: nil)
84
83
  return false unless json.is_a?(Hash)
@@ -126,7 +126,7 @@ module Uc3DmpId
126
126
  b = deep_copy_dmp(obj: dmp_b)
127
127
 
128
128
  # ignore some of the attributes before comparing
129
- %w[SK dmphub_modification_day dmphub_updated_at dmphub_created_at].each do |key|
129
+ %w[SK dmphub_modification_day dmphub_updated_at dmphub_created_at dmphub_assertions].each do |key|
130
130
  a['dmp'].delete(key) unless a['dmp'][key].nil?
131
131
  b['dmp'].delete(key) unless b['dmp'][key].nil?
132
132
  end
@@ -212,7 +212,7 @@ module Uc3DmpId
212
212
  return json.map { |obj| cleanse_dmp_json(json: obj) }.compact if json.is_a?(Array)
213
213
 
214
214
  cleansed = {}
215
- allowable = %w[dmphub_versions]
215
+ allowable = %w[dmphub_versions dmphub_updated_at]
216
216
  json.each_key do |key|
217
217
  next if (key.to_s.start_with?('dmphub') && !allowable.include?(key)) || %w[PK SK].include?(key.to_s)
218
218
 
@@ -1,53 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Uc3DmpId
4
6
  class UpdaterError < StandardError; end
5
7
 
6
8
  class Updater
7
9
  class << self
8
- # Update a record in the table
10
+ # Update a DMP ID
9
11
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
12
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
11
13
  # -------------------------------------------------------------------------
12
- def update(provenance:, p_key:, json: {}, logger: nil)
14
+ def update(provenance:, p_key:, json: {}, note: nil, logger: nil)
13
15
  raise UpdaterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
14
16
 
15
- dmp = Helper.parse_json(json: json)
17
+ mods = Helper.parse_json(json: json).fetch('dmp', {})
16
18
  p_key = Helper.append_pk_prefix(p_key: p_key)
19
+ logger.debug(message: "Incoming modifications for PK #{p_key}", details: mods) if logger.respond_to?(:debug)
17
20
 
18
- # Add the DMPHub specific attributes
19
- annotated = Helper.annotate_dmp_json(provenance: provenance, p_key: p_key, json: dmp['dmp'])
20
- logger.debug(message: "Incoming JSON after annotation", details: annotated) if logger.respond_to?(:debug)
21
-
22
- # fetch the existing latest version of the DMP ID
23
- client = Uc3DmpDynamo::Client.new(logger: logger)
24
- existing = Finder.by_pk(p_key: p_key, client: client, logger: logger, cleanse: false)
25
- logger.debug(message: "Existing latest record", details: existing) if logger.respond_to?(:debug)
21
+ # Fetch the latest version of the DMP ID
22
+ client = Uc3DmpDynamo::Client.new
23
+ latest_version = Finder.by_pk(p_key: p_key, client: client, logger: logger, cleanse: false)
24
+ latest_version = latest_version.nil? ? {} : latest_version.fetch('dmp', {})
25
+ logger.debug(message: "Latest version for PK #{p_key}", details: latest_version) if logger.respond_to?(:debug)
26
26
 
27
- errs = _updateable?(provenance: provenance, p_key: p_key, json: annotated)
27
+ # Verify that the DMP ID is updateable with the info passed in
28
+ errs = _updateable?(provenance: provenance, p_key: p_key, latest_version: latest_version['dmp'], mods: mods['dmp'])
29
+ logger.error(message: errs.join(', ')) if errs.is_a?(Array) && errs.any?
28
30
  raise UpdaterError, errs if errs.is_a?(Array) && errs.any?
29
31
  # Don't continue if nothing has changed!
30
- raise UpdaterError, MSG_NO_CHANGE if Helper.eql?(dmp_a: existing, dmp_b: dmp)
32
+ raise UpdaterError, MSG_NO_CHANGE if Helper.eql?(dmp_a: latest, dmp_b: mods)
31
33
 
32
- # Generate a new version of the DMP. This involves versioning the current latest version
33
- new_version = Versioner.new_version(provenance: provenance, p_key: p_key, client: client, logger: logger,
34
- dmp: dmp, latest_version: existing)
35
- logger.debug(message: "New version", details: new_version) if logger.respond_to?(:debug)
36
- raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if new_version.nil?
34
+ # Version the DMP ID record (if applicable).
35
+ owner = latest_version['dmphub_provenance_id']
36
+ updater = provenance['PK']
37
+ version = Versioner.generate_version(client: client, latest_version: latest_version, owner: owner, updater: updater,
38
+ logger: logger)
39
+ raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if version.nil?
37
40
 
38
- # Save the changes as the new latest version
39
- resp = client.put_item(json: new_version, logger: logger)
41
+ # Splice the assertions
42
+ version = _process_modifications(owner: owner, updater: updater, version: version, mods: mods, note: note,
43
+ logger: logger)
44
+
45
+ # Save the changes
46
+ resp = client.put_item(json: version, logger: logger)
40
47
  raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if resp.nil?
41
48
 
42
- # Send the updates to EZID, notify the provenance and download the PDF if applicable
43
- _post_process(provenance: provenance, json: dmp, logger: logger)
49
+ # Send the updates to EZID
50
+ _post_process(provenance: provenance, json: version, logger: logger)
51
+
44
52
  # Return the new version record
45
53
  logger.info(message: "Updated DMP ID: #{p_key}") if logger.respond_to?(:debug)
46
- Helper.cleanse_dmp_json(json: JSON.parse({ dmp: new_version }.to_json))
54
+ Helper.cleanse_dmp_json(json: JSON.parse({ dmp: version }.to_json))
47
55
  end
48
56
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
49
57
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
50
58
 
59
+ # Save a DMP ID's corresponding narrative PDF document to S3 and add the download URL for that
60
+ # document to the DMP ID's :dmpraodmap_related_identifiers array as an `is_metadata_for` relation
51
61
  def attach_narrative(provenance:, p_key:, url:, logger: nil)
52
62
  raise UpdaterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
53
63
 
@@ -59,7 +69,7 @@ module Uc3DmpId
59
69
  errs = _updateable?(provenance: provenance, p_key: p_key, json: dmp['dmp'])
60
70
  raise UpdaterError, errs if errs.is_a?(Array) && errs.any?
61
71
 
62
- # Add the DMPHub specific attributes and then add the download URl for the PDF
72
+ # Add the download URl for the PDF as a related identifier on the DMP ID record
63
73
  annotated = Helper.annotate_dmp_json(provenance: provenance, p_key: p_key, json: dmp['dmp'])
64
74
  annotated['dmproadmap_related_identifiers'] = [] if annotated['dmproadmap_related_identifiers'].nil?
65
75
  annotated['dmproadmap_related_identifiers'] << {
@@ -76,19 +86,30 @@ module Uc3DmpId
76
86
 
77
87
  private
78
88
 
79
- # Check if the DMP ID is updateable by the provenance
80
- def _updateable?(provenance:, p_key:, json:)
89
+ # Check to make sure the incoming JSON is valid, the DMP ID requested matches the DMP ID in the JSON
90
+ def _updateable?(provenance:, p_key:, latest_version: {}, mods: {})
81
91
  # Validate the incoming JSON first
82
- errs = Validator.validate(mode: 'author', json: json)
92
+ errs = Validator.validate(mode: 'author', json: JSON.parse({ dmp: mods }.to_json))
83
93
  return errs.join(', ') if errs.is_a?(Array) && errs.any? && errs.first != Validator::MSG_VALID_JSON
84
94
  # Fail if the provenance is not defined
85
95
  return [MSG_DMP_FORBIDDEN] unless provenance.is_a?(Hash) && !provenance['PK'].nil?
86
96
  # Verify that the JSON is for the same DMP in the PK
87
- dmp_id = json['dmp'].fetch('dmp_id', {})
88
- return [MSG_DMP_FORBIDDEN] unless Helper.dmp_id_to_pk(json: dmp_id) == p_key
97
+ return [MSG_DMP_FORBIDDEN] unless Helper.dmp_id_to_pk(json: mods.fetch('dmp_id', {})) == p_key
98
+ # Bail out if the DMP ID could not be found or the PKs do not match for some reason
99
+ return [MSG_DMP_UNKNOWN] if latest_version.nil? || latest_version.fetch['PK'] != p_key
100
+ end
101
+
102
+ def _process_modifications(owner:, updater:, version:, mods:, note: nil, logger: nil)
103
+ return version unless mods.is_a?(Hash) && !updater.nil?
104
+ return mods unless version.is_a?(Hash) && !owner.nil?
105
+
106
+ # Splice together any assertions that may have been made while the user was editing the DMP ID
107
+ Asserter.splice(latest_version: version, modified_version: mods, logger: logger) if owner == updater
108
+ # Attach the incoming changes as an assertion to the DMP ID since the updater is NOT the owner
109
+ Asserter.add(updater: provenance['PK'], dmp: version, mods: mods, note: note, logger: logger) if owner != updater
89
110
  end
90
111
 
91
- # Once the DMP has been updated, we need to register it's DMP ID and download any PDF if applicable
112
+ # Once the DMP has been updated, we need to update it's DOI metadata
92
113
  # -------------------------------------------------------------------------
93
114
  def _post_process(provenance:, json:, logger: nil)
94
115
  return false unless json.is_a?(Hash)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uc3DmpId
4
- VERSION = '0.0.97'
4
+ VERSION = '0.0.98'
5
5
  end
@@ -26,39 +26,42 @@ module Uc3DmpId
26
26
  client.query(args: args, logger: logger)
27
27
  end
28
28
 
29
- # Create a new version of the DMP. This involves:
30
- # - Cloning the existing `VERSION#latest` and setting it's `SK` to `VERSION=yyyy-mm-ddThh:mm:ss+zz:zz`
31
- # - Saving the new `VERSION=yyyy-mm-ddThh:mm:ss+zz:zz` item
32
- # - Splicing in the current changes onto the existing `VERSION#latest` item
33
- # - Returning the spliced `VERSION#latest` back to this method
34
- def new_version(provenance:, p_key:, client: nil, dmp:, latest_version: {}, logger: nil)
35
-
36
- puts "Provenance: #{provenance}, PK: #{p_key}"
37
- puts dmp
38
-
39
- return nil unless p_key.is_a?(String) && !p_key.strip.empty? && _versionable?(p_key: p_key, dmp: latest_version['dmp'])
40
-
41
- client = Uc3DmpDynamo::Client.new if client.nil?
42
-
43
- owner = latest_version['dmphub_provenance_id']
44
- updater = provenance['PK']
45
- prior = _generate_version(client: client, latest_version: latest_version['dmp'], owner: owner, updater: updater)
46
- return nil if prior.nil?
29
+ # Generate a snapshot of the current latest version of the DMP ID using the existing :dmphub_updated_at as
30
+ # the new SK.
31
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
32
+ def generate_version(client:, latest_version:, owner:, updater:, logger: nil)
33
+ # Only create a version if the Updater is not the Owner OR the changes have happened on a different day
34
+ mod_time = Time.parse(latest_version.fetch('dmphub_updated_at', Time.now.iso8601))
35
+ now = Time.now
36
+ if mod_time.nil? || !(now - mod_time).is_a?(Float)
37
+ logger.error(message: "#{SOURCE} unable to determine mod time: #{mod_time}") if logger.respond_to?(:debug)
38
+ return latest_version
39
+ end
47
40
 
48
- args = { owner: owner, updater: updater, base: prior, mods: dmp['dmp'], logger: logger }
49
- logger.debug(message: "#{SOURCE} record prior to splicing", details: dmp) if logger.respond_to?(:debug)
41
+ # Only allow a new version if the owner and updater are the same and it has been at least one hour since
42
+ # the last version was created
43
+ same_hour = (now - mod_time).round <= 3600
44
+ if owner != updater || (owner == updater && same_hour)
45
+ logger.debug(message: "#{SOURCE} same owner and updater? #{owner == updater}") if logger.respond_to?(:debug)
46
+ logger.debug(message: "#{SOURCE} already updated within the past hour? #{same_hour}") if logger.respond_to?(:debug)
47
+ return latest_version
48
+ end
50
49
 
51
- # If the system of provenance is making the change then just use the
52
- # new version as the base and then splice in any mods made by others
53
- # args = args.merge({ base: new_version, mods: original_version })
54
- new_version = Splicer.splice_for_owner(args) if owner == updater
55
- # Otherwise use the original version as the base and then update the
56
- # metadata owned by the updater system
57
- new_version = Splicer.splice_for_others(args) if new_version.nil?
50
+ # Make a copy of the latest_version and then update it's SK to the :dmphub_updated_at to mark it in a point of time
51
+ # We essentially make a snapshot of the record before making changes
52
+ prior = Helper.deep_copy_dmp(obj: latest_version)
53
+ prior['SK'] = "#{Helper::SK_DMP_PREFIX}#{latest_version['dmphub_updated_at'] || Time.now.iso8601}"
54
+ # Create the prior version record ()
55
+ client = client.nil? ? Uc3DmpDynamo::Client.new : client
56
+ resp = client.put_item(json: prior, logger: logger)
57
+ return nil if resp.nil?
58
58
 
59
- logger.debug(message: "#{SOURCE} record after splicing", details: new_version) if logger.respond_to?(:debug)
60
- new_version
59
+ msg = "#{SOURCE} created version PK: #{prior['PK']} SK: #{prior['SK']}"
60
+ logger.info(message: msg, details: prior) if logger.respond_to?(:debug)
61
+ # Return the latest version
62
+ latest_version
61
63
  end
64
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
62
65
 
63
66
  # Build the :dmphub_versions array and attach it to the DMP JSON
64
67
  # rubocop:disable Metrics/AbcSize
@@ -80,44 +83,6 @@ puts dmp
80
83
  json
81
84
  end
82
85
  # rubocop:enable Metrics/AbcSize
83
-
84
- private
85
-
86
- # Determine whether the specified DMP metadata is versionable - returns boolean
87
- def _versionable?(p_key:, dmp:)
88
-
89
- puts "PK MATCH? #{dmp['PK'] == p_key}, SK IS LATEST? #{dmp['SK'] == Helper::DMP_LATEST_VERSION}, DMP ID PRESENT? #{!dmp.fetch('dmp_id', {})['identifier'].nil?}"
90
-
91
- return false unless dmp.is_a?(Hash) && dmp['PK'] == p_key && dmp['SK'] == Helper::DMP_LATEST_VERSION
92
-
93
- # It's versionable if it has a DMP ID
94
- !dmp.fetch('dmp_id', {})['identifier'].nil?
95
- end
96
-
97
- # Generate a version
98
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
- def _generate_version(client:, latest_version:, owner:, updater:, logger: nil)
100
- # Only create a version if the Updater is not the Owner OR the changes have happened on a different day
101
- mod_time = Time.parse(latest_version.fetch('dmphub_updated_at', Time.now.iso8601))
102
- now = Time.now
103
- logger.debug(message: "#{SOURCE} generating version - mod_time: #{mod_time}") if logger.respond_to?(:debug)
104
- return latest_version if mod_time.nil? || !(now - mod_time).is_a?(Float)
105
-
106
- same_hour = (now - mod_time).round <= 3600
107
- logger.debug(message: "#{SOURCE} same owner and updater? #{owner == updater}") if logger.respond_to?(:debug)
108
- logger.debug(message: "#{SOURCE} already updated within the past hour? #{same_hour}") if logger.respond_to?(:debug)
109
- return latest_version if owner != updater || (owner == updater && same_hour)
110
-
111
- latest_version['SK'] = "#{Helper::SK_DMP_PREFIX}#{latest_version['dmphub_updated_at'] || Time.now.iso8601}"
112
- # Create the prior version record
113
- resp = client.put_item(json: latest_version, logger: logger)
114
- return nil if resp.nil?
115
-
116
- msg = "#{SOURCE} created version PK: #{latest_version['PK']} SK: #{latest_version['SK']}"
117
- logger.info(message: msg, details: latest_version) if logger.respond_to?(:debug)
118
- latest_version
119
- end
120
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
121
86
  end
122
87
  end
123
88
  end
data/lib/uc3-dmp-id.rb CHANGED
@@ -6,11 +6,11 @@ require 'json-schema'
6
6
 
7
7
  require 'uc3-dmp-event-bridge'
8
8
 
9
+ require 'uc3-dmp-id/asserter'
9
10
  require 'uc3-dmp-id/creator'
10
11
  require 'uc3-dmp-id/deleter'
11
12
  require 'uc3-dmp-id/finder'
12
13
  require 'uc3-dmp-id/helper'
13
- require 'uc3-dmp-id/splicer'
14
14
  require 'uc3-dmp-id/updater'
15
15
  require 'uc3-dmp-id/validator'
16
16
  require 'uc3-dmp-id/versioner'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uc3-dmp-id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.97
4
+ version: 0.0.98
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Riley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2023-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -131,13 +131,13 @@ extra_rdoc_files: []
131
131
  files:
132
132
  - README.md
133
133
  - lib/uc3-dmp-id.rb
134
+ - lib/uc3-dmp-id/asserter.rb
134
135
  - lib/uc3-dmp-id/creator.rb
135
136
  - lib/uc3-dmp-id/deleter.rb
136
137
  - lib/uc3-dmp-id/finder.rb
137
138
  - lib/uc3-dmp-id/helper.rb
138
139
  - lib/uc3-dmp-id/schemas/amend.rb
139
140
  - lib/uc3-dmp-id/schemas/author.rb
140
- - lib/uc3-dmp-id/splicer.rb
141
141
  - lib/uc3-dmp-id/updater.rb
142
142
  - lib/uc3-dmp-id/validator.rb
143
143
  - lib/uc3-dmp-id/version.rb
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Uc3DmpId
4
- class SplicerError < StandardError; end
5
-
6
- class Splicer
7
- class << self
8
- # Splice changes from other systems onto the system of provenance's updated record
9
- # --------------------------------------------------------------
10
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
11
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
12
- def splice_for_owner(owner:, updater:, base:, mods:, logger: nil)
13
- return base if owner.nil? || updater.nil? || mods.nil?
14
- return mods if base.nil?
15
-
16
- provenance_regex = /"dmphub_provenance_id":"#{Helper::PK_PROVENANCE_PREFIX}[a-zA-Z\-_]+"/
17
- others = base.to_json.match(provenance_regex)
18
- # Just return it as is if there are no mods by other systems
19
- return mods if others.nil?
20
-
21
- spliced = Helper.deep_copy_dmp(obj: base)
22
- cloned_mods = Helper.deep_copy_dmp(obj: mods)
23
-
24
- # ensure that the :project and :funding are defined
25
- spliced['project'] = [{}] if spliced['project'].nil? || spliced['project'].empty?
26
- spliced['project'].first['funding'] = [] if spliced['project'].first['funding'].nil?
27
- # get all the new funding and retain other system's funding metadata
28
- mod_fundings = cloned_mods.fetch('project', [{}]).first.fetch('funding', [])
29
- other_fundings = spliced['project'].first['funding'].reject { |fund| fund['dmphub_provenance_id'].nil? }
30
- # process funding (just attach all funding not owned by the system of provenance)
31
- spliced['project'].first['funding'] = mod_fundings
32
- spliced['project'].first['funding'] << other_fundings if other_fundings.any?
33
- return spliced if cloned_mods['dmproadmap_related_identifiers'].nil?
34
-
35
- # process related_identifiers (just attach all related identifiers not owned by the system of provenance)
36
- spliced['dmproadmap_related_identifiers'] = [] if spliced['dmproadmap_related_identifiers'].nil?
37
- mod_relateds = cloned_mods.fetch('dmproadmap_related_identifiers', [])
38
- other_relateds = spliced['dmproadmap_related_identifiers'].reject { |id| id['dmphub_provenance_id'].nil? }
39
- spliced['dmproadmap_related_identifiers'] = mod_relateds
40
- spliced['dmproadmap_related_identifiers'] << other_relateds if other_relateds.any?
41
-
42
- logger.debug(message: "JSON after splicing in changes from provenance", details: spliced) if logger.respond_to?(:debug)
43
- spliced
44
- end
45
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
46
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
47
-
48
- # Splice changes from the other system onto the system of provenance and other system's changes
49
- # --------------------------------------------------------------
50
- # rubocop:disable Metrics/AbcSize
51
- def splice_for_others(owner:, updater:, base:, mods:, logger: nil)
52
- return base if owner.nil? || updater.nil? || base.nil? || mods.nil?
53
-
54
- spliced = Helper.deep_copy_dmp(obj: base)
55
- base_funds = spliced.fetch('project', [{}]).first.fetch('funding', [])
56
- base_relateds = spliced.fetch('dmproadmap_related_identifiers', [])
57
-
58
- mod_funds = mods.fetch('project', [{}]).first.fetch('funding', [])
59
- mod_relateds = mods.fetch('dmproadmap_related_identifiers', [])
60
-
61
- # process funding
62
- spliced['project'].first['funding'] = _update_funding(
63
- updater: updater, base: base_funds, mods: mod_funds
64
- )
65
- return spliced if mod_relateds.empty?
66
-
67
- # process related_identifiers
68
- spliced['dmproadmap_related_identifiers'] = _update_related_identifiers(
69
- updater: updater, base: base_relateds, mods: mod_relateds
70
- )
71
-
72
- logger.debug(message: "JSON after splicing in changes from non-provenance", details: spliced) if logger.respond_to?(:debug)
73
- spliced
74
- end
75
- # rubocop:enable Metrics/AbcSize
76
-
77
- private
78
-
79
- # These Splicing operations could probably be refined or genericized to traverse the Hash
80
- # and apply to each object
81
-
82
- # Splice funding changes
83
- # --------------------------------------------------------------
84
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
85
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
86
- def _update_funding(updater:, base:, mods:)
87
- return base if updater.nil? || mods.nil? || mods.empty?
88
-
89
- spliced = Helper.deep_copy_dmp(obj: base)
90
- mods.each do |funding|
91
- # Ignore it if it has no status or grant id
92
- next if funding['funding_status'].nil? && funding['grant_id'].nil?
93
-
94
- # See if there is an existing funding record for the funder that's waiting on an update
95
- spliced = [] if spliced.nil?
96
- items = spliced.select do |orig|
97
- !orig['funder_id'].nil? &&
98
- orig['funder_id'] == funding['funder_id'] &&
99
- %w[applied planned].include?(orig['funding_status'])
100
- end
101
- # Always grab the most current
102
- items = items.sort { |a, b| b.fetch('dmphub_created_at', '') <=> a.fetch('dmphub_created_at', '') }
103
- item = items.first
104
-
105
- # Out with the old and in with the new
106
- spliced.delete(item) unless item.nil?
107
- # retain the original name
108
- funding['name'] = item['name'] unless item.nil?
109
- item = Helper.deep_copy_dmp(obj: funding)
110
-
111
- item['funding_status'] == funding['funding_status'] unless funding['funding_status'].nil?
112
- spliced << item if funding['grant_id'].nil?
113
- next if funding['grant_id'].nil?
114
-
115
- item['grant_id'] = funding['grant_id']
116
- item['funding_status'] = funding['grant_id'].nil? ? 'rejected' : 'granted'
117
-
118
- # Add the provenance to the entry
119
- item['grant_id']['dmphub_provenance_id'] = updater
120
- item['grant_id']['dmphub_created_at'] = Time.now.iso8601
121
- spliced << item
122
- end
123
- spliced
124
- end
125
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
126
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
127
-
128
- # Splice related identifier changes
129
- # --------------------------------------------------------------
130
- def _update_related_identifiers(updater:, base:, mods:)
131
- return base if updater.nil? || mods.nil? || mods.empty?
132
-
133
- # Remove the updater's existing related identifiers and replace with the new set
134
- spliced = base.nil? ? [] : Helper.deep_copy_dmp(obj: base)
135
- spliced = spliced.reject { |related| related['dmphub_provenance_id'] == updater }
136
- # Add the provenance to the entry
137
- updates = mods.nil? ? [] : Helper.deep_copy_dmp(obj: mods)
138
- updates = updates.map do |related|
139
- related['dmphub_provenance_id'] = updater
140
- related
141
- end
142
- spliced + updates
143
- end
144
- end
145
- end
146
- end