Skip to content

Commit

Permalink
Merge pull request #51 from CDLUC3/v1-beta
Browse files Browse the repository at this point in the history
V1 beta
  • Loading branch information
briri authored Aug 25, 2023
2 parents 077ee90 + ebdeaeb commit 59c700f
Show file tree
Hide file tree
Showing 77 changed files with 6,061 additions and 1,938 deletions.
6 changes: 3 additions & 3 deletions config/dev/regional/dynamo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ parameters:
DynamoTableClass: 'STANDARD'
DynamoEnableContributorInsights: 'true'
DynamoEnablePointInTimeRecovery: 'false'
DynamoBillingMode: 'PROVISIONED'
DynamoReadCapacityUnits: '16'
DynamoWriteCapacityUnits: '60'
DynamoBillingMode: 'PAY_PER_REQUEST'
DynamoReadCapacityUnits: '8'
DynamoWriteCapacityUnits: '30'

hooks:
after_create:
Expand Down
2 changes: 1 addition & 1 deletion config/stg/regional/eventbridge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ parameters:

# Log and Archive retention
LogRetentionDays: '7'
ArchiveRetentionDays: '14'
ArchiveRetentionDays: '7'
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function OrgLink(props) {
if (org !== undefined) {
if (org.affiliation_id !== '') {
return (
<Link href={org.affiliation_id} label={org.name.replace(nameUrlRegex, '')} remote='true' index={idx + 'aid'}/>
<Link href={org.affiliation_id?.identifier} label={org.name.replace(nameUrlRegex, '')} remote='true' index={idx + 'aid'}/>
);
} else {
return org.name;
Expand Down
7 changes: 3 additions & 4 deletions src/landing_page/src/components/outputs/outputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,11 @@ function LicenseLink(props) {
let licenses = props?.licenses || [];
let idx = props?.index;

// DMPTool only allows for one license, so just display the first one
return (
<span key={idx + 'attr-license-s'}>
{licenses.map((license, index) => (
<Link href={license?.license_ref} label={license?.license_ref?.split('/').at(-1).replace('.json', '')}
remote='true' key={idx + 'attr-license-s' + index}/>
))}
<Link href={licenses[0]?.license_ref} label={licenses[0]?.license_ref?.split('/').at(-1).replace('.json', '')}
remote='true' key={idx + 'attr-license-s'}/>
</span>
);
}
Expand Down
8 changes: 7 additions & 1 deletion src/landing_page/src/components/works/works.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SanitizeHTML } from '../../utils';
import { Link } from '../../components/link/link';

function groupByType(works) {
Expand All @@ -17,7 +18,12 @@ function Work(props) {

return (
<li key={idx + 'li'}>
<Link href={work.identifier} remote='true' key={idx + 'li a'}/> No citation available.
{work.citation !== undefined &&
<p><SanitizeHTML html={work.citation}/></p>
}
{work.citation === undefined &&
<p><Link href={work.identifier} remote='true' key={idx + 'li a'}/> No citation available.</p>
}
</li>
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/landing_page/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ code {
margin-right: 10px;
}

main .landing-list p {
line-height: var(--dmptool-brand-line-height1);
}

// ***** List item separators ***** //
li.comma-separated a:not(:last-child) {
&::after {
Expand Down
12 changes: 8 additions & 4 deletions src/landing_page/src/pages/landing/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function Landing() {
contact: getValue(dmp, "contact", {}),
contributors: getValue(dmp, "contributor", []),
datasets: getValue(dmp, "dataset", []),
related_identifiers: filterWorks(getValue(dmp, "dmproadmap_related_identifiers", [])),
related_identifiers: getValue(dmp, "dmproadmap_related_identifiers", []),
versions: getValue(dmp, "dmphub_versions", []),
});
} else {
Expand Down Expand Up @@ -117,7 +117,11 @@ function Landing() {
}
}
function filterWorks(works) {
return works.filter((work) => work?.work_type !== 'output_management_plan' );
if (works !== undefined) {
return works.filter((work) => work?.work_type !== 'output_management_plan' );
} else {
return [];
}
}

return (
Expand All @@ -137,7 +141,7 @@ function Landing() {
<div className="t-step__landing-title">
<div className={isPublic() ? 'dmp-title' : 'dmp-title-wide'}>
<p>This page describes a data management plan written for the <FunderLink/> using the <DmptoolLink/>.
You can access this infomation as <Link href={formData.json_url} label='json here.' remote='true'/></p>
You can access this infomation as <Link href={formData.json_url} label='json' remote='true'/> here.</p>
<h1>{formData.title === '' ? formData.project_title : formData.title}</h1>
</div>
{isPublic() && narrativeUrl() && (
Expand Down Expand Up @@ -186,7 +190,7 @@ function Landing() {
}

{(formData.related_identifiers && formData.related_identifiers.length > 0) &&
<Works works={formData.related_identifiers}/>
<Works works={filterWorks(formData.related_identifiers)}/>
}

<Footer/>
Expand Down
117 changes: 117 additions & 0 deletions src/sam/functions/citer/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# frozen_string_literal: true

# Docs say that the LambdaLayer gems are found mounted as /opt/ruby/gems but an inspection
# of the $LOAD_PATH shows that only /opt/ruby/lib is available. So we add what we want here
# and indicate exactly which folders contain the *.rb files
my_gem_path = Dir['/opt/ruby/gems/**/lib/']
$LOAD_PATH.unshift(*my_gem_path)

require 'uc3-dmp-api-core'
require 'uc3-dmp-citation'
require 'uc3-dmp-cloudwatch'
require 'uc3-dmp-dynamo'
require 'uc3-dmp-event-bridge'
require 'uc3-dmp-id'

module Functions
# Lambda function that is invoked by SNS and communicates with EZID to register/update DMP IDs
class Citer
SOURCE = 'Citer'

# Parameters
# ----------
# event: Hash, required
# EventBridge Event input:
# {
# "version": "0",
# "id": "5c9a3747-293c-59d7-dcee-a2210ac034fc",
# "detail-type": "DMP change",
# "source": "dmphub.uc3dev.cdlib.net:lambda:event_publisher",
# "account": "1234567890",
# "time": "2023-02-14T16:42:06Z",
# "region": "us-west-2",
# "resources": [],
# "detail": {
# "PK": "DMP#doi.org/10.12345/ABC123",
# "SK": "VERSION#latest",
# "dmproadmap_related_identifier": {
# "work_type": "article",
# "descriptor": "references",
# "type": "doi",
# "identifier": "https://dx.doi.org/10.12345/ABCD1234"
# }
# }
# }
#
# context: object, required
# Lambda Context runtime methods and attributes
# Context doc: https://docs.aws.amazon.com/lambda/latest/dg/ruby-context.html
class << self
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def process(event:, context:)
# Setup the Logger
log_level = ENV.fetch('LOG_LEVEL', 'error')
req_id = context.aws_request_id if context.is_a?(LambdaContext)
logger = Uc3DmpCloudwatch::Logger.new(source: SOURCE, request_id: req_id, event: event, level: log_level)

# No need to validate the source and detail-type because that is done by the EventRule
detail = event.fetch('detail', {})
json = detail.is_a?(Hash) ? detail : JSON.parse(detail)
dmp_pk = json['PK']
dmp_sk = json.fetch('SK', Uc3DmpId::Helper::DMP_LATEST_VERSION)

if !dmp_pk.nil? && !dmp_sk.nil?
# Load the DMP metadata
dmp = Uc3DmpId::Finder.by_pk(p_key: dmp_pk, s_key: dmp_sk, cleanse: false, logger: logger)
if !dmp.nil?
# Get all of the related identifiers that are DOIs and are un-cited
identifiers = dmp.fetch('dmp', {}).fetch('dmproadmap_related_identifiers', [])
uncited = Uc3DmpId::Helper.citable_related_identifiers(dmp: dmp['dmp'])

if identifiers.any? && uncited.any?
existing_citations = identifiers.reject { |id| uncited.include?(id) }
processed = []
uncited.each do |identifier|
citation = Uc3DmpCitation::Citer.fetch_citation(doi: identifier['identifier']&.strip, logger: logger)
identifier['citation'] = citation unless citation.nil?
processed << identifier
end

logger.debug(message: 'Results of citation retrieval', details: processed)
dmp['dmp']['dmproadmap_related_identifiers'] = existing_citations + processed

# Remove the version info because we don't want to save it on the record
dmp['dmp'].delete('dmphub_versions')

client = Uc3DmpDynamo::Client.new
resp = client.put_item(json: dmp['dmp'], logger: logger)
end
end
end
rescue Uc3DmpId::FinderError => e
logger.error(message: e.message, details: e.backtrace)
rescue Uc3DmpCitation::CiterError => e
logger.error(message: e.message, details: e.backtrace)
rescue Uc3DmpExternalApi::ExternalApiError => e
logger.error(message: e.message, details: e.backtrace)
rescue StandardError => e
logger.error(message: e.message, details: e.backtrace)
deets = { message: "Fatal error - #{e.message}", event_details: json}
Uc3DmpApiCore::Notifier.notify_administrator(source: SOURCE, details: deets, event: event)
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

private

# Send the output to the Responder
def _respond(status:, items: [], errors: [], event: {}, params: {})
Uc3DmpApiCore::Responder.respond(
status: status, items: items, errors: errors, event: event,
page: params['page'], per_page: params['per_page']
)
end
end
end
end
1 change: 1 addition & 0 deletions src/sam/functions/delete_dmp/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def self.process(event:, context:)
_respond(status: 400, errors: [Uc3DmpId::MSG_DMP_NO_DMP_ID, e.message], event: event)
rescue StandardError => e
logger.error(message: e.message, details: e.backtrace)
Uc3DmpApiCore::Notifier.notify_administrator(source: SOURCE, details: { dmp_id: p_key }, event: event)
{ statusCode: 500, body: { errors: [Uc3DmpApiCore::MSG_SERVER_ERROR] }.to_json }
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
Expand Down
106 changes: 53 additions & 53 deletions src/sam/functions/ezid_publisher/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class EzidPublisher
# {
# "version": "0",
# "id": "5c9a3747-293c-59d7-dcee-a2210ac034fc",
# "detail-type": "DMP change",
# "source": "dmphub-dev.cdlib.org:lambda:event_publisher",
# "detail-type": "EZID update",
# "source": "dmphub.uc3dev.cdlib.net:lambda:event_publisher",
# "account": "1234567890",
# "time": "2023-02-14T16:42:06Z",
# "region": "us-west-2",
Expand All @@ -57,8 +57,7 @@ class EzidPublisher
# "dmphub_provenance_id": "PROVENANCE#example",
# "dmproadmap_links": {
# "download": "https://example.com/api/dmps/12345.pdf",
# },
# "dmphub_updater_is_provenance": false
# }
# }
# }
#
Expand Down Expand Up @@ -101,60 +100,61 @@ def process(event:, context:)
# If submissions are paused, toss the event into the EventBridge archive where it can be
# replayed at a later time
if paused
logger.info("SUBMISSIONS PAUSED: Placing event #{event['id']} in the EventBridge archive.", details: payload)
Uc3DmpEventBridge.publish(source: SOURCE, dmp: json, event_type: 'paused', logger: logger)
_respond(status: 200, items: [], event: event)
end

# Load the DMP metadata
dmp = Uc3DmpId::Finder.by_pk(p_key: dmp_pk, logger: logger)
_respond(status: 404, errors: [Uc3DmpId::MSG_DMP_NOT_FOUND], event: event) if dmp.nil?

dmp_id = dmp.fetch('dmp', {}).fetch('dmp_id', {})['identifier'].gsub(/https?:\/\//, '').gsub(ENV['DMP_ID_BASE_URL'], '')
dmp_id = dmp_id.start_with?('/') ? dmp_id[1..dmp_id.length] : dmp_id
ezid_url = Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_api_url, logger: logger)
ezid_url = ezid_url.end_with?('/') ? ezid_url : "#{ezid_url}/"
base_url = Uc3DmpApiCore::SsmReader.get_ssm_value(key: :base_url, logger: logger)

url = "#{ezid_url}id/doi:#{dmp_id}?update_if_exists=yes"
landing_page_url = "#{base_url}/dmps/#{dmp_id}"
datacite_xml = dmp_to_datacite_xml(dmp_id: dmp_id, dmp: dmp['dmp'])&.gsub(/[\r\n]/, ' ')
logger.error(message: "Failed to build DatCite XML for #{dmp_id}", details: dmp) if datacite_xml.nil?

payload = <<~TEXT
_target: #{landing_page_url}
datacite: #{datacite_xml}
TEXT
logger.debug(message: 'Prepared DMP ID metadata for EZID.', details: payload)

if skip_ezid
logger.info(message: 'EZID is currently in Debug mode.', details: payload)
_respond(status: 200, items: [], event: event)
logger.info(message: "EZID submissions paused: You can replay events from the archive when ready.",
details: json)
deets = { message: "EZID Paused", event_details: json }
Uc3DmpApiCore::Notifier.notify_administrator(source: SOURCE, details: deets, event: event)
else
# Load the DMP metadata
dmp = Uc3DmpId::Finder.by_pk(p_key: dmp_pk, logger: logger)
# _respond(status: 404, errors: [Uc3DmpId::MSG_DMP_NOT_FOUND], event: event) if dmp.nil?

dmp_id = dmp.fetch('dmp', {}).fetch('dmp_id', {})['identifier'].gsub(/https?:\/\//, '').gsub(ENV['DMP_ID_BASE_URL'], '')
dmp_id = dmp_id.start_with?('/') ? dmp_id[1..dmp_id.length] : dmp_id
ezid_url = Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_api_url, logger: logger)
ezid_url = ezid_url.end_with?('/') ? ezid_url : "#{ezid_url}/"
base_url = Uc3DmpApiCore::SsmReader.get_ssm_value(key: :base_url, logger: logger)

url = "#{ezid_url}id/doi:#{dmp_id}?update_if_exists=yes"
landing_page_url = "#{base_url}/dmps/#{dmp_id}"
datacite_xml = dmp_to_datacite_xml(dmp_id: dmp_id, dmp: dmp['dmp'])&.gsub(/[\r\n]/, ' ')
logger.error(message: "Failed to build DatCite XML for #{dmp_id}", details: dmp) if datacite_xml.nil?

payload = <<~TEXT
_target: #{landing_page_url}
datacite: #{datacite_xml}
TEXT
logger.debug(message: 'Prepared DMP ID metadata for EZID.', details: payload)

if skip_ezid
logger.info(message: 'EZID is currently in Debug mode. Skipping EZID submission', details: payload)
else
headers = {
'Content-Type': 'text/plain',
'Accept': 'text/plain'
}
auth = {
username: Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_client_id, logger: logger),
password: Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_client_secret, logger: logger)
}
logger.info(message: "Sending updated DMP ID metadata to EZID for #{dmp_id}")
logger.debug(message: "Sending DMP ID metadata to EZID for #{dmp_id}",
details: { url: url, headers: headers, payload: payload.to_s })
resp = Uc3DmpExternalApi::Client.call(url: url, method: :put, body: payload.to_s, basic_auth: auth,
additional_headers: headers, logger: logger)
end
end

headers = {
'Content-Type': 'text/plain',
'Accept': 'text/plain'
}
auth = {
username: Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_client_id, logger: logger),
password: Uc3DmpApiCore::SsmReader.get_ssm_value(key: :dmp_id_client_secret, logger: logger)
}
logger.debug(message: "Sending DMP ID metadata to EZID for #{dmp_id}",
details: { url: url, headers: headers, payload: payload.to_s })
resp = Uc3DmpExternalApi::Client.call(url: url, method: :put, body: payload.to_s, basic_auth: auth,
additional_headers: headers, logger: logger)
_respond(status: 500, errors: MSG_EZID_FAILURE, event: event) if resp.nil?

_respond(status: 200, items: [], event: event)
rescue Uc3DmpId::FinderError => e
logger.error(message: e.message, details: e.backtrace)
_respond(status: 500, errors: [e.message], event: event)
rescue Uc3DmpExternalApi::ExternalApiError => e
_respond(status: 500, errors: [e.message], event: event)
# EZID returned an error, so notify the admin. They can replay once the issue is resolved
logger.error(message: e.message, details: json)
deets = { message: "EZID returned an error #{e.message}", event_details: json}
Uc3DmpApiCore::Notifier.notify_administrator(source: SOURCE, details: deets, event: event)
rescue StandardError => e
logger.error(message: e.message, details: e.backtrace)
{ statusCode: 500, body: { errors: [Uc3DmpApiCore::MSG_SERVER_ERROR] }.to_json }
deets = { message: "Fatal error - #{e.message}", event_details: json}
Uc3DmpApiCore::Notifier.notify_administrator(source: SOURCE, details: deets, event: event)
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
Expand Down Expand Up @@ -195,7 +195,7 @@ def dmp_to_datacite_xml(dmp_id:, dmp:)
{
name: fund['name'],
type: id['type'],
identifier: %w[fundref ror].include?(id['type'].downcase) ? id['identifier'] : nil,
identifier: %w[fundref ror].include?(id['type']&.downcase) ? id['identifier'] : nil,
grant: id['grant_id'],
title: dmp['title']
}
Expand Down
Loading

0 comments on commit 59c700f

Please sign in to comment.