uc3-dmp-id 0.0.97 → 0.0.99

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b18b3d95b39f2da88fa37724c6fda10a5b52ca9f12c846a931d2437c2d765ba
4
- data.tar.gz: bf97c7cd424030c19c720fff5011f9f6605aa375d6c9bf0f962f4ffd10174742
3
+ metadata.gz: e3bcba612098c1d0acd9a553f25549a198bbefb0f28c9c0ef166038382c17c07
4
+ data.tar.gz: 5ba9be2055b9c39a4c887930b5250860cd9dc986ddf4d0248d25e786f932c9fe
5
5
  SHA512:
6
- metadata.gz: 791986d2bf6e7382ad4f1bf84a7e4c6c7d501609a1dfe4d0d3aec0d3554496a5637231db298a01481ec18f0e1055b82c95158cd203a53a6aecbbf8ba4ddb6109
7
- data.tar.gz: f0baea87de3fb99225c98bbce5df28adf9ef32fc90593c32334d5094acf2314ea5c9d01d53088bbf1bc5529cdd9f496061aa8544b40687f7624cdde7991e59a0
6
+ metadata.gz: f3b3e728a5c532fb024dddd69a366fc9867e3a05856562c863b89059a38a320d63e70b9d0241249b2a8d419bf8898e894bd93617c681b3601ea82e701433f558
7
+ data.tar.gz: 5401aba365595519141ea7f2115366bdaf53b47183ee2fcd3ecba219c9625f8148cc1042faa8f67737f4fb750d60dfe62d2a334ed8e7449a02b8edbd2023744f
@@ -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_version, 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.99'
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.99
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