From 7e5faa998eccb2c546fd05d1919a505e24d316e7 Mon Sep 17 00:00:00 2001 From: jbukhari Date: Mon, 1 Apr 2024 13:07:09 -0400 Subject: [PATCH] 1377 patch: implement auth validation at subfield level (#1378) * implement auth validation at subfield level * bugfix; error handling --------- Co-authored-by: jbukhari --- dlx_rest/static/js/import.js | 44 +++++++---------- dlx_rest/static/js/jmarc.mjs | 91 ++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 67 deletions(-) diff --git a/dlx_rest/static/js/import.js b/dlx_rest/static/js/import.js index a02b0aa6..86d46318 100644 --- a/dlx_rest/static/js/import.js +++ b/dlx_rest/static/js/import.js @@ -196,34 +196,28 @@ export let importcomponent = { // records? If not, error and prevent import. let validationErrors = [] let fatalErrors = [] + if (jmarc.fields.length > 0) { jmarc.symbolInUse().then( symbolInUse => { if (symbolInUse) { this.issues += 1 validationErrors.push({"message": "Duplicate Symbol Warning: The symbol for this record is already in use."}) } - }) - for (let field of jmarc.fields) { - let auth = jmarc.authMap[field.tag] - if (auth) { - let headingTag = Object.values(auth)[0] - let thisAuth = new Jmarc("auths") - let newField = thisAuth.createField(headingTag) - for (let subfield of field.subfields) { - if (Object.keys(auth).includes(subfield.code)) { - let newSub = newField.createSubfield(subfield.code) - newSub.value = subfield.value - } + }); + + for (let field of jmarc.fields.filter(x => ! x.tag.match(/^00/))) { + for (let subfield of field.subfields.filter(x => 'xref' in x)) { + if (subfield.xref instanceof Error) { + // unresolved xrefs are set to an Error object + fatalErrors.push({"message": `Fatal: ${field.tag} ${field.toStr()}`}) } - thisAuth.authExists().then( authExists => { - if (!authExists) { - fatalErrors.push({"message": `Fatal: ${field.tag} ${field.toStr()} has an unmatched or ambiguous authority value. Create the authority record or edit this record before importing.`}) - } - }) - } + } } + this.records.push({"jmarc": jmarc, "mrk": mrk, "validationErrors": validationErrors, "fatalErrors": fatalErrors, "checked": false}) } + }).catch(error => { + throw error }) } } @@ -250,18 +244,16 @@ export let importcomponent = { } } }, - submit(record) { + async submit(record) { let binary = new Blob([record['mrk']]) let jmarc = record['jmarc'] // Only allow one click, so we don't accidentally post multiple records //e.target.classList.add("disabled") - jmarc.post_mrk(binary).then( - response => { - jmarc.recordId = response.recordId - return response - } - ) - return 0 + return jmarc.post() + .catch(error => { + // may need some user notifcation here? + throw error + }) }, filterView(e) { let values = [e.target.value] diff --git a/dlx_rest/static/js/jmarc.mjs b/dlx_rest/static/js/jmarc.mjs index 49644531..2ef390ec 100644 --- a/dlx_rest/static/js/jmarc.mjs +++ b/dlx_rest/static/js/jmarc.mjs @@ -147,6 +147,44 @@ export class Subfield { return flags } + + async detectAndSetXref() { + /* Tries to look up and set the subfield xref given the subfield value. + Sets xref to an error object if the xref is not found or ambiguous */ + + const field = this.parentField; + const jmarc = field.parentRecord; + const isAuthorityControlled = jmarc.isAuthorityControlled(field.tag, this.code); + + if (isAuthorityControlled) { + + + const searchStr = + field.subfields + .filter(x => Object.keys(authMap[jmarc.collection][field.tag]).includes(x.code)) + .map(x => `${authMap[jmarc.collection][field.tag][x.code]}__${x.code}:'${x.value}'`) + .join(" AND "); + + const xref = await fetch(Jmarc.apiUrl + "marc/auths/records?search=" + encodeURIComponent(searchStr)) + .then(response => response.json()) + .then(json => { + const recordsList = json['data']; + + if (recordsList.length === 0) { + return new Error("Unmatched heading") + } else if (recordsList.length > 1) { + return new Error("Ambiguous heading") + } else { + // get the xref from the URL + const parts = recordsList[0].split("/"); + return parts[parts.length - 1] + } + }).catch(error => {throw error}) + + this.xref = xref + return xref + } + } } class LinkedSubfield extends Subfield { @@ -203,7 +241,7 @@ export class DataField { this.parentRecord.deleteField(this); } } else if (this.tag in amap && subfield.code in amap[this.tag] && ! subfield.xref) { - throw new Error("Invalid authority-controlled value") + throw new Error(`Invalid authority-controlled value: ${this.tag} ${subfield.code} ${subfield.value}`) } } } @@ -701,31 +739,35 @@ export class Jmarc { static async fromMrk(mrk, collection="bibs") { let jmarc = new Jmarc(collection) + for (let line of mrk.split("\n")) { let match = line.match(/=(\w{3}) (.*)/) + if (match != null){ let tag = match[1] let rest = match[2] if (tag == 'LDR') { tag = '000' } - let field = jmarc.createField(tag) - if (field instanceof BibDataField || field instanceof AuthDataField) { + + let field = jmarc.createField(tag); + if (field instanceof(ControlField)) { + field.value = rest; + continue + } else { let indicators = rest.substring(0,2).replace(/\\/g, " ") - jmarc.indicators = [indicators.charAt(0), indicators.charAt(1)] + field.indicators = [indicators.charAt(0), indicators.charAt(1)] for (let subfield of rest.substring(2, rest.length).split("$")) { if (subfield.length > 0) { let code = subfield.substring(0,1) let value = subfield.substring(1, subfield.length) - if (code.length > 0 && value.length > 0) { let newSub = field.createSubfield(code) - newSub.value = value + newSub.value = value + await newSub.detectAndSetXref(); } } } - } else { - field.value = rest } } } @@ -778,39 +820,6 @@ export class Jmarc { ) } - async post_mrk(content) { - let savedResponse - const formData = new FormData() - return fetch ( - this.collectionUrl + '/records?format=mrk', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: content - } - ).then ( - response => { - savedResponse = response - return response.json() - } - ).then ( - json => { - //console.log(json) - if (savedResponse.status != 201) { - throw new Error(json['message']) - } - - this.url = json['result']; - this.recordId = parseInt(this.url.split('/').slice(-1)); - this.updateSavedState(); - - return Jmarc.get(this.collection, this.recordId) - } - ).catch ( - error => { throw new Error(error) } - ) - } - async put() { if (! this.recordId) { return Promise.reject("Can't PUT new record")