diff --git a/src/js/collections/DataPackage.js b/src/js/collections/DataPackage.js index 405b9a9f1..e0911e30d 100644 --- a/src/js/collections/DataPackage.js +++ b/src/js/collections/DataPackage.js @@ -12,7 +12,7 @@ define([ "models/DataONEObject", "models/metadata/ScienceMetadata", "models/metadata/eml211/EML211", -], function ( +], ( $, _, Backbone, @@ -24,17 +24,17 @@ define([ DataONEObject, ScienceMetadata, EML211, -) { +) => { /** - * @class DataPackage - * @classdesc A DataPackage represents a hierarchical collection of + * @class DataPackage + * @classdesc A DataPackage represents a hierarchical collection of packages, metadata, and data objects, modeling an OAI-ORE RDF graph. TODO: incorporate Backbone.UniqueModel - * @classcategory Collections - * @name DataPackage - * @extends Backbone.Collection - * @constructor - */ + * @classcategory Collections + * @name DataPackage + * @augments Backbone.Collection + * @class + */ var DataPackage = Backbone.Collection.extend( /** @lends DataPackage.prototype */ { /** @@ -58,7 +58,8 @@ define([ */ transferQueue: [], - /** A flag ued for the package's edit status. Can be + /** + * A flag ued for the package's edit status. Can be * set to false to 'lock' the package * @type {boolean} */ @@ -76,7 +77,8 @@ define([ */ packageModel: null, - /** The science data identifiers associated with this + /** + * The science data identifiers associated with this * data package (from cito:documents), mapped to the science metadata * identifier that documents it * Not to be changed after initial fetch - this is to keep track of the relationships in their original state @@ -84,7 +86,8 @@ define([ */ originalIsDocBy: {}, - /** An array of ids that are aggregated in the resource map on the server. + /** + * An array of ids that are aggregated in the resource map on the server. * Taken from the original RDF XML that was fetched from the server. * Used for comparing the original aggregation with the aggregation of this collection. * @type {string[]} @@ -123,7 +126,8 @@ define([ */ filterModel: null, - /** Define the namespaces used in the RDF XML + /** + * Define the namespaces used in the RDF XML * @type {object} */ namespaces: { @@ -159,8 +163,8 @@ define([ numSaves: 0, // Constructor: Initialize a new DataPackage - initialize: function (models, options) { - if (typeof options == "undefined") var options = {}; + initialize(models, options) { + if (typeof options === "undefined") var options = {}; // Create an rdflib reference this.rdf = rdf; @@ -168,10 +172,10 @@ define([ // Create an initial RDF graph this.dataPackageGraph = this.rdf.graph(); - //Set the id or create a new one - this.id = options.id || "resource_map_urn:uuid:" + uuid.v4(); + // Set the id or create a new one + this.id = options.id || `resource_map_urn:uuid:${uuid.v4()}`; - let packageModelAttrs = options.packageModelAttrs || {}; + const packageModelAttrs = options.packageModelAttrs || {}; if (typeof options.packageModel !== "undefined") { // use the given package model @@ -192,13 +196,13 @@ define([ this.id = this.packageModel.id; - //Create a Filter for this DataPackage using the id + // Create a Filter for this DataPackage using the id this.filterModel = new Filter({ fields: ["resourceMap"], values: [this.id], matchSubstring: false, }); - //If the id is ever changed, update the id in the Filter + // If the id is ever changed, update the id in the Filter this.listenTo(this.packageModel, "change:id", function () { this.filterModel.set("values", [this.packageModel.get("id")]); }); @@ -212,27 +216,25 @@ define([ // Build the DataPackage URL based on the MetacatUI.appModel.objectServiceUrl // and id or seriesid - url: function (options) { + url(options) { if (options && options.update) { return ( MetacatUI.appModel.get("objectServiceUrl") + (encodeURIComponent(this.packageModel.get("oldPid")) || encodeURIComponent(this.packageModel.get("seriesid"))) ); - } else { - //URL encode the id or seriesId - var encodedId = - encodeURIComponent(this.packageModel.get("id")) || - encodeURIComponent(this.packageModel.get("seriesid")); - //Use the object service URL if it is available (when pointing to a MN) - if (MetacatUI.appModel.get("objectServiceUrl")) { - return MetacatUI.appModel.get("objectServiceUrl") + encodedId; - } - //Otherwise, use the resolve service URL (when pointing to a CN) - else { - return MetacatUI.appModel.get("resolveServiceUrl") + encodedId; - } } + // URL encode the id or seriesId + const encodedId = + encodeURIComponent(this.packageModel.get("id")) || + encodeURIComponent(this.packageModel.get("seriesid")); + // Use the object service URL if it is available (when pointing to a MN) + if (MetacatUI.appModel.get("objectServiceUrl")) { + return MetacatUI.appModel.get("objectServiceUrl") + encodedId; + } + // Otherwise, use the resolve service URL (when pointing to a CN) + + return MetacatUI.appModel.get("resolveServiceUrl") + encodedId; }, /* @@ -240,6 +242,7 @@ define([ * DataONEObjects, including Metadata and Data objects. * Return the correct model based on the type */ + // eslint-disable-next-line object-shorthand model: function (attrs, options) { switch (attrs.formatid) { case "http://www.openarchives.org/ore/terms": @@ -414,19 +417,18 @@ define([ /** * Overload fetch calls for a DataPackage - * - * @param {Object} [options] - Optional options for this fetch that get sent with the XHR request + * @param {object} [options] - Optional options for this fetch that get sent with the XHR request * @property {boolean} fetchModels - If false, this fetch will not fetch * each model in the collection. It will only get the resource map object. * @property {boolean} fromIndex - If true, the collection will be fetched from Solr rather than * fetching the system metadata of each model. Useful when you only need to retrieve limited information about * each package member. Set query-specific parameters on the `solrResults` SolrResults set on this collection. */ - fetch: function (options) { + fetch(options) { // Fetch the system metadata for this resource map this.packageModel.fetch(); - if (typeof options == "object") { + if (typeof options === "object") { // If the fetchModels property is set to false, if (options.fetchModels === false) { // Save the property to the Collection itself so it is accessible in other functions @@ -443,14 +445,14 @@ define([ } // Set some custom fetch options - var fetchOptions = _.extend({ dataType: "text" }, options); + const fetchOptions = _.extend({ dataType: "text" }, options); - var thisPackage = this; + const thisPackage = this; // Function to retry fetching with user login details if the initial fetch fails - var retryFetch = function () { + const retryFetch = function () { // Add the authorization options - var authFetchOptions = _.extend( + const authFetchOptions = _.extend( fetchOptions, MetacatUI.appUserModel.createAjaxSettings(), ); @@ -458,7 +460,7 @@ define([ // Fetch the resource map RDF XML with user login details return Backbone.Collection.prototype.fetch .call(thisPackage, authFetchOptions) - .fail(function () { + .fail(() => { // trigger failure() console.log("Fetch failed"); @@ -469,50 +471,50 @@ define([ // Fetch the resource map RDF XML return Backbone.Collection.prototype.fetch .call(this, fetchOptions) - .fail(function () { + .fail(() => // If the initial fetch fails, retry with user login details - return retryFetch(); - }); + retryFetch(), + ); }, /* * Deserialize a Package from OAI-ORE RDF XML */ - parse: function (response, options) { - //Save the raw XML in case it needs to be used later + parse(response, options) { + // Save the raw XML in case it needs to be used later this.objectXML = response; - var RDF = this.rdf.Namespace(this.namespaces.RDF), - FOAF = this.rdf.Namespace(this.namespaces.FOAF), - OWL = this.rdf.Namespace(this.namespaces.OWL), - DC = this.rdf.Namespace(this.namespaces.DC), - ORE = this.rdf.Namespace(this.namespaces.ORE), - DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS), - CITO = this.rdf.Namespace(this.namespaces.CITO), - PROV = this.rdf.Namespace(this.namespaces.PROV), - XSD = this.rdf.Namespace(this.namespaces.XSD); - - var memberStatements = [], - atLocationStatements = [], // array to store atLocation statements - memberURIParts, - memberPIDStr, - memberPID, - memberPIDs = [], - memberModel, - documentsStatements, - objectParts, - objectPIDStr, - objectPID, - objectAtLocationValue, - scimetaID, // documentor - scidataID, // documentee - models = []; // the models returned by parse() + const RDF = this.rdf.Namespace(this.namespaces.RDF); + const FOAF = this.rdf.Namespace(this.namespaces.FOAF); + const OWL = this.rdf.Namespace(this.namespaces.OWL); + const DC = this.rdf.Namespace(this.namespaces.DC); + const ORE = this.rdf.Namespace(this.namespaces.ORE); + const DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS); + const CITO = this.rdf.Namespace(this.namespaces.CITO); + const PROV = this.rdf.Namespace(this.namespaces.PROV); + const XSD = this.rdf.Namespace(this.namespaces.XSD); + + let memberStatements = []; + let atLocationStatements = []; // array to store atLocation statements + let memberURIParts; + let memberPIDStr; + let memberPID; + let memberPIDs = []; + let memberModel; + let documentsStatements; + let objectParts; + let objectPIDStr; + let objectPID; + let objectAtLocationValue; + let scimetaID; // documentor + let scidataID; // documentee + const models = []; // the models returned by parse() try { - //First, make sure we are only using one CN Base URL in the RDF or the RDF parsing will fail. - var cnResolveUrl = MetacatUI.appModel.get("resolveServiceUrl"); + // First, make sure we are only using one CN Base URL in the RDF or the RDF parsing will fail. + const cnResolveUrl = MetacatUI.appModel.get("resolveServiceUrl"); - var cnURLs = _.uniq( + const cnURLs = _.uniq( response.match( /cn\S+\.test\.dataone\.org\/cn\/v\d\/resolve|cn\.dataone\.org\/cn\/v\d\/resolve/g, ), @@ -549,8 +551,8 @@ define([ if (memberPID) memberPIDs.push(memberPID); - //TODO: Test passing merge:true when adding a model and this if statement may not be necessary - //Create a DataONEObject model to represent this collection member and add to the collection + // TODO: Test passing merge:true when adding a model and this if statement may not be necessary + // Create a DataONEObject model to represent this collection member and add to the collection if (!_.contains(this.pluck("id"), memberPID)) { memberModel = new DataONEObject({ id: memberPID, @@ -560,12 +562,12 @@ define([ models.push(memberModel); } - //If the model already exists, add this resource map ID to it's list of resource maps + // If the model already exists, add this resource map ID to it's list of resource maps else { memberModel = this.get(memberPID); models.push(memberModel); - var rMaps = memberModel.get("resourceMap"); + let rMaps = memberModel.get("resourceMap"); if ( rMaps && Array.isArray(rMaps) && @@ -580,7 +582,7 @@ define([ this, ); - //Save the list of original ids + // Save the list of original ids this.originalMembers = memberPIDs; // Get the isDocumentedBy relationships @@ -591,7 +593,7 @@ define([ undefined, ); - var sciMetaPids = []; + const sciMetaPids = []; _.each( documentsStatements, @@ -609,7 +611,7 @@ define([ ); // Store the isDocumentedBy relationship - if (typeof this.originalIsDocBy[scidataID] == "undefined") + if (typeof this.originalIsDocBy[scidataID] === "undefined") this.originalIsDocBy[scidataID] = [scimetaID]; else if ( Array.isArray(this.originalIsDocBy[scidataID]) && @@ -622,15 +624,13 @@ define([ scimetaID, ]); - //Find the model in this collection for this data object - //var dataObj = this.get(scidataID); - var dataObj = _.find(models, function (m) { - return m.get("id") == scidataID; - }); + // Find the model in this collection for this data object + // var dataObj = this.get(scidataID); + const dataObj = _.find(models, (m) => m.get("id") == scidataID); if (dataObj) { - //Get the isDocumentedBy field - var isDocBy = dataObj.get("isDocumentedBy"); + // Get the isDocumentedBy field + let isDocBy = dataObj.get("isDocumentedBy"); if ( isDocBy && Array.isArray(isDocBy) && @@ -641,18 +641,18 @@ define([ isDocBy = [isDocBy, scimetaID]; else isDocBy = [scimetaID]; - //Set the isDocumentedBy field + // Set the isDocumentedBy field dataObj.set("isDocumentedBy", isDocBy); } }, this, ); - //Save the list of science metadata pids + // Save the list of science metadata pids this.sciMetaPids = sciMetaPids; // Parse atLocation - var atLocationObject = {}; + const atLocationObject = {}; atLocationStatements = this.dataPackageGraph.statementsMatching( undefined, PROV("atLocation"), @@ -665,7 +665,7 @@ define([ // Get atLocation information for each statement in the resourceMap _.each( atLocationStatements, - function (atLocationStatement) { + (atLocationStatement) => { objectParts = atLocationStatement.subject.value.split("/"); objectPIDStr = _.last(objectParts); objectPID = decodeURIComponent(objectPIDStr); @@ -680,46 +680,46 @@ define([ this.atLocationObject = atLocationObject; - //Put the science metadata pids first + // Put the science metadata pids first memberPIDs = _.difference(memberPIDs, sciMetaPids); - _.each(_.uniq(sciMetaPids), function (id) { + _.each(_.uniq(sciMetaPids), (id) => { memberPIDs.unshift(id); }); - //Don't fetch each member model if the fetchModels property on this Collection is set to false + // Don't fetch each member model if the fetchModels property on this Collection is set to false if (this.fetchModels !== false) { - //Add the models to the collection now, silently - //this.add(models, {silent: true}); + // Add the models to the collection now, silently + // this.add(models, {silent: true}); - //Retrieve the model for each member + // Retrieve the model for each member _.each( models, function (memberModel) { - var collection = this; + const collection = this; memberModel.fetch(); - memberModel.once("sync", function (oldModel) { - //Get the right model type based on the model values - var newModel = collection.getMember(oldModel); + memberModel.once("sync", (oldModel) => { + // Get the right model type based on the model values + const newModel = collection.getMember(oldModel); - //If the model type has changed, then mark the model as unsynced, since there may be custom fetch() options for the new model + // If the model type has changed, then mark the model as unsynced, since there may be custom fetch() options for the new model if (oldModel.type != newModel.type) { // DataPackages shouldn't be fetched until we support nested packages better in the UI if (newModel.type == "DataPackage") { - //Trigger a replace event so other parts of the app know when a model has been replaced with a different type + // Trigger a replace event so other parts of the app know when a model has been replaced with a different type oldModel.trigger("replace", newModel); } else { newModel.set("synced", false); newModel.fetch(); - newModel.once("sync", function (fetchedModel) { + newModel.once("sync", (fetchedModel) => { fetchedModel.set("synced", true); - //Remove the model from the collection and add it back + // Remove the model from the collection and add it back collection.remove(oldModel); collection.add(fetchedModel); - //Trigger a replace event so other parts of the app know when a model has been replaced with a different type + // Trigger a replace event so other parts of the app know when a model has been replaced with a different type oldModel.trigger("replace", newModel); if (newModel.type == "EML") @@ -753,12 +753,12 @@ define([ /* Parse the provenance relationships from the RDF graph, after all DataPackage members have been fetched, as the prov info will be stored in them. */ - parseProv: function () { + parseProv() { try { /* Now run the SPARQL queries for the provenance relationships */ - var provQueries = []; + const provQueries = []; /* result: pidValue, wasDerivedFromValue (prov_wasDerivedFrom) */ - provQueries["prov_wasDerivedFrom"] = + provQueries.prov_wasDerivedFrom = " \n" + "PREFIX rdfs: \n" + @@ -776,7 +776,7 @@ define([ "]]>"; /* result: pidValue, generatedValue (prov_generated) */ - provQueries["prov_generated"] = + provQueries.prov_generated = " \n" + "PREFIX rdfs: \n" + @@ -796,7 +796,7 @@ define([ "]]>"; /* result: pidValue, wasInformedByValue (prov_wasInformedBy) */ - provQueries["prov_wasInformedBy"] = + provQueries.prov_wasInformedBy = " \n" + "PREFIX rdfs: \n" + @@ -814,7 +814,7 @@ define([ "]]> \n"; /* result: pidValue, usedValue (prov_used) */ - provQueries["prov_used"] = + provQueries.prov_used = " \n" + "PREFIX rdfs: \n" + @@ -834,7 +834,7 @@ define([ "]]> \n"; /* result: pidValue, programPidValue (prov_generatesByProgram) */ - provQueries["prov_generatedByProgram"] = + provQueries.prov_generatedByProgram = " \n" + "PREFIX rdfs: \n" + @@ -854,7 +854,7 @@ define([ "]]> \n"; /* result: pidValue, executionPidValue */ - provQueries["prov_generatedByExecution"] = + provQueries.prov_generatedByExecution = " \n" + "PREFIX rdfs: \n" + @@ -872,7 +872,7 @@ define([ "]]> \n"; /* result: pidValue, pid (prov_generatedByProgram) */ - provQueries["prov_generatedByUser"] = + provQueries.prov_generatedByUser = " \n" + "PREFIX rdfs: \n" + @@ -891,7 +891,7 @@ define([ "]]> \n"; /* results: pidValue, programPidValue (prov_usedByProgram) */ - provQueries["prov_usedByProgram"] = + provQueries.prov_usedByProgram = " \n" + "PREFIX rdfs: \n" + @@ -911,7 +911,7 @@ define([ "]]> \n"; /* results: pidValue, executionIdValue (prov_usedByExecution) */ - provQueries["prov_usedByExecution"] = + provQueries.prov_usedByExecution = " \n" + "PREFIX rdfs: \n" + @@ -929,7 +929,7 @@ define([ "]]> \n"; /* results: pidValue, pid (prov_usedByUser) */ - provQueries["prov_usedByUser"] = + provQueries.prov_usedByUser = " \n" + "PREFIX rdfs: \n" + @@ -947,7 +947,7 @@ define([ "} \n" + "]]> \n"; /* results: pidValue, executionIdValue (prov_wasExecutedByExecution) */ - provQueries["prov_wasExecutedByExecution"] = + provQueries.prov_wasExecutedByExecution = " \n" + "PREFIX rdfs: \n" + @@ -966,7 +966,7 @@ define([ "]]> \n"; /* results: pidValue, pid (prov_wasExecutedByUser) */ - provQueries["prov_wasExecutedByUser"] = + provQueries.prov_wasExecutedByUser = " \n" + "PREFIX rdfs: \n" + @@ -985,7 +985,7 @@ define([ "]]> \n"; /* results: pidValue, derivedDataPidValue (prov_hasDerivations) */ - provQueries["prov_hasDerivations"] = + provQueries.prov_hasDerivations = " \n" + "PREFIX rdfs: \n" + @@ -1004,7 +1004,7 @@ define([ "]]> \n"; /* results: pidValue, pid (prov_instanceOfClass) */ - provQueries["prov_instanceOfClass"] = + provQueries.prov_instanceOfClass = " \n" + "PREFIX rdfs: \n" + @@ -1041,10 +1041,10 @@ define([ ]; // Process each SPARQL query - var keys = Object.keys(provQueries); + const keys = Object.keys(provQueries); this.queriesToRun = keys.length; - //Bind the onResult and onDone functions to the model so they can be called out of context + // Bind the onResult and onDone functions to the model so they can be called out of context this.onResult = _.bind(this.onResult, this); this.onDone = _.bind(this.onDone, this); @@ -1053,8 +1053,8 @@ define([ to the 'onResult' function. When each query has completed, the 'onDone' function is called for that query. */ - for (var iquery = 0; iquery < keys.length; iquery++) { - var eq = rdf.SPARQLToQuery( + for (let iquery = 0; iquery < keys.length; iquery++) { + const eq = rdf.SPARQLToQuery( provQueries[keys[iquery]], false, this.dataPackageGraph, @@ -1072,12 +1072,13 @@ define([ }, // The return values have to be extracted from the result. - getValue: function (result, name) { - var res = result[name]; + getValue(result, name) { + const res = result[name]; // The result is of type 'NamedNode', just return the string value if (res) { return res.value; - } else return " "; + } + return " "; }, /* This callback is called for every query solution of the SPARQL queries. One @@ -1093,24 +1094,22 @@ define([ ?primary_data : t {termType: "NamedNode", value: "https://cn-stage.test.dataone.org/cn/v2/resolve/urn%3Auuid%3Aaae9d025-a331-4c3a-b399-a8ca0a2826ef"} ?prov_wasDerivedFrom : t {termType: "Literal", value: "urn:uuid:aae9d025-a331-4c3a-b399-a8ca0a2826ef", datatype: t}] */ - onResult: function (result) { - var currentPid = this.getValue(result, "?pid"); - var resval; - var provFieldResult; + onResult(result) { + const currentPid = this.getValue(result, "?pid"); + let resval; + let provFieldResult; var provFieldValues; // If there is a solution for this query, assign the value // to the prov field attribute (e.g. "prov_generated") of the package member (a DataONEObject) // with id = '?pid' if (typeof currentPid !== "undefined" && currentPid !== " ") { - var currentMember = null; + let currentMember = null; var provFieldValues; - var fieldName = null; - var vals = []; - var resultMember = null; - currentMember = this.find(function (model) { - return model.get("id") === currentPid; - }); + let fieldName = null; + let vals = []; + let resultMember = null; + currentMember = this.find((model) => model.get("id") === currentPid); if (typeof currentMember === "undefined") { return; @@ -1121,9 +1120,9 @@ define([ // Note: dataPackage.provSources and dataPackage.provDerivations are accumulators for // the entire DataPackage. member.sources and member.derivations are accumulators for // each package member, and are used by functions such as ProvChartView(). - for (var iFld = 0; iFld < this.provFields.length; iFld++) { + for (let iFld = 0; iFld < this.provFields.length; iFld++) { fieldName = this.provFields[iFld]; - resval = "?" + fieldName; + resval = `?${fieldName}`; // The pid corresponding to the object of the RDF triple, with the predicate // of 'prov_generated', 'prov_used', etc. // getValue returns a string value. @@ -1133,9 +1132,9 @@ define([ // prov_* value to it. This is the package member that is the 'subject' of the // prov relationship. // The 'resultMember' could be in the current package, or could be in another 'related' package. - resultMember = this.find(function (model) { - return model.get("id") === provFieldResult; - }); + resultMember = this.find( + (model) => model.get("id") === provFieldResult, + ); if (typeof resultMember !== "undefined") { // If this prov field is a 'source' field, add it to 'sources' @@ -1189,10 +1188,10 @@ define([ currentMember.set(fieldName, vals); } - //provFieldValues = _.uniq(provFieldValues); + // provFieldValues = _.uniq(provFieldValues); // Add the current prov valid (a pid) to the current value in the member - //currentMember.set(fieldName, provFieldValues); - //this.add(currentMember, { merge: true }); + // currentMember.set(fieldName, provFieldValues); + // this.add(currentMember, { merge: true }); } else { // The query result field is not the identifier of a packge member, so it may be the identifier // of another 'related' package, or it may be a string value that is the object of a prov relationship, @@ -1210,7 +1209,7 @@ define([ }, /* This callback is called when all queries have finished. */ - onDone: function () { + onDone() { if (this.queriesToRun > 1) { this.queriesToRun--; } else { @@ -1223,33 +1222,33 @@ define([ /* * Use the DataONEObject parseSysMeta() function */ - parseSysMeta: function () { + parseSysMeta() { return DataONEObject.parseSysMeta.call(this, arguments[0]); }, /** * Overwrite the Backbone.Collection.sync() function to set custom options - * @param {Object} [options] - Options for this DataPackage save - * @param {Boolean} [options.sysMetaOnly] - If true, only the system metadata of this Package will be saved. - * @param {Boolean} [options.resourceMapOnly] - If true, only the Resource Map/Package object will be saved. Metadata and Data objects aggregated by the package will be skipped. + * @param {object} [options] - Options for this DataPackage save + * @param {boolean} [options.sysMetaOnly] - If true, only the system metadata of this Package will be saved. + * @param {boolean} [options.resourceMapOnly] - If true, only the Resource Map/Package object will be saved. Metadata and Data objects aggregated by the package will be skipped. */ - save: function (options) { + save(options) { if (!options) var options = {}; this.packageModel.set("uploadStatus", "p"); - //Get the system metadata first if we haven't retrieved it yet + // Get the system metadata first if we haven't retrieved it yet if (!this.packageModel.get("sysMetaXML")) { var collection = this; this.packageModel.fetch({ - success: function () { + success() { collection.save(options); }, }); return; } - //If we want to update the system metadata only, + // If we want to update the system metadata only, // then update via the DataONEObject model and exit if (options.sysMetaOnly) { this.packageModel.save(null, options); @@ -1257,20 +1256,21 @@ define([ } if (options.resourceMapOnly !== true) { - //Sort the models in the collection so the metadata is saved first - var metadataModels = this.where({ type: "Metadata" }); - var dataModels = _.difference(this.models, metadataModels); - var sortedModels = _.union(metadataModels, dataModels); - var modelsInProgress = _.filter(sortedModels, function (m) { - return ( + // Sort the models in the collection so the metadata is saved first + const metadataModels = this.where({ type: "Metadata" }); + const dataModels = _.difference(this.models, metadataModels); + const sortedModels = _.union(metadataModels, dataModels); + const modelsInProgress = _.filter( + sortedModels, + (m) => m.get("uploadStatus") == "p" || - m.get("sysMetaUploadStatus") == "p" - ); - }); - var modelsToBeSaved = _.filter(sortedModels, function (m) { - //Models should be saved if they are in the save queue, had an error saving earlier, - //or they are Science Metadata model that is NOT already in progress - return ( + m.get("sysMetaUploadStatus") == "p", + ); + const modelsToBeSaved = _.filter( + sortedModels, + (m) => + // Models should be saved if they are in the save queue, had an error saving earlier, + // or they are Science Metadata model that is NOT already in progress (m.get("type") == "Metadata" && m.get("uploadStatus") == "q") || (m.get("type") == "Data" && m.get("hasContentChanges") && @@ -1281,30 +1281,28 @@ define([ m.get("uploadStatus") != "p" && m.get("uploadStatus") != "c" && m.get("uploadStatus") != "e" && - m.get("uploadStatus") !== null) - ); - }); - //Get an array of data objects whose system metadata should be updated. - var sysMetaToUpdate = _.reject(dataModels, function (m) { - // Find models that don't have any content changes to save, - // and whose system metadata is not already saving - return ( + m.get("uploadStatus") !== null), + ); + // Get an array of data objects whose system metadata should be updated. + var sysMetaToUpdate = _.reject( + dataModels, + (m) => + // Find models that don't have any content changes to save, + // and whose system metadata is not already saving !m.hasUpdates() || m.get("hasContentChanges") || m.get("sysMetaUploadStatus") == "p" || m.get("sysMetaUploadStatus") == "c" || - m.get("sysMetaUploadStatus") == "e" - ); - }); + m.get("sysMetaUploadStatus") == "e", + ); - //First quickly validate all the models before attempting to save any - var allValid = _.every(modelsToBeSaved, function (m) { + // First quickly validate all the models before attempting to save any + const allValid = _.every(modelsToBeSaved, (m) => { if (m.isValid()) { m.trigger("valid"); return true; - } else { - return false; } + return false; }); // If at least once model to be saved is invalid, @@ -1312,9 +1310,7 @@ define([ if ( !allValid || _.contains( - _.map(metadataModels, function (model) { - return model.get("uploadStatus"); - }), + _.map(metadataModels, (model) => model.get("uploadStatus")), "e", ) ) { @@ -1324,53 +1320,52 @@ define([ return; } - //If we are saving at least one model in this package, then serialize the Resource Map RDF XML + // If we are saving at least one model in this package, then serialize the Resource Map RDF XML if (modelsToBeSaved.length) { try { - //Set a new id and keep our old id + // Set a new id and keep our old id if (!this.packageModel.isNew()) { - //Update the identifier for this object + // Update the identifier for this object this.packageModel.updateID(); } - //Create the resource map XML + // Create the resource map XML var mapXML = this.serialize(); } catch (serializationException) { - //If serialization failed, revert back to our old id + // If serialization failed, revert back to our old id this.packageModel.resetID(); - //Cancel the save and show an error message + // Cancel the save and show an error message this.packageModel.set("changed", false); this.packageModel.set("uploadStatus", "q"); this.trigger( "errorSaving", - "There was a Javascript error during the serialization process: " + - serializationException, + `There was a Javascript error during the serialization process: ${serializationException}`, ); return; } } - //First save all the models of the collection, if needed + // First save all the models of the collection, if needed _.each( modelsToBeSaved, function (model) { - //If the model is saved successfully, start this save function again + // If the model is saved successfully, start this save function again this.stopListening(model, "successSaving", this.save); this.listenToOnce(model, "successSaving", this.save); - //If the model fails to save, start this save function + // If the model fails to save, start this save function this.stopListening(model, "errorSaving", this.save); this.listenToOnce(model, "errorSaving", this.save); - //If the model fails to save, start this save function + // If the model fails to save, start this save function this.stopListening(model, "cancelSave", this.save); this.listenToOnce(model, "cancelSave", this.save); - //Save the model and watch for fails + // Save the model and watch for fails model.save(); - //Add it to the list of models in progress + // Add it to the list of models in progress modelsInProgress.push(model); this.numSaves++; @@ -1378,56 +1373,55 @@ define([ this, ); - //Save the system metadata of all the Data objects + // Save the system metadata of all the Data objects _.each( sysMetaToUpdate, function (dataModel) { - //When the sytem metadata has been saved, save this resource map + // When the sytem metadata has been saved, save this resource map this.listenTo(dataModel, "change:sysMetaUploadStatus", this.save); - //Update the system metadata + // Update the system metadata dataModel.updateSysMeta(); - //Add it to the list of models in progress + // Add it to the list of models in progress modelsInProgress.push(dataModel); this.numSaves++; }, this, ); - //If there are still models in progress of uploading, then exit. (We will return when they are synced to upload the resource map) + // If there are still models in progress of uploading, then exit. (We will return when they are synced to upload the resource map) if (modelsInProgress.length) return; } - //If we are saving the resource map object only, and there are changes to save, serialize the RDF XML + // If we are saving the resource map object only, and there are changes to save, serialize the RDF XML else if (this.needsUpdate()) { try { - //Set a new id and keep our old id + // Set a new id and keep our old id if (!this.packageModel.isNew()) { - //Update the identifier for this object + // Update the identifier for this object this.packageModel.updateID(); } - //Create the resource map XML + // Create the resource map XML var mapXML = this.serialize(); } catch (serializationException) { - //If serialization failed, revert back to our old id + // If serialization failed, revert back to our old id this.packageModel.resetID(); - //Cancel the save and show an error message + // Cancel the save and show an error message this.packageModel.set("changed", false); this.packageModel.set("uploadStatus", "q"); this.trigger( "errorSaving", - "There was a Javascript error during the serialization process: " + - serializationException, + `There was a Javascript error during the serialization process: ${serializationException}`, ); return; } } - //If we are saving the resource map object only, and there are no changes to save, exit the function + // If we are saving the resource map object only, and there are no changes to save, exit the function else if (!this.needsUpdate()) { return; } - //If no models were saved and this package has no changes, we can exit without saving the resource map + // If no models were saved and this package has no changes, we can exit without saving the resource map if (this.numSaves < 1 && !this.needsUpdate()) { this.numSaves = 0; this.packageModel.set( @@ -1438,78 +1432,78 @@ define([ return; } - //Reset the number of models saved since they should all be completed by now + // Reset the number of models saved since they should all be completed by now this.numSaves = 0; - //Determine the HTTP request type - var requestType; + // Determine the HTTP request type + let requestType; if (this.packageModel.isNew()) { requestType = "POST"; } else { requestType = "PUT"; } - //Create a FormData object to send data with the XHR - var formData = new FormData(); + // Create a FormData object to send data with the XHR + const formData = new FormData(); - //Add the identifier to the XHR data + // Add the identifier to the XHR data if (this.packageModel.isNew()) { formData.append("pid", this.packageModel.get("id")); } else { - //Add the ids to the form data + // Add the ids to the form data formData.append("newPid", this.packageModel.get("id")); formData.append("pid", this.packageModel.get("oldPid")); } - //Do a fresh re-serialization of the RDF XML, in case any pids in the package have changed. - //The hope is that any errors during the serialization process have already been caught during the first serialization above + // Do a fresh re-serialization of the RDF XML, in case any pids in the package have changed. + // The hope is that any errors during the serialization process have already been caught during the first serialization above try { var mapXML = this.serialize(); } catch (serializationException) { - //Cancel the save and show an error message + // Cancel the save and show an error message this.packageModel.set("changed", false); this.packageModel.set("uploadStatus", "q"); this.trigger( "errorSaving", - "There was a Javascript error during the serialization process: " + - serializationException, + `There was a Javascript error during the serialization process: ${serializationException}`, ); return; } - //Make a Blob object from the serialized RDF XML - var mapBlob = new Blob([mapXML], { type: "application/xml" }); + // Make a Blob object from the serialized RDF XML + const mapBlob = new Blob([mapXML], { type: "application/xml" }); - //Get the size of the new resource map + // Get the size of the new resource map this.packageModel.set("size", mapBlob.size); - //Get the new checksum of the resource map - var checksum = md5(mapXML); + // Get the new checksum of the resource map + const checksum = md5(mapXML); this.packageModel.set("checksum", checksum); this.packageModel.set("checksumAlgorithm", "MD5"); - //Set the file name based on the id + // Set the file name based on the id this.packageModel.set( "fileName", - this.packageModel.get("id").replace(/[^a-zA-Z0-9]/g, "_") + - ".rdf.xml", + `${this.packageModel + .get("id") + .replace(/[^a-zA-Z0-9]/g, "_")}.rdf.xml`, ); - //Create the system metadata - var sysMetaXML = this.packageModel.serializeSysMeta(); + // Create the system metadata + const sysMetaXML = this.packageModel.serializeSysMeta(); - //Send the system metadata - var xmlBlob = new Blob([sysMetaXML], { + // Send the system metadata + const xmlBlob = new Blob([sysMetaXML], { type: "application/xml", }); - //Add the object XML and System Metadata XML to the form data - //Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler + // Add the object XML and System Metadata XML to the form data + // Append the system metadata first, so we can take advantage of Metacat's streaming multipart handler formData.append("sysmeta", xmlBlob, "sysmeta"); formData.append("object", mapBlob); var collection = this; - var requestSettings = { + const requestSettings = { url: this.packageModel.isNew() ? this.url() : this.url({ update: true }), @@ -1518,16 +1512,16 @@ define([ contentType: false, processData: false, data: formData, - success: function (response) { - //Update the object XML + success(response) { + // Update the object XML collection.objectXML = mapXML; collection.packageModel.set( "sysMetaXML", collection.packageModel.serializeSysMeta(), ); - //Reset the upload status for all members - _.each(collection.where({ uploadStatus: "c" }), function (m) { + // Reset the upload status for all members + _.each(collection.where({ uploadStatus: "c" }), (m) => { m.set("uploadStatus", m.defaults().uploadStatus); }); @@ -1535,7 +1529,7 @@ define([ // in the future collection.packageModel.set("oldPid", null); - //Reset the upload status for the package + // Reset the upload status for the package collection.packageModel.set( "uploadStatus", collection.packageModel.defaults().uploadStatus, @@ -1551,20 +1545,20 @@ define([ collection.packageModel.fetch({ merge: true }); - _.each(sysMetaToUpdate, function (dataModel) { + _.each(sysMetaToUpdate, (dataModel) => { dataModel.set("sysMetaUploadStatus", "c"); }); }, - error: function (data) { - //Reset the id back to its original state + error(data) { + // Reset the id back to its original state collection.packageModel.resetID(); - //Reset the upload status for all members - _.each(collection.where({ uploadStatus: "c" }), function (m) { + // Reset the upload status for all members + _.each(collection.where({ uploadStatus: "c" }), (m) => { m.set("uploadStatus", m.defaults().uploadStatus); }); - //When there is no network connection (status == 0), there will be no response text + // When there is no network connection (status == 0), there will be no response text if (data.status == 408 || data.status == 0) { var parsedResponse = "There was a network issue that prevented this file from uploading. " + @@ -1575,10 +1569,10 @@ define([ .text(); } - //Save the error message in the model + // Save the error message in the model collection.packageModel.set("errorMessage", parsedResponse); - //Reset the upload status for the package + // Reset the upload status for the package collection.packageModel.set("uploadStatus", "e"); collection.trigger("errorSaving", parsedResponse); @@ -1603,8 +1597,8 @@ define([ * When a data package member updates, we evaluate it for its formatid, * and update it appropriately if it is not a data object only */ - getMember: function (context, args) { - var memberModel = {}; + getMember(context, args) { + let memberModel = {}; switch (context.get("formatId")) { case "http://www.openarchives.org/ore/terms": @@ -1909,8 +1903,8 @@ define([ return memberModel; }, - triggerComplete: function (model) { - //If the last fetch did not fetch the models of the collection, then mark as complete now. + triggerComplete(model) { + // If the last fetch did not fetch the models of the collection, then mark as complete now. if (this.fetchModels === false) { // Delete the fetchModels property since it is set only once per fetch. delete this.fetchModels; @@ -1920,17 +1914,17 @@ define([ return; } - //Check if the collection is done being retrieved - var notSynced = this.reject(function (m) { - return m.get("synced") || m.get("id") == model.get("id"); - }); + // Check if the collection is done being retrieved + const notSynced = this.reject( + (m) => m.get("synced") || m.get("id") == model.get("id"), + ); - //If there are any models that are not synced yet, the collection is not complete + // If there are any models that are not synced yet, the collection is not complete if (notSynced.length > 0) { return; } - //If the number of models in this collection does not equal the number of objects referenced in the RDF XML, the collection is not complete + // If the number of models in this collection does not equal the number of objects referenced in the RDF XML, the collection is not complete if (this.originalMembers.length > this.length) return; this.sort(); @@ -1940,23 +1934,23 @@ define([ /* Accumulate edits that are made to the provenance relationships via the ProvChartView. these edits are accumulated here so that they are available to any package member or view. */ - recordProvEdit: function (operation, subject, predicate, object) { + recordProvEdit(operation, subject, predicate, object) { if (!this.provEdits.length) { this.provEdits = [[operation, subject, predicate, object]]; } else { // First check if the edit already exists in the list. If yes, then // don't add it again! This could occur if an edit icon was clicked rapidly // before it is dismissed. - var editFound = _.find(this.provEdits, function (edit) { - return ( + const editFound = _.find( + this.provEdits, + (edit) => edit[0] == operation && edit[1] == subject && edit[2] == predicate && - edit[3] == object - ); - }); + edit[3] == object, + ); - if (typeof editFound != "undefined") { + if (typeof editFound !== "undefined") { return; } @@ -1964,14 +1958,14 @@ define([ // is in the edit list (i.e. the user may have changed their mind, and // they just want to cancel an edit). If yes, then just delete the // matching add edit request - var editListSize = this.provEdits.length; - var oppositeOp = operation == "delete" ? "add" : "delete"; - - this.provEdits = _.reject(this.provEdits, function (edit) { - var editOperation = edit[0]; - var editSubjectId = edit[1]; - var editPredicate = edit[2]; - var editObject = edit[3]; + const editListSize = this.provEdits.length; + const oppositeOp = operation == "delete" ? "add" : "delete"; + + this.provEdits = _.reject(this.provEdits, (edit) => { + const editOperation = edit[0]; + const editSubjectId = edit[1]; + const editPredicate = edit[2]; + const editObject = edit[3]; if ( editOperation == oppositeOp && editSubjectId == subject && @@ -1992,7 +1986,7 @@ define([ }, // Return true if the prov edits list is not empty - provEditsPending: function () { + provEditsPending() { if (this.provEdits.length) return true; return false; }, @@ -2000,22 +1994,22 @@ define([ /* If provenance relationships have been modified by the provenance editor (in ProvChartView), then update the ORE Resource Map and save it to the server. */ - saveProv: function () { - var rdf = this.rdf; - var graph = this.dataPackageGraph; + saveProv() { + const { rdf } = this; + const graph = this.dataPackageGraph; - var provEdits = this.provEdits; + const { provEdits } = this; if (!provEdits.length) { return; } - var RDF = rdf.Namespace(this.namespaces.RDF), - PROV = rdf.Namespace(this.namespaces.PROV), - PROVONE = rdf.Namespace(this.namespaces.PROVONE), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - CITO = rdf.Namespace(this.namespaces.CITO), - XSD = rdf.Namespace(this.namespaces.XSD); + const RDF = rdf.Namespace(this.namespaces.RDF); + const PROV = rdf.Namespace(this.namespaces.PROV); + const PROVONE = rdf.Namespace(this.namespaces.PROVONE); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const CITO = rdf.Namespace(this.namespaces.CITO); + const XSD = rdf.Namespace(this.namespaces.XSD); - var cnResolveUrl = this.getCnURI(); + const cnResolveUrl = this.getCnURI(); /* Check if this package member had provenance relationships added or deleted by the provenance editor functionality of the ProvChartView @@ -2023,8 +2017,11 @@ define([ _.each( provEdits, function (edit) { - var operation, subject, predicate, object; - var provStatements; + let operation; + let subject; + let predicate; + let object; + let provStatements; operation = edit[0]; subject = edit[1]; predicate = edit[2]; @@ -2041,16 +2038,23 @@ define([ // inputs and outputs and is connected to a program via a prov:association. We must 'expand' the // simplified provenance updates recorded by the editor into the fully detailed representation // of the actual model. - var executionId, executionURI, executionNode; - var programId, programURI, programNode; - var dataId, dataURI, dataNode; - var derivedDataURI, derivedDataNode; - var lastRef = false; - //var graph = this.dataPackageGraph; - - //Create a node for the subject and object - var subjectNode = rdf.sym(this.getURIFromRDF(subject)), - objectNode = rdf.sym(this.getURIFromRDF(object)); + let executionId; + let executionURI; + let executionNode; + let programId; + let programURI; + let programNode; + let dataId; + let dataURI; + let dataNode; + let derivedDataURI; + let derivedDataNode; + const lastRef = false; + // var graph = this.dataPackageGraph; + + // Create a node for the subject and object + const subjectNode = rdf.sym(this.getURIFromRDF(subject)); + const objectNode = rdf.sym(this.getURIFromRDF(object)); switch (predicate) { case "prov_wasDerivedFrom": @@ -2094,7 +2098,7 @@ define([ // 'subject' is the program id, which is a simplification of the PROVONE model for display. // In the PROVONE model, execution 'uses' and input, and is associated with a program. executionId = this.addProgramToGraph(programId); - //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); + // executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); executionNode = this.getExecutionNode(executionId); this.addToGraph(dataNode, RDF("type"), PROVONE("Data")); this.addToGraph( @@ -2126,7 +2130,7 @@ define([ // 'subject' is the program id, which is a simplification of the PROVONE model for display. // In the PROVONE model, execution 'uses' and input, and is associated with a program. executionId = this.addProgramToGraph(programId); - //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); + // executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); executionNode = this.getExecutionNode(executionId); this.addToGraph(dataNode, RDF("type"), PROVONE("Data")); this.addToGraph(executionNode, PROV("used"), dataNode); @@ -2202,9 +2206,9 @@ define([ /* Add the specified relationship to the RDF graph only if it has not already been added. */ - addToGraph: function (subject, predicate, object) { - var graph = this.dataPackageGraph; - var statements = graph.statementsMatching(subject, predicate, object); + addToGraph(subject, predicate, object) { + const graph = this.dataPackageGraph; + const statements = graph.statementsMatching(subject, predicate, object); if (!statements.length) { graph.add(subject, predicate, object); @@ -2218,15 +2222,15 @@ define([ Also don't remove it if the subject is in any other prov statement, meaning it still references another prov object. */ - removeIfLastProvRef: function (subjectNode, predicateNode, objectNode) { - var graph = this.dataPackageGraph; - var stillUsed = false; - var PROV = rdf.Namespace(this.namespaces.PROV); - var PROVONE = rdf.Namespace(this.namespaces.PROVONE); + removeIfLastProvRef(subjectNode, predicateNode, objectNode) { + const graph = this.dataPackageGraph; + const stillUsed = false; + const PROV = rdf.Namespace(this.namespaces.PROV); + const PROVONE = rdf.Namespace(this.namespaces.PROVONE); // PROV namespace value, used to identify PROV statements - var provStr = PROV("").value; + const provStr = PROV("").value; // PROVONE namespace value, used to identify PROVONE statements - var provoneStr = PROVONE("").value; + const provoneStr = PROVONE("").value; // Get the statements from the RDF graph that reference the subject of the // statement to remove. var statements = graph.statementsMatching( @@ -2235,9 +2239,9 @@ define([ subjectNode, ); - var found = _.find( + let found = _.find( statements, - function (statement) { + (statement) => { if ( statement.subject == subjectNode && statement.predicate == predicateNode && @@ -2245,7 +2249,7 @@ define([ ) return false; - var pVal = statement.predicate.value; + const pVal = statement.predicate.value; // Now check if the subject is referenced in a prov statement // There is another statement that references the subject of the @@ -2259,7 +2263,7 @@ define([ ); // IF not found in the first test, keep looking. - if (typeof found == "undefined") { + if (typeof found === "undefined") { // Get the statements from the RDF where var statements = graph.statementsMatching( subjectNode, @@ -2269,14 +2273,14 @@ define([ found = _.find( statements, - function (statement) { + (statement) => { if ( statement.subject == subjectNode && statement.predicate == predicateNode && statement.object == objectNode ) return false; - var pVal = statement.predicate.value; + const pVal = statement.predicate.value; // Now check if the subject is referenced in a prov statement if (pVal.indexOf(provStr) != -1) return true; @@ -2291,7 +2295,7 @@ define([ } // The specified statement term isn't being used for prov, so remove it. - if (typeof found == "undefined") { + if (typeof found === "undefined") { graph.removeMatches( subjectNode, predicateNode, @@ -2314,13 +2318,13 @@ define([ * Should be called during a call to serialize() and mutates * this.dataPackageGraph directly as a side-effect. */ - removeOrphanedBlankNodes: function () { + removeOrphanedBlankNodes() { if (!this.dataPackageGraph || !this.dataPackageGraph.statements) { return; } // Collect an array of statements to be removed - var toRemove = []; + const toRemove = []; _.each( this.dataPackageGraph.statements, @@ -2330,9 +2334,9 @@ define([ } // For this statement, look for other statments about it - var matches = 0; + let matches = 0; - _.each(this.dataPackageGraph.statements, function (other) { + _.each(this.dataPackageGraph.statements, (other) => { if ( other.subject.termType === "BlankNode" && other.subject.id === statement.object.id @@ -2364,40 +2368,39 @@ define([ for the program script, or available by tracing backward in the RDF graph from the program node, through the assocation to the related execution. */ - getExecutionId: function (programId) { - var rdf = this.rdf; - var graph = this.dataPackageGraph; - var stmts = null; - var cnResolveUrl = this.getCnURI(); - var RDF = rdf.Namespace(this.namespaces.RDF), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - PROV = rdf.Namespace(this.namespaces.PROV), - PROVONE = rdf.Namespace(this.namespaces.PROVONE); - - var member = this.get(programId); - var executionId = member.get("prov_wasExecutedByExecution"); + getExecutionId(programId) { + const { rdf } = this; + const graph = this.dataPackageGraph; + let stmts = null; + const cnResolveUrl = this.getCnURI(); + const RDF = rdf.Namespace(this.namespaces.RDF); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const PROV = rdf.Namespace(this.namespaces.PROV); + const PROVONE = rdf.Namespace(this.namespaces.PROVONE); + + const member = this.get(programId); + const executionId = member.get("prov_wasExecutedByExecution"); if (executionId.length > 0) { return executionId[0]; - } else { - var programNode = rdf.sym(this.getURIFromRDF(programId)); - // Get the executionId from the RDF graph - // There can be only one plan for an association - stmts = graph.statementsMatching( - undefined, - PROV("hadPlan"), - programNode, - ); - if (typeof stmts == "undefined") return null; - var associationNode = stmts[0].subject; - // There should be only one execution for this assocation. - stmts = graph.statementsMatching( - undefined, - PROV("qualifiedAssociation"), - associationNode, - ); - if (typeof stmts == "undefined") return null; - return stmts[0].subject; } + const programNode = rdf.sym(this.getURIFromRDF(programId)); + // Get the executionId from the RDF graph + // There can be only one plan for an association + stmts = graph.statementsMatching( + undefined, + PROV("hadPlan"), + programNode, + ); + if (typeof stmts === "undefined") return null; + const associationNode = stmts[0].subject; + // There should be only one execution for this assocation. + stmts = graph.statementsMatching( + undefined, + PROV("qualifiedAssociation"), + associationNode, + ); + if (typeof stmts === "undefined") return null; + return stmts[0].subject; }, /* Get the RDF node for an execution that is associated with the execution identifier. @@ -2405,12 +2408,12 @@ define([ (no resolveURI), or as a resolve URL, so check for both until the id is found. */ - getExecutionNode: function (executionId) { - var rdf = this.rdf; - var graph = this.dataPackageGraph; - var stmts = null; - var testNode = null; - var cnResolveUrl = this.getCnURI(); + getExecutionNode(executionId) { + const { rdf } = this; + const graph = this.dataPackageGraph; + let stmts = null; + let testNode = null; + const cnResolveUrl = this.getCnURI(); // First see if the execution exists in the RDF graph as a 'bare' idenfier, i.e. // a 'urn:uuid'. @@ -2419,7 +2422,7 @@ define([ undefined, undefined, ); - if (typeof stmts == "undefined" || !stmts.length) { + if (typeof stmts === "undefined" || !stmts.length) { // The execution node as urn was not found, look for fully qualified version. testNode = rdf.sym(this.getURIFromRDF(executionId)); stmts = graph.statementsMatching( @@ -2427,54 +2430,52 @@ define([ undefined, undefined, ); - if (typeof stmts == "undefined") { + if (typeof stmts === "undefined") { // Couldn't find the execution, return the standard RDF node value executionNode = rdf.sym(this.getURIFromRDF(executionId)); return executionNode; - } else { - return testNode; } - } else { - // The executionNode was found in the RDF graph as a urn - var executionNode = stmts[0].subject; - return executionNode; + return testNode; } + // The executionNode was found in the RDF graph as a urn + var executionNode = stmts[0].subject; + return executionNode; }, - addProgramToGraph: function (programId) { - var rdf = this.rdf; - var graph = this.dataPackageGraph; - var RDF = rdf.Namespace(this.namespaces.RDF), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - PROV = rdf.Namespace(this.namespaces.PROV), - PROVONE = rdf.Namespace(this.namespaces.PROVONE), - XSD = rdf.Namespace(this.namespaces.XSD); - var member = this.get(programId); - var executionId = member.get("prov_wasExecutedByExecution"); - var executionNode = null; - var programNode = null; - var associationId = null; - var associationNode = null; - var cnResolveUrl = this.getCnURI(); + addProgramToGraph(programId) { + const { rdf } = this; + const graph = this.dataPackageGraph; + const RDF = rdf.Namespace(this.namespaces.RDF); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const PROV = rdf.Namespace(this.namespaces.PROV); + const PROVONE = rdf.Namespace(this.namespaces.PROVONE); + const XSD = rdf.Namespace(this.namespaces.XSD); + const member = this.get(programId); + let executionId = member.get("prov_wasExecutedByExecution"); + let executionNode = null; + let programNode = null; + const associationId = null; + let associationNode = null; + const cnResolveUrl = this.getCnURI(); if (!executionId.length) { // This is a new execution, so create new execution and association ids - executionId = "urn:uuid:" + uuid.v4(); + executionId = `urn:uuid:${uuid.v4()}`; member.set("prov_wasExecutedByExecution", [executionId]); // Blank node id. RDF validator doesn't like ':' so don't use in the id - //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); + // executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); executionNode = this.getExecutionNode(executionId); - //associationId = "_" + uuid.v4(); + // associationId = "_" + uuid.v4(); associationNode = graph.bnode(); } else { executionId = executionId[0]; // Check if an association exists in the RDF graph for this execution id - //executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); + // executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); executionNode = this.getExecutionNode(executionId); // Check if there is an association id for this execution. // If this execution is newly created (via the editor (existing would // be parsed from the resmap), then create a new association id. - var stmts = graph.statementsMatching( + const stmts = graph.statementsMatching( executionNode, PROV("qualifiedAssociation"), undefined, @@ -2483,14 +2484,14 @@ define([ // (Associations aren't stored in the ) if (stmts.length) { associationNode = stmts[0].object; - //associationId = stmts[0].object.value; + // associationId = stmts[0].object.value; } else { - //associationId = "_" + uuid.v4(); + // associationId = "_" + uuid.v4(); associationNode = graph.bnode(); } } - //associationNode = graph.bnode(associationId); - //associationNode = graph.bnode(); + // associationNode = graph.bnode(associationId); + // associationNode = graph.bnode(); programNode = rdf.sym(this.getURIFromRDF(programId)); try { this.addToGraph( @@ -2515,24 +2516,24 @@ define([ // Remove a program identifier from the RDF graph and remove associated // linkage between the program id and the exection, if the execution is not // being used by any other statements. - removeProgramFromGraph: function (programId) { - var graph = this.dataPackageGraph; - var rdf = this.rdf; - var stmts = null; - var cnResolveUrl = this.getCnURI(); - var RDF = rdf.Namespace(this.namespaces.RDF), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - PROV = rdf.Namespace(this.namespaces.PROV), - PROVONE = rdf.Namespace(this.namespaces.PROVONE), - XSD = rdf.Namespace(this.namespaces.XSD); - var associationNode = null; - - var executionId = this.getExecutionId(programId); + removeProgramFromGraph(programId) { + const graph = this.dataPackageGraph; + const { rdf } = this; + let stmts = null; + const cnResolveUrl = this.getCnURI(); + const RDF = rdf.Namespace(this.namespaces.RDF); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const PROV = rdf.Namespace(this.namespaces.PROV); + const PROVONE = rdf.Namespace(this.namespaces.PROVONE); + const XSD = rdf.Namespace(this.namespaces.XSD); + let associationNode = null; + + const executionId = this.getExecutionId(programId); if (executionId == null) return false; - //var executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); - var executionNode = this.getExecutionNode(executionId); - var programNode = rdf.sym(this.getURIFromRDF(programId)); + // var executionNode = rdf.sym(cnResolveUrl + encodeURIComponent(executionId)); + const executionNode = this.getExecutionNode(executionId); + const programNode = rdf.sym(this.getURIFromRDF(programId)); // In order to remove this program from the graph, we have to first determine that // nothing else is using the execution that is associated with the program (the plan). @@ -2543,7 +2544,7 @@ define([ // Is the program in the graph? If the program is not in the graph, then // we don't know how to remove the proper execution and assocation. stmts = graph.statementsMatching(undefined, undefined, programNode); - if (typeof stmts == "undefined" || !stmts.length) return false; + if (typeof stmts === "undefined" || !stmts.length) return false; // Is anything else linked to this execution? stmts = graph.statementsMatching(executionNode, PROV("used")); @@ -2614,37 +2615,37 @@ define([ /* * Serialize the DataPackage to OAI-ORE RDF XML */ - serialize: function () { - //Create an RDF serializer - var serializer = this.rdf.Serializer(), - oldPidVariations, - modifiedDate, - subjectClone, - predicateClone, - objectClone; + serialize() { + // Create an RDF serializer + const serializer = this.rdf.Serializer(); + let oldPidVariations; + let modifiedDate; + let subjectClone; + let predicateClone; + let objectClone; serializer.store = this.dataPackageGraph; - //Define the namespaces - var ORE = this.rdf.Namespace(this.namespaces.ORE), - CITO = this.rdf.Namespace(this.namespaces.CITO), - DC = this.rdf.Namespace(this.namespaces.DC), - DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS), - FOAF = this.rdf.Namespace(this.namespaces.FOAF), - RDF = this.rdf.Namespace(this.namespaces.RDF), - XSD = this.rdf.Namespace(this.namespaces.XSD); - - //Get the pid of this package - depends on whether we are updating or creating a resource map - var pid = this.packageModel.get("id"), - oldPid = this.packageModel.get("oldPid"), - cnResolveUrl = this.getCnURI(); - - //Get a list of the model pids that should be aggregated by this package - var idsFromModel = []; - this.each(function (packageMember) { - //If this object isn't done uploading, don't aggregate it. - //Or if it failed to upload, don't aggregate it. - //But if the system metadata failed to update, it can still be aggregated. + // Define the namespaces + const ORE = this.rdf.Namespace(this.namespaces.ORE); + const CITO = this.rdf.Namespace(this.namespaces.CITO); + const DC = this.rdf.Namespace(this.namespaces.DC); + const DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS); + const FOAF = this.rdf.Namespace(this.namespaces.FOAF); + const RDF = this.rdf.Namespace(this.namespaces.RDF); + const XSD = this.rdf.Namespace(this.namespaces.XSD); + + // Get the pid of this package - depends on whether we are updating or creating a resource map + const pid = this.packageModel.get("id"); + const oldPid = this.packageModel.get("oldPid"); + let cnResolveUrl = this.getCnURI(); + + // Get a list of the model pids that should be aggregated by this package + let idsFromModel = []; + this.each((packageMember) => { + // If this object isn't done uploading, don't aggregate it. + // Or if it failed to upload, don't aggregate it. + // But if the system metadata failed to update, it can still be aggregated. if ( packageMember.get("uploadStatus") !== "p" || packageMember.get("uploadStatus") !== "e" || @@ -2656,7 +2657,7 @@ define([ this.idsToAggregate = idsFromModel; - //Update the pids in the RDF graph only if we are updating the resource map with a new pid + // Update the pids in the RDF graph only if we are updating the resource map with a new pid if (!this.packageModel.isNew()) { // Remove all describes/isDescribedBy statements (they'll be rebuilt) this.dataPackageGraph.removeMany( @@ -2674,7 +2675,7 @@ define([ undefined, ); - //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph + // Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph oldPidVariations = [ oldPid, encodeURIComponent(oldPid), @@ -2683,17 +2684,17 @@ define([ this.getURIFromRDF(oldPid), ]; - //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph - var idsFromXML = []; + // Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph + const idsFromXML = []; - var identifierStatements = this.dataPackageGraph.statementsMatching( + const identifierStatements = this.dataPackageGraph.statementsMatching( undefined, DCTERMS("identifier"), undefined, ); _.each( identifierStatements, - function (statement) { + (statement) => { idsFromXML.push( statement.object.value, encodeURIComponent(statement.object.value), @@ -2704,23 +2705,23 @@ define([ this, ); - //Get all the child package ids - var childPackages = this.packageModel.get("childPackages"); - if (typeof childPackages == "object") { + // Get all the child package ids + const childPackages = this.packageModel.get("childPackages"); + if (typeof childPackages === "object") { idsFromModel = _.union(idsFromModel, Object.keys(childPackages)); } - //Find the difference between the model IDs and the XML IDs to get a list of added members - var addedIds = _.without( + // Find the difference between the model IDs and the XML IDs to get a list of added members + const addedIds = _.without( _.difference(idsFromModel, idsFromXML), oldPidVariations, ); - //Start an array to track all the member id variations - var allMemberIds = idsFromModel; + // Start an array to track all the member id variations + const allMemberIds = idsFromModel; - //Add the ids with the CN Resolve URLs - _.each(idsFromModel, function (id) { + // Add the ids with the CN Resolve URLs + _.each(idsFromModel, (id) => { allMemberIds.push( cnResolveUrl + encodeURIComponent(id), cnResolveUrl + id, @@ -2728,27 +2729,27 @@ define([ ); }); - //Find the identifier statement in the resource map - var idNode = this.rdf.lit(oldPid); - var idStatements = this.dataPackageGraph.statementsMatching( + // Find the identifier statement in the resource map + const idNode = this.rdf.lit(oldPid); + const idStatements = this.dataPackageGraph.statementsMatching( undefined, undefined, idNode, ); - //Change all the resource map identifier literal node in the RDF graph + // Change all the resource map identifier literal node in the RDF graph if (idStatements.length) { - var idStatement = idStatements[0]; + const idStatement = idStatements[0]; - //Remove the identifier statement + // Remove the identifier statement try { this.dataPackageGraph.remove(idStatement); } catch (error) { console.log(error); } - //Replace the id in the subject URI with the new id - var newRMapURI = ""; + // Replace the id in the subject URI with the new id + let newRMapURI = ""; if (idStatement.subject.value.indexOf(oldPid) > -1) { newRMapURI = idStatement.subject.value.replace(oldPid, pid); } else if ( @@ -2760,10 +2761,10 @@ define([ ); } - //Create resource map nodes for the subject and object - var rMapNode = this.rdf.sym(newRMapURI), - rMapIdNode = this.rdf.lit(pid); - //Add the triple for the resource map id + // Create resource map nodes for the subject and object + var rMapNode = this.rdf.sym(newRMapURI); + const rMapIdNode = this.rdf.lit(pid); + // Add the triple for the resource map id this.dataPackageGraph.add( rMapNode, DCTERMS("identifier"), @@ -2771,8 +2772,8 @@ define([ ); } - //Get all the isAggregatedBy statements - var aggByStatements = $.extend( + // Get all the isAggregatedBy statements + const aggByStatements = $.extend( true, [], this.dataPackageGraph.statementsMatching( @@ -2797,16 +2798,16 @@ define([ _.each( oldPidVariations, function (oldPid) { - //Create a node for the old aggregation using this pid variation - aggregationNode = this.rdf.sym(oldPid + "#aggregation"); - var aggregationLitNode = this.rdf.lit( - oldPid + "#aggregation", + // Create a node for the old aggregation using this pid variation + aggregationNode = this.rdf.sym(`${oldPid}#aggregation`); + const aggregationLitNode = this.rdf.lit( + `${oldPid}#aggregation`, "", XSD("anyURI"), ); - //Get all the triples where the old aggregation is the subject - var aggregationSubjStatements = _.union( + // Get all the triples where the old aggregation is the subject + const aggregationSubjStatements = _.union( this.dataPackageGraph.statementsMatching(aggregationNode), this.dataPackageGraph.statementsMatching(aggregationLitNode), ); @@ -2815,18 +2816,17 @@ define([ _.each( aggregationSubjStatements, function (statement) { - //Clone the subject + // Clone the subject subjectClone = this.cloneNode(statement.subject); - //Clone the predicate + // Clone the predicate predicateClone = this.cloneNode(statement.predicate); - //Clone the object + // Clone the object objectClone = this.cloneNode(statement.object); - //Set the subject value to the new aggregation id - subjectClone.value = - this.getURIFromRDF(pid) + "#aggregation"; + // Set the subject value to the new aggregation id + subjectClone.value = `${this.getURIFromRDF(pid)}#aggregation`; - //Add a new statement with the new aggregation subject but the same predicate and object + // Add a new statement with the new aggregation subject but the same predicate and object this.dataPackageGraph.add( subjectClone, predicateClone, @@ -2836,12 +2836,12 @@ define([ this, ); - //Remove the old aggregation statements from the graph + // Remove the old aggregation statements from the graph this.dataPackageGraph.removeMany(aggregationNode); } // Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID - var aggregationObjStatements = _.union( + const aggregationObjStatements = _.union( this.dataPackageGraph.statementsMatching( undefined, undefined, @@ -2858,16 +2858,15 @@ define([ _.each( aggregationObjStatements, function (statement) { - //Clone the subject, object, and predicate + // Clone the subject, object, and predicate subjectClone = this.cloneNode(statement.subject); predicateClone = this.cloneNode(statement.predicate); objectClone = this.cloneNode(statement.object); - //Set the object to the new aggregation pid - objectClone.value = - this.getURIFromRDF(pid) + "#aggregation"; + // Set the object to the new aggregation pid + objectClone.value = `${this.getURIFromRDF(pid)}#aggregation`; - //Add the statement with the old subject and predicate but new aggregation object + // Add the statement with the old subject and predicate but new aggregation object this.dataPackageGraph.add( subjectClone, predicateClone, @@ -2877,7 +2876,7 @@ define([ this, ); - //Remove all the old aggregation statements from the graph + // Remove all the old aggregation statements from the graph this.dataPackageGraph.removeMany( undefined, undefined, @@ -2886,8 +2885,8 @@ define([ } // Change all the resource map subject nodes in the RDF graph - var rMapNode = this.rdf.sym(this.getURIFromRDF(oldPid)); - var rMapStatements = $.extend( + const rMapNode = this.rdf.sym(this.getURIFromRDF(oldPid)); + const rMapStatements = $.extend( true, [], this.dataPackageGraph.statementsMatching(rMapNode), @@ -2906,13 +2905,13 @@ define([ objectClone.value = new Date().toISOString(); } - //Update the subject to the new pid + // Update the subject to the new pid subjectClone.value = this.getURIFromRDF(pid); - //Remove the old resource map statement + // Remove the old resource map statement this.dataPackageGraph.remove(statement); - //Add the statement with the new subject pid, but the same predicate and object + // Add the statement with the new subject pid, but the same predicate and object this.dataPackageGraph.add( subjectClone, predicateClone, @@ -2929,15 +2928,15 @@ define([ this.dataPackageGraph.add( this.rdf.sym(this.getURIFromRDF(pid)), ORE("describes"), - this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation"), + this.rdf.sym(`${this.getURIFromRDF(pid)}#aggregation`), ); this.dataPackageGraph.add( - this.rdf.sym(this.getURIFromRDF(pid) + "#aggregation"), + this.rdf.sym(`${this.getURIFromRDF(pid)}#aggregation`), ORE("isDescribedBy"), this.rdf.sym(this.getURIFromRDF(pid)), ); - //Add nodes for new package members + // Add nodes for new package members _.each( addedIds, function (id) { @@ -2950,19 +2949,19 @@ define([ this.dataPackageGraph = this.rdf.graph(); cnResolveUrl = this.getCnURI(); - //Create a resource map node + // Create a resource map node var rMapNode = this.rdf.sym(this.getURIFromRDF(this.packageModel.id)); - //Create an aggregation node + // Create an aggregation node var aggregationNode = this.rdf.sym( - this.getURIFromRDF(this.packageModel.id) + "#aggregation", + `${this.getURIFromRDF(this.packageModel.id)}#aggregation`, ); // Describe the resource map with a Creator - var creatorNode = this.rdf.blankNode(); - var creatorName = this.rdf.lit( - (MetacatUI.appUserModel.get("firstName") || "") + - " " + - (MetacatUI.appUserModel.get("lastName") || ""), + const creatorNode = this.rdf.blankNode(); + const creatorName = this.rdf.lit( + `${MetacatUI.appUserModel.get("firstName") || ""} ${ + MetacatUI.appUserModel.get("lastName") || "" + }`, "", XSD("string"), ); @@ -2988,7 +2987,11 @@ define([ ORE("describes"), aggregationNode, ); - var idLiteral = this.rdf.lit(this.packageModel.id, "", XSD("string")); + const idLiteral = this.rdf.lit( + this.packageModel.id, + "", + XSD("string"), + ); this.dataPackageGraph.add(rMapNode, DCTERMS("identifier"), idLiteral); // Describe the aggregation @@ -3014,7 +3017,7 @@ define([ // is not the subject of any other statements. this.removeOrphanedBlankNodes(); - var xmlString = serializer.statementsToXML( + const xmlString = serializer.statementsToXML( this.dataPackageGraph.statements, ); @@ -3023,7 +3026,7 @@ define([ // Clone an rdflib.js Node by creaing a new node based on the // original node RDF term type and data type. - cloneNode: function (nodeToClone) { + cloneNode(nodeToClone) { switch (nodeToClone.termType) { case "NamedNode": return this.rdf.sym(nodeToClone.value); @@ -3036,13 +3039,13 @@ define([ undefined, nodeToClone.datatype, ); - } else { - return this.rdf.literal(nodeToClone.value); } + return this.rdf.literal(nodeToClone.value); + break; case "BlankNode": - //Blank nodes don't need to be cloned - return nodeToClone; //(this.rdf.blankNode(nodeToClone.value)); + // Blank nodes don't need to be cloned + return nodeToClone; // (this.rdf.blankNode(nodeToClone.value)); break; case "Collection": // TODO: construct a list of nodes for this term type. @@ -3050,30 +3053,30 @@ define([ break; default: console.log( - "ERROR: unknown node type to clone: " + nodeToClone.termType, + `ERROR: unknown node type to clone: ${nodeToClone.termType}`, ); } }, // Adds a new object to the resource map RDF graph - addToAggregation: function (id) { + addToAggregation(id) { // Initialize the namespaces - var ORE = this.rdf.Namespace(this.namespaces.ORE), - DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS), - XSD = this.rdf.Namespace(this.namespaces.XSD), - CITO = this.rdf.Namespace(this.namespaces.CITO); + const ORE = this.rdf.Namespace(this.namespaces.ORE); + const DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS); + const XSD = this.rdf.Namespace(this.namespaces.XSD); + const CITO = this.rdf.Namespace(this.namespaces.CITO); // Create a node for this object, the identifier, the resource map, and the aggregation - var objectNode = this.rdf.sym(this.getURIFromRDF(id)), - rMapURI = this.getURIFromRDF(this.packageModel.get("id")), - mapNode = this.rdf.sym(rMapURI), - aggNode = this.rdf.sym(rMapURI + "#aggregation"), - idNode = this.rdf.literal(id, undefined, XSD("string")), - idStatements = [], - aggStatements = [], - aggByStatements = [], - documentsStatements = [], - isDocumentedByStatements = []; + const objectNode = this.rdf.sym(this.getURIFromRDF(id)); + const rMapURI = this.getURIFromRDF(this.packageModel.get("id")); + const mapNode = this.rdf.sym(rMapURI); + const aggNode = this.rdf.sym(`${rMapURI}#aggregation`); + const idNode = this.rdf.literal(id, undefined, XSD("string")); + let idStatements = []; + let aggStatements = []; + let aggByStatements = []; + let documentsStatements = []; + let isDocumentedByStatements = []; // Add the statement: this object isAggregatedBy the resource map aggregation aggByStatements = this.dataPackageGraph.statementsMatching( @@ -3106,9 +3109,9 @@ define([ } // Find the metadata doc that describes this object - var model = this.findWhere({ id: id }), - isDocBy = model.get("isDocumentedBy"), - documents = model.get("documents"); + const model = this.findWhere({ id }); + const isDocBy = model.get("isDocumentedBy"); + const documents = model.get("documents"); // Deal with Solr indexing bug where metadata-only packages must "document" themselves if (isDocBy.length === 0 && documents.length === 0) { @@ -3118,17 +3121,17 @@ define([ // If this object is documented by any metadata... if (isDocBy && isDocBy.length) { // Get the ids of all the metadata objects in this package - var metadataInPackage = _.compact( - _.map(this.models, function (m) { - if (m.get("formatType") == "METADATA") return m; - }), - ), - metadataInPackageIDs = _.each(metadataInPackage, function (m) { - return m.get("id"); - }); + const metadataInPackage = _.compact( + _.map(this.models, (m) => { + if (m.get("formatType") == "METADATA") return m; + }), + ); + const metadataInPackageIDs = _.each(metadataInPackage, (m) => + m.get("id"), + ); // Find the metadata IDs that are in this package that also documents this data object - var metadataIds = Array.isArray(isDocBy) + let metadataIds = Array.isArray(isDocBy) ? _.intersection(metadataInPackageIDs, isDocBy) : _.intersection(metadataInPackageIDs, [isDocBy]); @@ -3136,13 +3139,13 @@ define([ // then we should check if it's documented by an obsoleted pid. If so, // we'll want to change that so it's documented by a current metadata. if (metadataIds.length == 0) { - for (var i = 0; i < metadataInPackage.length; i++) { - //If the previous version of this metadata documents this data, + for (let i = 0; i < metadataInPackage.length; i++) { + // If the previous version of this metadata documents this data, if (_.contains(isDocBy, metadataInPackage[i].get("obsoletes"))) { - //Save the metadata id for serialization + // Save the metadata id for serialization metadataIds = [metadataInPackage[i].get("id")]; - //Exit the for loop + // Exit the for loop break; } } @@ -3152,19 +3155,19 @@ define([ _.each( metadataIds, function (metaId) { - //Create the named nodes and statements - var dataNode = this.rdf.sym(this.getURIFromRDF(id)), - metadataNode = this.rdf.sym(this.getURIFromRDF(metaId)), - isDocByStatement = this.rdf.st( - dataNode, - CITO("isDocumentedBy"), - metadataNode, - ), - documentsStatement = this.rdf.st( - metadataNode, - CITO("documents"), - dataNode, - ); + // Create the named nodes and statements + const dataNode = this.rdf.sym(this.getURIFromRDF(id)); + const metadataNode = this.rdf.sym(this.getURIFromRDF(metaId)); + const isDocByStatement = this.rdf.st( + dataNode, + CITO("isDocumentedBy"), + metadataNode, + ); + const documentsStatement = this.rdf.st( + metadataNode, + CITO("documents"), + dataNode, + ); // Add the statements documentsStatements = this.dataPackageGraph.statementsMatching( @@ -3192,20 +3195,20 @@ define([ // If this object documents a data object if (documents && documents.length) { // Create a literal node for it - var metadataNode = this.rdf.sym(this.getURIFromRDF(id)); + const metadataNode = this.rdf.sym(this.getURIFromRDF(id)); _.each( documents, function (dataID) { - //Make sure the id is one that will be aggregated + // Make sure the id is one that will be aggregated if (_.contains(this.idsToAggregate, dataID)) { - //Find the identifier statement for this data object - var dataURI = this.getURIFromRDF(dataID); + // Find the identifier statement for this data object + const dataURI = this.getURIFromRDF(dataID); - //Create a data node using the exact way the identifier URI is written - var dataNode = this.rdf.sym(dataURI); + // Create a data node using the exact way the identifier URI is written + const dataNode = this.rdf.sym(dataURI); - //Get the statements for data isDocumentedBy metadata + // Get the statements for data isDocumentedBy metadata isDocumentedByStatements = this.dataPackageGraph.statementsMatching( dataNode, @@ -3213,34 +3216,34 @@ define([ metadataNode, ); - //If that statement is not in the RDF already... + // If that statement is not in the RDF already... if (isDocumentedByStatements.length < 1) { // Create a statement: This data is documented by this metadata - var isDocByStatement = this.rdf.st( + const isDocByStatement = this.rdf.st( dataNode, CITO("isDocumentedBy"), metadataNode, ); - //Add the "isDocumentedBy" statement + // Add the "isDocumentedBy" statement this.dataPackageGraph.add(isDocByStatement); } - //Get the statements for metadata documents data + // Get the statements for metadata documents data documentsStatements = this.dataPackageGraph.statementsMatching( metadataNode, CITO("documents"), dataNode, ); - //If that statement is not in the RDF already... + // If that statement is not in the RDF already... if (documentsStatements.length < 1) { // Create a statement: This metadata documents data - var documentsStatement = this.rdf.st( + const documentsStatement = this.rdf.st( metadataNode, CITO("documents"), dataNode, ); - //Add the "isDocumentedBy" statement + // Add the "isDocumentedBy" statement this.dataPackageGraph.add(documentsStatement); } } @@ -3253,26 +3256,26 @@ define([ /* * Removes an object from the aggregation in the RDF graph */ - removeFromAggregation: function (id) { + removeFromAggregation(id) { if (id.indexOf(this.dataPackageGraph.cnResolveUrl) == -1) { id = this.getURIFromRDF(id); } // Create a literal node for the removed object - var removedObjNode = this.rdf.sym(id), - // Get the statements from the RDF where the removed object is the subject or object - statements = $.extend( - true, - [], - _.union( - this.dataPackageGraph.statementsMatching( - undefined, - undefined, - removedObjNode, - ), - this.dataPackageGraph.statementsMatching(removedObjNode), + const removedObjNode = this.rdf.sym(id); + // Get the statements from the RDF where the removed object is the subject or object + const statements = $.extend( + true, + [], + _.union( + this.dataPackageGraph.statementsMatching( + undefined, + undefined, + removedObjNode, ), - ); + this.dataPackageGraph.statementsMatching(removedObjNode), + ), + ); // Remove all the statements mentioning this object try { @@ -3286,56 +3289,54 @@ define([ * Finds the given identifier in the RDF graph and returns the subject * URI of that statement. This is useful when adding additional statements * to the RDF graph for an object that already exists in that graph. - * * @param {string} id - The identifier to search for - * @return {string} - The full URI for the given id as it exists in the RDF. + * @returns {string} - The full URI for the given id as it exists in the RDF. */ - getURIFromRDF: function (id) { - //Exit if no id was given + getURIFromRDF(id) { + // Exit if no id was given if (!id) return ""; - //Create a literal node with the identifier as the value - var XSD = this.rdf.Namespace(this.namespaces.XSD), - DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS), - idNode = this.rdf.literal(id, undefined, XSD("string")), - //Find the identifier statements for the given id - idStatements = this.dataPackageGraph.statementsMatching( - undefined, - DCTERMS("identifier"), - idNode, - ); + // Create a literal node with the identifier as the value + const XSD = this.rdf.Namespace(this.namespaces.XSD); + const DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS); + const idNode = this.rdf.literal(id, undefined, XSD("string")); + // Find the identifier statements for the given id + const idStatements = this.dataPackageGraph.statementsMatching( + undefined, + DCTERMS("identifier"), + idNode, + ); - //If this object has an identifier statement, + // If this object has an identifier statement, if (idStatements.length > 0) { - //Return the subject of the statement + // Return the subject of the statement return idStatements[0].subject.value; - } else { - return this.getCnURI() + encodeURIComponent(id); } + return this.getCnURI() + encodeURIComponent(id); }, /** * Parses out the CN Resolve URL from the existing statements in the RDF * or if not found in the RDF, from the app configuration. - * - * @return {string} - The CN resolve URL + * @returns {string} - The CN resolve URL */ - getCnURI: function () { - //If the CN resolve URL was already found, return it + getCnURI() { + // If the CN resolve URL was already found, return it if (this.dataPackageGraph.cnResolveUrl) { return this.dataPackageGraph.cnResolveUrl; - } else if (this.packageModel.get("oldPid")) { - //Find the identifier statement for the resource map in the RDF graph - var idNode = this.rdf.lit(this.packageModel.get("oldPid")), - idStatements = this.dataPackageGraph.statementsMatching( - undefined, - undefined, - idNode, - ), - idStatement = idStatements.length ? idStatements[0] : null; + } + if (this.packageModel.get("oldPid")) { + // Find the identifier statement for the resource map in the RDF graph + const idNode = this.rdf.lit(this.packageModel.get("oldPid")); + const idStatements = this.dataPackageGraph.statementsMatching( + undefined, + undefined, + idNode, + ); + const idStatement = idStatements.length ? idStatements[0] : null; if (idStatement) { - //Parse the CN resolve URL from the statement subject URI + // Parse the CN resolve URL from the statement subject URI this.dataPackageGraph.cnResolveUrl = idStatement.subject.value.substring( 0, @@ -3358,16 +3359,16 @@ define([ MetacatUI.appModel.get("resolveServiceUrl"); } - //Return the CN resolve URL + // Return the CN resolve URL return this.dataPackageGraph.cnResolveUrl; }, /** * Checks if this resource map has had any changes that requires an update */ - needsUpdate: function () { - //Check for changes to the list of aggregated members - var ids = this.pluck("id"); + needsUpdate() { + // Check for changes to the list of aggregated members + const ids = this.pluck("id"); if ( this.originalMembers.length != ids.length || _.intersection(this.originalMembers, ids).length != ids.length @@ -3377,18 +3378,18 @@ define([ // If the provenance relationships have been updated, then the resource map // needs to be updated. if (this.provEdits.length) return true; - //Check for changes to the isDocumentedBy relationships - var isDifferent = false, - i = 0; + // Check for changes to the isDocumentedBy relationships + let isDifferent = false; + let i = 0; - //Keep going until we find a difference + // Keep going until we find a difference while (!isDifferent && i < this.length) { - //Get the original isDocBy relationships from the resource map, and the new isDocBy relationships from the models - var isDocBy = this.models[i].get("isDocumentedBy"), - id = this.models[i].get("id"), - origIsDocBy = this.originalIsDocBy[id]; + // Get the original isDocBy relationships from the resource map, and the new isDocBy relationships from the models + let isDocBy = this.models[i].get("isDocumentedBy"); + const id = this.models[i].get("id"); + let origIsDocBy = this.originalIsDocBy[id]; - //Make sure they are both formatted as arrays for these checks + // Make sure they are both formatted as arrays for these checks isDocBy = _.uniq( _.flatten(_.compact(Array.isArray(isDocBy) ? isDocBy : [isDocBy])), ); @@ -3400,18 +3401,18 @@ define([ ), ); - //Remove the id of this object so metadata can not be "isDocumentedBy" itself + // Remove the id of this object so metadata can not be "isDocumentedBy" itself isDocBy = _.without(isDocBy, id); origIsDocBy = _.without(origIsDocBy, id); - //Simply check if they are the same + // Simply check if they are the same if (origIsDocBy === isDocBy) { i++; continue; } - //Are the number of relationships different? + // Are the number of relationships different? else if (isDocBy.length != origIsDocBy.length) isDifferent = true; - //Are the arrays the same? + // Are the arrays the same? else if ( _.intersection(isDocBy, origIsDocBy).length != origIsDocBy.length ) @@ -3426,39 +3427,37 @@ define([ /* * Returns an array of the models that are in the queue or in progress of uploading */ - getQueue: function () { - return this.filter(function (m) { - return m.get("uploadStatus") == "q" || m.get("uploadStatus") == "p"; - }); + getQueue() { + return this.filter( + (m) => m.get("uploadStatus") == "q" || m.get("uploadStatus") == "p", + ); }, /* * Adds a DataONEObject model to this DataPackage collection */ - addNewModel: function (model) { - //Check that this collection doesn't already contain this model + addNewModel(model) { + // Check that this collection doesn't already contain this model if (!this.contains(model)) { this.add(model); - //Mark this data package as changed + // Mark this data package as changed this.packageModel.set("changed", true); this.packageModel.trigger("change:changed"); } }, - handleAdd: function (dataONEObject) { - var metadataModel = this.find(function (m) { - return m.get("type") == "Metadata"; - }); + handleAdd(dataONEObject) { + const metadataModel = this.find((m) => m.get("type") == "Metadata"); // Append to or create a new documents list if (metadataModel) { if (!Array.isArray(metadataModel.get("documents"))) { metadataModel.set("documents", [dataONEObject.id]); - } else { - if (!_.contains(metadataModel.get("documents"), dataONEObject.id)) - metadataModel.get("documents").push(dataONEObject.id); - } + } else if ( + !_.contains(metadataModel.get("documents"), dataONEObject.id) + ) + metadataModel.get("documents").push(dataONEObject.id); // Create an EML Entity for this DataONE Object if there isn't one already if ( @@ -3475,7 +3474,7 @@ define([ this.setLoadingFiles(dataONEObject); - //Save a reference to this DataPackage + // Save a reference to this DataPackage // If the collections attribute is an array /* if( Array.isArray(dataONEObject.get("collections")) ){ //Add this DataPackage to the collections list if it's not already in the array @@ -3508,25 +3507,25 @@ define([ * Fetches this DataPackage from the Solr index by using a SolrResults collection * and merging the models in. */ - fetchFromIndex: function () { - if (typeof this.solrResults == "undefined" || !this.solrResults) { + fetchFromIndex() { + if (typeof this.solrResults === "undefined" || !this.solrResults) { this.solrResults = new SolrResults(); } - //If no query is set yet, use the FilterModel associated with this DataPackage + // If no query is set yet, use the FilterModel associated with this DataPackage if (!this.solrResults.currentquery.length) { this.solrResults.currentquery = this.filterModel.getQuery(); } this.listenToOnce(this.solrResults, "reset", function (solrResults) { - //Merge the SolrResults into this collection + // Merge the SolrResults into this collection this.mergeModels(solrResults.models); - //Trigger the fetch as complete + // Trigger the fetch as complete this.trigger("complete"); }); - //Query the index for this data package + // Query the index for this data package this.solrResults.query(); }, @@ -3534,14 +3533,13 @@ define([ * Merge the attributes of other models into the corresponding models in this collection. * This should be used when merging models of other types (e.g. SolrResult) that represent the same * object that the DataONEObject models in the collection represent. - * * @param {Backbone.Model[]} otherModels - the other models to merge with the models in this collection * @param {string[]} [fieldsToMerge] - If specified, only these fields will be extracted from the otherModels */ - mergeModels: function (otherModels, fieldsToMerge) { - //If no otherModels are given, exit the function since there is nothing to merge + mergeModels(otherModels, fieldsToMerge) { + // If no otherModels are given, exit the function since there is nothing to merge if ( - typeof otherModels == "undefined" || + typeof otherModels === "undefined" || !otherModels || !otherModels.length ) { @@ -3551,43 +3549,43 @@ define([ _.each( otherModels, function (otherModel) { - //Get the model from this collection that matches ids with the other model - var modelInDataPackage = this.findWhere({ + // Get the model from this collection that matches ids with the other model + const modelInDataPackage = this.findWhere({ id: otherModel.get("id"), }); - //If a match is found, + // If a match is found, if (modelInDataPackage) { - var valuesFromOtherModel; + let valuesFromOtherModel; - //If specific fields to merge are given, get the values for those from the other model + // If specific fields to merge are given, get the values for those from the other model if (fieldsToMerge && fieldsToMerge.length) { valuesFromOtherModel = _.pick( otherModel.toJSON(), fieldsToMerge, ); } - //If no specific fields are given, merge (almost) all others + // If no specific fields are given, merge (almost) all others else { - //Get the default values for this model type - var otherModelDefaults = otherModel.defaults, - //Get a JSON object of all the attributes on this model - otherModelAttr = otherModel.toJSON(), - //Start an array of attributes to omit during the merge - omitKeys = []; - - _.each(otherModelAttr, function (val, key) { - //If this model's attribute is the default, don't set it on our DataONEObject model + // Get the default values for this model type + const otherModelDefaults = otherModel.defaults; + // Get a JSON object of all the attributes on this model + const otherModelAttr = otherModel.toJSON(); + // Start an array of attributes to omit during the merge + const omitKeys = []; + + _.each(otherModelAttr, (val, key) => { + // If this model's attribute is the default, don't set it on our DataONEObject model // because whatever value is in the DataONEObject model is better information than the default // value of the other model. if (otherModelDefaults[key] === val) omitKeys.push(key); }); - //Remove the properties that are still the default value + // Remove the properties that are still the default value valuesFromOtherModel = _.omit(otherModelAttr, omitKeys); } - //Set the values from the other model on the model in this collection + // Set the values from the other model on the model in this collection modelInDataPackage.set(valuesFromOtherModel); } }, @@ -3598,24 +3596,24 @@ define([ /** * Update the relationships in this resource map when its been udpated */ - updateRelationships: function () { - //Get the old id - var oldId = this.packageModel.get("oldPid"); + updateRelationships() { + // Get the old id + const oldId = this.packageModel.get("oldPid"); if (!oldId) return; - //Update the resource map list + // Update the resource map list this.each(function (m) { - var updateRMaps = _.without(m.get("resourceMap"), oldId); + const updateRMaps = _.without(m.get("resourceMap"), oldId); updateRMaps.push(this.packageModel.get("id")); m.set("resourceMap", updateRMaps); }, this); }, - saveReference: function (model) { - //Save a reference to this collection in the model - var currentCollections = model.get("collections"); + saveReference(model) { + // Save a reference to this collection in the model + const currentCollections = model.get("collections"); if (currentCollections.length > 0) { currentCollections.push(this); model.set("collections", _.uniq(currentCollections)); @@ -3630,16 +3628,15 @@ define([ * How this works is likely to change in the future. * * Closely tied to the AccessPolicyView.broadcast property. - * * @param {AccessPolicy} accessPolicy - The accessPolicy to * broadcast */ - broadcastAccessPolicy: function (accessPolicy) { + broadcastAccessPolicy(accessPolicy) { if (!accessPolicy) { return; } - var policy = _.clone(accessPolicy); + const policy = _.clone(accessPolicy); this.packageModel.set("accessPolicy", policy); // Stop now if the package is new because we don't want force @@ -3648,12 +3645,12 @@ define([ return; } - this.packageModel.on("sysMetaUpdateError", function (e) { + this.packageModel.on("sysMetaUpdateError", (e) => { // Show a generic error. Any errors at this point are things the // user can't really recover from. i.e., we've already checked // that the user has changePermission perms and we've already // re-tried the request a few times - var message = + const message = "There was an error sharing your dataset. Not all of your changes were applied."; // TODO: Is this really the right way to hook into the editor's @@ -3671,30 +3668,30 @@ define([ * the number of loading files will be calcualted and set on the packageModel. * @since 2.17.1 */ - setLoadingFiles: function (dataONEObject) { - //Set the number of loading files and the isLoadingFiles flag - let numLoadingFiles = + setLoadingFiles(dataONEObject) { + // Set the number of loading files and the isLoadingFiles flag + const numLoadingFiles = this.where({ uploadStatus: "l" }).length + this.where({ uploadStatus: "p" }).length; this.packageModel.set({ isLoadingFiles: numLoadingFiles > 0, - numLoadingFiles: numLoadingFiles, + numLoadingFiles, }); if (dataONEObject) { - //Listen to the upload status to update the flag + // Listen to the upload status to update the flag this.listenTo(dataONEObject, "change:uploadStatus", function () { - //If the object is done being successfully saved + // If the object is done being successfully saved if (dataONEObject.get("uploadStatus") == "c") { - let numLoadingFiles = + const numLoadingFiles = this.where({ uploadStatus: "l" }).length + this.where({ uploadStatus: "p" }).length; - //If all models in this DataPackage have finished loading, then mark the loading as complete + // If all models in this DataPackage have finished loading, then mark the loading as complete if (!numLoadingFiles) { this.packageModel.set({ isLoadingFiles: false, - numLoadingFiles: numLoadingFiles, + numLoadingFiles, }); } else { this.packageModel.set("numLoadingFiles", numLoadingFiles); @@ -3710,13 +3707,12 @@ define([ * @returns object with PIDs as key and atLocation paths as values * @since 2.28.0 */ - getAtLocation: function () { + getAtLocation() { return this.atLocationObject; }, /** * Get the absolute path from a relative path, handling '~', '..', and '.'. - * * @param {string} relativePath - The relative path to be converted to an absolute path. * @returns {string} - The absolute path after processing '~', '..', and '.'. * If the result is empty, returns '/'. diff --git a/src/js/common/Utilities.js b/src/js/common/Utilities.js index b48b2dc21..c1ac81aaa 100644 --- a/src/js/common/Utilities.js +++ b/src/js/common/Utilities.js @@ -1,6 +1,11 @@ "use strict"; define([], () => { + const KIBIBYTE = 1024; + const MEBIBYTE = KIBIBYTE * 1024; + const GIBIBYTE = MEBIBYTE * 1024; + const TEBIBYTE = GIBIBYTE * 1024; + /** * @namespace Utilities * @description A generic utility object that contains functions used throughout MetacatUI to perform useful functions, @@ -214,6 +219,35 @@ define([], () => { return json; }, + + /** + * Convert number of bytes into human readable format + * @param integer bytes Number of bytes to convert + * @param integer precision Number of digits after the decimal separator + * @param bytes + * @param precision + * @returns string + */ + bytesToSize(bytes, precision = 0) { + if (typeof bytes === "undefined") return `0 B`; + + if (bytes >= 0 && bytes < KIBIBYTE) { + return `${bytes} B`; + } + if (bytes >= KIBIBYTE && bytes < MEBIBYTE) { + return `${(bytes / KIBIBYTE).toFixed(precision)} KiB`; + } + if (bytes >= MEBIBYTE && bytes < GIBIBYTE) { + return `${(bytes / MEBIBYTE).toFixed(precision)} MiB`; + } + if (bytes >= GIBIBYTE && bytes < TEBIBYTE) { + return `${(bytes / GIBIBYTE).toFixed(precision)} GiB`; + } + if (bytes >= TEBIBYTE) { + return `${(bytes / TEBIBYTE).toFixed(precision)} TiB`; + } + return `${bytes} B`; + }, }; return Utilities; diff --git a/src/js/models/DataONEObject.js b/src/js/models/DataONEObject.js index f9155fdb0..341fd33c6 100644 --- a/src/js/models/DataONEObject.js +++ b/src/js/models/DataONEObject.js @@ -6,19 +6,20 @@ define([ "he", "collections/AccessPolicy", "collections/ObjectFormats", + "common/Utilities", "md5", -], function ($, _, Backbone, uuid, he, AccessPolicy, ObjectFormats, md5) { +], ($, _, Backbone, uuid, he, AccessPolicy, ObjectFormats, Utilities, md5) => { /** - * @class DataONEObject - * @classdesc A DataONEObject represents a DataONE object, such as a data file, + * @class DataONEObject + * @classdesc A DataONEObject represents a DataONE object, such as a data file, a science metadata object, or a resource map. It stores the system metadata attributes for the object, performs updates to the system metadata, and other basic DataONE API functions. This model can be extended to provide specific functionality for different object types, such as the {@link ScienceMetadata} model and the {@link EML211} model. - * @classcategory Models - * @augments Backbone.Model - */ + * @classcategory Models + * @augments Backbone.Model + */ var DataONEObject = Backbone.Model.extend( /** @lends DataONEObject.prototype */ { type: "DataONEObject", @@ -26,7 +27,7 @@ define([ PROV: "http://www.w3.org/ns/prov#", PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#", - defaults: function () { + defaults() { return { // System Metadata attributes serialVersion: null, @@ -38,7 +39,7 @@ define([ checksumAlgorithm: "MD5", submitter: null, rightsHolder: null, - accessPolicy: [], //An array of accessPolicy literal JS objects + accessPolicy: [], // An array of accessPolicy literal JS objects replicationAllowed: null, replicationPolicy: [], obsoletes: null, @@ -62,7 +63,7 @@ define([ readPermission: null, isPublic: null, dateModified: null, - id: "urn:uuid:" + uuid.v4(), + id: `urn:uuid:${uuid.v4()}`, sizeStr: null, type: "", // Data, Metadata, or DataPackage formatType: "", @@ -75,30 +76,30 @@ define([ nodeLevel: 0, // Indicates hierarchy level in the view for indentation sortOrder: 2, // Metadata: 1, Data: 2, DataPackage: 3 synced: false, // True if the full model has been synced - uploadStatus: null, //c=complete, p=in progress, q=queued, e=error, w=warning, no upload status=not in queue + uploadStatus: null, // c=complete, p=in progress, q=queued, e=error, w=warning, no upload status=not in queue uploadProgress: null, - sysMetaUploadStatus: null, //c=complete, p=in progress, q=queued, e=error, l=loading, no upload status=not in queue + sysMetaUploadStatus: null, // c=complete, p=in progress, q=queued, e=error, l=loading, no upload status=not in queue percentLoaded: 0, // Percent the file is read before caclculating the md5 sum uploadFile: null, // The file reference to be uploaded (JS object: File) errorMessage: null, sysMetaErrorCode: null, // The status code given when there is an error updating the system metadata numSaveAttempts: 0, - notFound: false, //Whether or not this object was found in the system + notFound: false, // Whether or not this object was found in the system originalAttrs: [], // An array of original attributes in a DataONEObject changed: false, // If any attributes have been changed, including attrs in nested objects hasContentChanges: false, // If attributes outside of originalAttrs have been changed sysMetaXML: null, // A cached original version of the fetched system metadata document objectXML: null, // A cached version of the object fetched from the server isAuthorized: null, // If the stated permission is authorized by the user - isAuthorized_read: null, //If the user has permission to read - isAuthorized_write: null, //If the user has permission to write - isAuthorized_changePermission: null, //If the user has permission to changePermission - createSeriesId: false, //If true, a seriesId will be created when this object is saved. - collections: [], //References to collections that this model is in - possibleAuthMNs: [], //A list of possible authoritative MNs of this object + isAuthorized_read: null, // If the user has permission to read + isAuthorized_write: null, // If the user has permission to write + isAuthorized_changePermission: null, // If the user has permission to changePermission + createSeriesId: false, // If true, a seriesId will be created when this object is saved. + collections: [], // References to collections that this model is in + possibleAuthMNs: [], // A list of possible authoritative MNs of this object useAltRepo: false, - isLoadingFiles: false, //Only relevant to Resource Map objects. Is true if there is at least one file still loading into the package. - numLoadingFiles: 0, //Only relevant to Resource Map objects. The number of files still loading into the package. + isLoadingFiles: false, // Only relevant to Resource Map objects. Is true if there is at least one file still loading into the package. + numLoadingFiles: 0, // Only relevant to Resource Map objects. The number of files still loading into the package. provSources: [], provDerivations: [], prov_generated: [], @@ -119,13 +120,20 @@ define([ }; }, - initialize: function (attrs, options) { - if (typeof attrs == "undefined") var attrs = {}; + initialize(attrs, options) { + if (typeof attrs === "undefined") var attrs = {}; this.set("accessPolicy", this.createAccessPolicy()); - this.on("change:size", this.bytesToSize); - if (attrs.size) this.bytesToSize(); + const model = this; + this.on("change:size", () => { + const size = Utilities.bytesToSize(model.get("size")); + model.set("sizeStr", size); + }); + if (attrs.size) { + const size = Utilities.bytesToSize(model.get("size")); + model.set("sizeStr", size); + } // Cache an array of original attribute names to help in handleChange() if (this.type == "DataONEObject") @@ -138,10 +146,10 @@ define([ this.on("successSaving", this.updateRelationships); - //Save a reference to this DataONEObject model in the metadataEntity model - //whenever the metadataEntity is set + // Save a reference to this DataONEObject model in the metadataEntity model + // whenever the metadataEntity is set this.on("change:metadataEntity", function () { - var entityMetadataModel = this.get("metadataEntity"); + const entityMetadataModel = this.get("metadataEntity"); if (entityMetadataModel) entityMetadataModel.set("dataONEObject", this); @@ -151,8 +159,8 @@ define([ this.set("synced", true); }); - //Find Member Node object that might be the authoritative MN - //This is helpful when MetacatUI may be displaying content from multiple MNs + // Find Member Node object that might be the authoritative MN + // This is helpful when MetacatUI may be displaying content from multiple MNs this.setPossibleAuthMNs(); }, @@ -161,7 +169,7 @@ define([ * camel-cased sys meta node names (valid in DataONE). * Used during parse() and serialize() */ - nodeNameMap: function () { + nodeNameMap() { return { accesspolicy: "accessPolicy", accessrule: "accessRule", @@ -190,70 +198,67 @@ define([ * Returns the URL string where this DataONEObject can be fetched from or saved to * @returns {string} */ - url: function () { + url() { // With no id, we can't do anything if (!this.get("id") && !this.get("seriesid")) return ""; - //Get the active alternative repository, if one is configured - var activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); + // Get the active alternative repository, if one is configured + const activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); - //Start the base URL string - var baseUrl = ""; + // Start the base URL string + let baseUrl = ""; // Determine if we're updating a new/existing object, // or just its system metadata // New uploads use the object service URL if (this.isNew()) { - //Use the object service URL from the alt repo + // Use the object service URL from the alt repo if (this.get("useAltRepo") && activeAltRepo) { baseUrl = activeAltRepo.objectServiceUrl; } - //If this MetacatUI deployment is pointing to a MN, use the object service URL from the AppModel + // If this MetacatUI deployment is pointing to a MN, use the object service URL from the AppModel else { baseUrl = MetacatUI.appModel.get("objectServiceUrl"); } - //Return the full URL + // Return the full URL return baseUrl; - } else { - if (this.hasUpdates()) { - if (this.get("hasContentChanges")) { - //Use the object service URL from the alt repo - if (this.get("useAltRepo") && activeAltRepo) { - baseUrl = activeAltRepo.objectServiceUrl; - } else { - baseUrl = MetacatUI.appModel.get("objectServiceUrl"); - } - - // Exists on the server, use MN.update() - return baseUrl + encodeURIComponent(this.get("oldPid")); - } else { - //Use the meta service URL from the alt repo - if (this.get("useAltRepo") && activeAltRepo) { - baseUrl = activeAltRepo.metaServiceUrl; - } else { - baseUrl = MetacatUI.appModel.get("metaServiceUrl"); - } - - // Exists on the server, use MN.updateSystemMetadata() - return baseUrl + encodeURIComponent(this.get("id")); - } - } else { - //Use the meta service URL from the alt repo + } + if (this.hasUpdates()) { + if (this.get("hasContentChanges")) { + // Use the object service URL from the alt repo if (this.get("useAltRepo") && activeAltRepo) { - baseUrl = activeAltRepo.metaServiceUrl; + baseUrl = activeAltRepo.objectServiceUrl; } else { - baseUrl = MetacatUI.appModel.get("metaServiceUrl"); + baseUrl = MetacatUI.appModel.get("objectServiceUrl"); } - // Use MN.getSystemMetadata() - return ( - baseUrl + - (encodeURIComponent(this.get("id")) || - encodeURIComponent(this.get("seriesid"))) - ); + // Exists on the server, use MN.update() + return baseUrl + encodeURIComponent(this.get("oldPid")); + } + // Use the meta service URL from the alt repo + if (this.get("useAltRepo") && activeAltRepo) { + baseUrl = activeAltRepo.metaServiceUrl; + } else { + baseUrl = MetacatUI.appModel.get("metaServiceUrl"); } + + // Exists on the server, use MN.updateSystemMetadata() + return baseUrl + encodeURIComponent(this.get("id")); } + // Use the meta service URL from the alt repo + if (this.get("useAltRepo") && activeAltRepo) { + baseUrl = activeAltRepo.metaServiceUrl; + } else { + baseUrl = MetacatUI.appModel.get("metaServiceUrl"); + } + + // Use MN.getSystemMetadata() + return ( + baseUrl + + (encodeURIComponent(this.get("id")) || + encodeURIComponent(this.get("seriesid"))) + ); }, /** @@ -261,29 +266,30 @@ define([ * @returns PackageURL string for this DataONE Object * @since 2.28.0 */ - getPackageURL: function () { - var url = null; + getPackageURL() { + let url = null; // With no id, we can't do anything if (!this.get("id") && !this.get("seriesid")) return url; - //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from + // If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from if ( MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") > -1 && MetacatUI.nodeModel.get("members").length ) { - var source = this.get("datasource"), - node = _.find(MetacatUI.nodeModel.get("members"), { - identifier: source, - }); + const source = this.get("datasource"); + const node = _.find(MetacatUI.nodeModel.get("members"), { + identifier: source, + }); - //If this node has MNRead v2 services... + // If this node has MNRead v2 services... if (node && node.readv2) - url = - node.baseURL + - "/v2/packages/application%2Fbagit-097/" + - encodeURIComponent(this.get("id")); + url = `${ + node.baseURL + }/v2/packages/application%2Fbagit-097/${encodeURIComponent( + this.get("id"), + )}`; } else if (MetacatUI.appModel.get("packageServiceUrl")) url = MetacatUI.appModel.get("packageServiceUrl") + @@ -294,63 +300,53 @@ define([ /** * Overload Backbone.Model.fetch, so that we can set custom options for each fetch() request + * @param options */ - fetch: function (options) { + fetch(options) { if (!options) var options = {}; else var options = _.clone(options); options.url = this.url(); - //If we are using the Solr service to retrieve info about this object, then construct a query - if (typeof options != "undefined" && options.solrService) { - //Get basic information - var query = ""; + // If we are using the Solr service to retrieve info about this object, then construct a query + if (typeof options !== "undefined" && options.solrService) { + // Get basic information + let query = ""; - //Do not search for seriesId when it is not configured in this model/app + // Do not search for seriesId when it is not configured in this model/app if (typeof this.get("seriesid") === "undefined") - query += 'id:"' + encodeURIComponent(this.get("id")) + '"'; - //If there is no seriesid set, then search for pid or sid + query += `id:"${encodeURIComponent(this.get("id"))}"`; + // If there is no seriesid set, then search for pid or sid else if (!this.get("seriesid")) - query += - '(id:"' + - encodeURIComponent(this.get("id")) + - '" OR seriesId:"' + - encodeURIComponent(this.get("id")) + - '")'; - //If a seriesId is specified, then search for that + query += `(id:"${encodeURIComponent( + this.get("id"), + )}" OR seriesId:"${encodeURIComponent(this.get("id"))}")`; + // If a seriesId is specified, then search for that else if (this.get("seriesid") && this.get("id").length > 0) - query += - '(seriesId:"' + - encodeURIComponent(this.get("seriesid")) + - '" AND id:"' + - encodeURIComponent(this.get("id")) + - '")'; - //If only a seriesId is specified, then just search for the most recent version + query += `(seriesId:"${encodeURIComponent( + this.get("seriesid"), + )}" AND id:"${encodeURIComponent(this.get("id"))}")`; + // If only a seriesId is specified, then just search for the most recent version else if (this.get("seriesid") && !this.get("id")) - query += - 'seriesId:"' + - encodeURIComponent(this.get("id")) + - '" -obsoletedBy:*'; - - //The fields to return - var fl = "formatId,formatType,documents,isDocumentedBy,id,seriesId"; - - //Use the Solr query URL - var solrOptions = { - url: - MetacatUI.appModel.get("queryServiceUrl") + - "q=" + - query + - "&fl=" + - fl + - "&wt=json", + query += `seriesId:"${encodeURIComponent( + this.get("id"), + )}" -obsoletedBy:*`; + + // The fields to return + const fl = "formatId,formatType,documents,isDocumentedBy,id,seriesId"; + + // Use the Solr query URL + const solrOptions = { + url: `${MetacatUI.appModel.get( + "queryServiceUrl", + )}q=${query}&fl=${fl}&wt=json`, }; - //Merge with the options passed to this function + // Merge with the options passed to this function var fetchOptions = _.extend(options, solrOptions); - } else if (typeof options != "undefined") { - //Use custom options for retreiving XML - //Merge with the options passed to this function + } else if (typeof options !== "undefined") { + // Use custom options for retreiving XML + // Merge with the options passed to this function var fetchOptions = _.extend( { dataType: "text", @@ -358,37 +354,38 @@ define([ options, ); } else { - //Use custom options for retreiving XML + // Use custom options for retreiving XML var fetchOptions = _.extend({ dataType: "text", }); } - //Add the authorization options + // Add the authorization options fetchOptions = _.extend( fetchOptions, MetacatUI.appUserModel.createAjaxSettings(), ); - //Call Backbone.Model.fetch to retrieve the info + // Call Backbone.Model.fetch to retrieve the info return Backbone.Model.prototype.fetch.call(this, fetchOptions); }, /** * This function is called by Backbone.Model.fetch. * It deserializes the incoming XML from the /meta REST endpoint and converts it into JSON. + * @param response */ - parse: function (response) { + parse(response) { // If the response is XML - if (typeof response == "string" && response.indexOf("<") == 0) { - var responseDoc = $.parseHTML(response), - systemMetadata; + if (typeof response === "string" && response.indexOf("<") == 0) { + const responseDoc = $.parseHTML(response); + let systemMetadata; - //Save the raw XML in case it needs to be used later + // Save the raw XML in case it needs to be used later this.set("sysMetaXML", response); - //Find the XML node for the system metadata - for (var i = 0; i < responseDoc.length; i++) { + // Find the XML node for the system metadata + for (let i = 0; i < responseDoc.length; i++) { if ( responseDoc[i].nodeType == 1 && responseDoc[i].localName.indexOf("systemmetadata") > -1 @@ -398,14 +395,14 @@ define([ } } - //Parse the XML to JSON - var sysMetaValues = this.toJson(systemMetadata); + // Parse the XML to JSON + const sysMetaValues = this.toJson(systemMetadata); - //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code + // Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code _.each( Object.keys(sysMetaValues), function (key) { - var camelCasedKey = this.nodeNameMap()[key]; + const camelCasedKey = this.nodeNameMap()[key]; if (camelCasedKey) { sysMetaValues[camelCasedKey] = sysMetaValues[key]; delete sysMetaValues[key]; @@ -414,14 +411,14 @@ define([ this, ); - //Save the checksum from the system metadata in a separate attribute on the model + // Save the checksum from the system metadata in a separate attribute on the model sysMetaValues.originalChecksum = sysMetaValues.checksum; sysMetaValues.checksum = this.defaults().checksum; - //Save the identifier as the id attribute + // Save the identifier as the id attribute sysMetaValues.id = sysMetaValues.identifier; - //Parse the Access Policy + // Parse the Access Policy if ( this.get("accessPolicy") && AccessPolicy.prototype.isPrototypeOf(this.get("accessPolicy")) @@ -431,7 +428,7 @@ define([ ); sysMetaValues.accessPolicy = this.get("accessPolicy"); } else { - //Create a new AccessPolicy collection, if there isn't one already. + // Create a new AccessPolicy collection, if there isn't one already. sysMetaValues.accessPolicy = this.createAccessPolicy( $(systemMetadata).find("accesspolicy"), ); @@ -440,58 +437,62 @@ define([ return sysMetaValues; // If the response is a list of Solr docs - } else if ( + } + if ( typeof response === "object" && response.response && response.response.docs ) { - //If no objects were found in the index, mark as notFound and exit + // If no objects were found in the index, mark as notFound and exit if (!response.response.docs.length) { this.set("notFound", true); this.trigger("notFound"); return; } - //Get the Solr document (there should be only one) - var doc = response.response.docs[0]; + // Get the Solr document (there should be only one) + const doc = response.response.docs[0]; - //Take out any empty values - _.each(Object.keys(doc), function (field) { + // Take out any empty values + _.each(Object.keys(doc), (field) => { if (!doc[field] && doc[field] !== 0) delete doc[field]; }); - //Remove any erroneous white space from fields + // Remove any erroneous white space from fields this.removeWhiteSpaceFromSolrFields(doc); return doc; } // Default to returning the raw response - else return response; + return response; }, - /** A utility function for converting XML to JSON */ - toJson: function (xml) { + /** + * A utility function for converting XML to JSON + * @param xml + */ + toJson(xml) { // Create the return object - var obj = {}; + let obj = {}; // do children if (xml.hasChildNodes()) { - for (var i = 0; i < xml.childNodes.length; i++) { - var item = xml.childNodes.item(i); + for (let i = 0; i < xml.childNodes.length; i++) { + const item = xml.childNodes.item(i); - //If it's an empty text node, skip it + // If it's an empty text node, skip it if (item.nodeType == 3 && !item.nodeValue.trim()) continue; - //Get the node name - var nodeName = item.localName; + // Get the node name + const nodeName = item.localName; - //If it's a new container node, convert it to JSON and add as a new object attribute - if (typeof obj[nodeName] == "undefined" && item.nodeType == 1) { + // If it's a new container node, convert it to JSON and add as a new object attribute + if (typeof obj[nodeName] === "undefined" && item.nodeType == 1) { obj[nodeName] = this.toJson(item); } - //If it's a new text node, just store the text value and add as a new object attribute + // If it's a new text node, just store the text value and add as a new object attribute else if ( - typeof obj[nodeName] == "undefined" && + typeof obj[nodeName] === "undefined" && item.nodeType == 3 ) { obj = @@ -501,16 +502,16 @@ define([ ? true : item.nodeValue; } - //If this node name is already stored as an object attribute... - else if (typeof obj[nodeName] != "undefined") { - //Cache what we have now - var old = obj[nodeName]; + // If this node name is already stored as an object attribute... + else if (typeof obj[nodeName] !== "undefined") { + // Cache what we have now + let old = obj[nodeName]; if (!Array.isArray(old)) old = [old]; - //Create a new object to store this node info + // Create a new object to store this node info var newNode = {}; - //Add the new node info to the existing array we have now + // Add the new node info to the existing array we have now if (item.nodeType == 1) { newNode = this.toJson(item); var newArray = old.concat(newNode); @@ -519,22 +520,22 @@ define([ var newArray = old.concat(newNode); } - //Store the attributes for this node - _.each(item.attributes, function (attr) { + // Store the attributes for this node + _.each(item.attributes, (attr) => { newNode[attr.localName] = attr.nodeValue; }); - //Replace the old array with the updated one + // Replace the old array with the updated one obj[nodeName] = newArray; - //Exit + // Exit continue; } - //Store the attributes for this node - /*_.each(item.attributes, function(attr){ + // Store the attributes for this node + /* _.each(item.attributes, function(attr){ obj[nodeName][attr.localName] = attr.nodeValue; - });*/ + }); */ } } return obj; @@ -545,38 +546,39 @@ define([ @param {object} json - the JSON object to convert to XML @param {Element} containerNode - an HTML element to insertt the resulting XML into @returns {Element} The updated HTML Element - */ - toXML: function (json, containerNode) { - if (typeof json == "string") { + */ + toXML(json, containerNode) { + if (typeof json === "string") { containerNode.textContent = json; return containerNode; } - for (var i = 0; i < Object.keys(json).length; i++) { - var key = Object.keys(json)[i], - contents = json[key] || json[key]; + for (let i = 0; i < Object.keys(json).length; i++) { + const key = Object.keys(json)[i]; + const contents = json[key] || json[key]; - var node = document.createElement(key); + let node = document.createElement(key); - //Skip this attribute if it is not populated + // Skip this attribute if it is not populated if (!contents || (Array.isArray(contents) && !contents.length)) continue; - //If it's a simple text node - if (typeof contents == "string") { + // If it's a simple text node + if (typeof contents === "string") { containerNode.textContent = contents; return containerNode; - } else if (Array.isArray(contents)) { - var allNewNodes = []; + } + if (Array.isArray(contents)) { + const allNewNodes = []; - for (var ii = 0; ii < contents.length; ii++) { + for (let ii = 0; ii < contents.length; ii++) { allNewNodes.push(this.toXML(contents[ii], $(node).clone()[0])); } if (allNewNodes.length) node = allNewNodes; - } else if (typeof contents == "object") { + } else if (typeof contents === "object") { $(node).append(this.toXML(contents, node)); - var attributeNames = _.without(Object.keys(json[key]), "content"); + const attributeNames = _.without(Object.keys(json[key]), "content"); } $(containerNode).append(node); @@ -587,21 +589,23 @@ define([ /** * Saves the DataONEObject System Metadata to the server + * @param attributes + * @param options */ - save: function (attributes, options) { + save(attributes, options) { // Set missing file names before saving if (!this.get("fileName")) { this.setMissingFileName(); } else { - //Replace all non-alphanumeric characters with underscores - var fileNameWithoutExt = this.get("fileName").substring( - 0, - this.get("fileName").lastIndexOf("."), - ), - extension = this.get("fileName").substring( - this.get("fileName").lastIndexOf("."), - this.get("fileName").length, - ); + // Replace all non-alphanumeric characters with underscores + const fileNameWithoutExt = this.get("fileName").substring( + 0, + this.get("fileName").lastIndexOf("."), + ); + const extension = this.get("fileName").substring( + this.get("fileName").lastIndexOf("."), + this.get("fileName").length, + ); this.set( "fileName", fileNameWithoutExt.replace(/[^a-zA-Z0-9]/g, "_") + extension, @@ -613,51 +617,51 @@ define([ return; } - //Set the upload transfer as in progress + // Set the upload transfer as in progress this.set("uploadProgress", 2); this.set("uploadStatus", "p"); - //Check if the checksum has been calculated yet. + // Check if the checksum has been calculated yet. if (!this.get("checksum")) { - //When it is calculated, restart this function + // When it is calculated, restart this function this.on("checksumCalculated", this.save); - //Calculate the checksum for this file + // Calculate the checksum for this file this.calculateChecksum(); - //Exit this function until the checksum is done + // Exit this function until the checksum is done return; } - //Create a FormData object to send data with our XHR - var formData = new FormData(); + // Create a FormData object to send data with our XHR + const formData = new FormData(); - //If this is not a new object, update the id. New DataONEObjects will have an id + // If this is not a new object, update the id. New DataONEObjects will have an id // created during initialize. if (!this.isNew()) { this.updateID(); formData.append("pid", this.get("oldPid")); formData.append("newPid", this.get("id")); } else { - //Create an ID if there isn't one + // Create an ID if there isn't one if (!this.get("id")) { - this.set("id", "urn:uuid:" + uuid.v4()); + this.set("id", `urn:uuid:${uuid.v4()}`); } - //Add the identifier to the XHR data + // Add the identifier to the XHR data formData.append("pid", this.get("id")); } - //Create the system metadata XML - var sysMetaXML = this.serializeSysMeta(); + // Create the system metadata XML + const sysMetaXML = this.serializeSysMeta(); - //Send the system metadata as a Blob - var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); - //Add the system metadata XML to the XHR data + // Send the system metadata as a Blob + const xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); + // Add the system metadata XML to the XHR data formData.append("sysmeta", xmlBlob, "sysmeta.xml"); // Create the new object (MN.create()) formData.append("object", this.get("uploadFile"), this.get("fileName")); - var model = this; + const model = this; // On create(), add to the package and the metadata // Note: This should be added to the parent collection @@ -673,8 +677,8 @@ define([ this, ); - //Put together the AJAX and Backbone.save() options - var requestSettings = { + // Put together the AJAX and Backbone.save() options + let requestSettings = { url: this.url(), cache: false, contentType: false, @@ -682,15 +686,15 @@ define([ processData: false, data: formData, parse: false, - xhr: function () { - var xhr = new window.XMLHttpRequest(); + xhr() { + const xhr = new window.XMLHttpRequest(); - //Upload progress + // Upload progress xhr.upload.addEventListener( "progress", - function (evt) { + (evt) => { if (evt.lengthComputable) { - var percentComplete = (evt.loaded / evt.total) * 100; + const percentComplete = (evt.loaded / evt.total) * 100; model.set("uploadProgress", percentComplete); } @@ -701,24 +705,24 @@ define([ return xhr; }, success: this.onSuccessfulSave, - error: function (model, response, xhr) { - //Reset the identifier changes + error(model, response, xhr) { + // Reset the identifier changes model.resetID(); - //Reset the checksum, if this is a model that needs to be serialized with each save. + // Reset the checksum, if this is a model that needs to be serialized with each save. if (model.serialize) { model.set("checksum", model.defaults().checksum); } model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); - var numSaveAttempts = model.get("numSaveAttempts"); + const numSaveAttempts = model.get("numSaveAttempts"); if ( numSaveAttempts < 3 && (response.status == 408 || response.status == 0) ) { - //Try saving again in 10, 40, and 90 seconds + // Try saving again in 10, 40, and 90 seconds setTimeout( - function () { + () => { model.save.call(model); }, numSaveAttempts * numSaveAttempts * 10000, @@ -726,11 +730,11 @@ define([ } else { model.set("numSaveAttempts", 0); - var parsedResponse = $(response.responseText) + let parsedResponse = $(response.responseText) .not("style, title") .text(); - //When there is no network connection (status == 0), there will be no response text + // When there is no network connection (status == 0), there will be no response text if (!parsedResponse) parsedResponse = "There was a network issue that prevented this file from uploading. " + @@ -738,10 +742,10 @@ define([ model.set("errorMessage", parsedResponse); - //Set the model status as e for error + // Set the model status as e for error model.set("uploadStatus", "e"); - //Trigger a custom event for the model save error + // Trigger a custom event for the model save error model.trigger("errorSaving", parsedResponse); // Track this error in our analytics @@ -754,13 +758,13 @@ define([ }, }; - //Add the user settings + // Add the user settings requestSettings = _.extend( requestSettings, MetacatUI.appUserModel.createAjaxSettings(), ); - //Send the Save request + // Send the Save request Backbone.Model.prototype.save.call(this, null, requestSettings); }, @@ -772,8 +776,8 @@ define([ * @param {XMLHttpRequest.response} [response] The XHR response object * @param {XMLHttpRequest} [xhr] The XHR that was just completed successfully */ - onSuccessfulSave: function (model, response, xhr) { - if (typeof model == "undefined") { + onSuccessfulSave(model, response, xhr) { + if (typeof model === "undefined") { var model = this; } @@ -791,13 +795,13 @@ define([ // Reset the content changes status model.set("hasContentChanges", false); - //Reset the model isNew attribute + // Reset the model isNew attribute model.set("isNew", false); // Reset oldPid so we can replace again model.set("oldPid", null); - //Set the last-calculated checksum as the original checksum + // Set the last-calculated checksum as the original checksum model.set("originalChecksum", model.get("checksum")); model.set("checksum", model.defaults().checksum); }, @@ -805,39 +809,39 @@ define([ /** * Updates the DataONEObject System Metadata to the server */ - updateSysMeta: function () { - //Update the upload status to "p" for "in progress" + updateSysMeta() { + // Update the upload status to "p" for "in progress" this.set("uploadStatus", "p"); - //Update the system metadata upload status to "p" as well, so the app + // Update the system metadata upload status to "p" as well, so the app // knows that the system metadata, specifically, is being updated. this.set("sysMetaUploadStatus", "p"); - var formData = new FormData(); + const formData = new FormData(); - //Add the identifier to the XHR data + // Add the identifier to the XHR data formData.append("pid", this.get("id")); - var sysMetaXML = this.serializeSysMeta(); + const sysMetaXML = this.serializeSysMeta(); - //Send the system metadata as a Blob - var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); - //Add the system metadata XML to the XHR data + // Send the system metadata as a Blob + const xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); + // Add the system metadata XML to the XHR data formData.append("sysmeta", xmlBlob, "sysmeta.xml"); - var model = this; + const model = this; - var baseUrl = "", - activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); - //Use the meta service URL from the alt repo + let baseUrl = ""; + const activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); + // Use the meta service URL from the alt repo if (activeAltRepo) { baseUrl = activeAltRepo.metaServiceUrl; } - //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel + // If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel else { baseUrl = MetacatUI.appModel.get("metaServiceUrl"); } - var requestSettings = { + let requestSettings = { url: baseUrl + encodeURIComponent(this.get("id")), cache: false, contentType: false, @@ -846,29 +850,29 @@ define([ processData: false, data: formData, parse: false, - success: function () { + success() { model.set("numSaveAttempts", 0); - //Fetch the system metadata from the server so we have a fresh copy of the newest sys meta. + // Fetch the system metadata from the server so we have a fresh copy of the newest sys meta. model.fetch({ systemMetadataOnly: true }); model.set("sysMetaErrorCode", null); - //Update the upload status to "c" for "complete" + // Update the upload status to "c" for "complete" model.set("uploadStatus", "c"); model.set("sysMetaUploadStatus", "c"); - //Trigger a custom event that the sys meta was updated + // Trigger a custom event that the sys meta was updated model.trigger("sysMetaUpdated"); }, - error: function (xhr, status, statusCode) { + error(xhr, status, statusCode) { model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); - var numSaveAttempts = model.get("numSaveAttempts"); + const numSaveAttempts = model.get("numSaveAttempts"); if (numSaveAttempts < 3 && (statusCode == 408 || statusCode == 0)) { - //Try saving again in 10, 40, and 90 seconds + // Try saving again in 10, 40, and 90 seconds setTimeout( - function () { + () => { model.updateSysMeta.call(model); }, numSaveAttempts * numSaveAttempts * 10000, @@ -876,11 +880,11 @@ define([ } else { model.set("numSaveAttempts", 0); - var parsedResponse = $(xhr.responseText) + let parsedResponse = $(xhr.responseText) .not("style, title") .text(); - //When there is no network connection (status == 0), there will be no response text + // When there is no network connection (status == 0), there will be no response text if (!parsedResponse) parsedResponse = "There was a network issue that prevented this file from updating. " + @@ -908,67 +912,67 @@ define([ }, }; - //Add the user settings + // Add the user settings requestSettings = _.extend( requestSettings, MetacatUI.appUserModel.createAjaxSettings(), ); - //Send the XHR + // Send the XHR $.ajax(requestSettings); }, /** * Check if the current user is authorized to perform an action on this object. This function doesn't return * the result of the check, but it sends an XHR, updates this model, and triggers a change event. - * @param {string} [action=changePermission] - The action (read, write, or changePermission) to check + * @param {string} [action] - The action (read, write, or changePermission) to check * if the current user has authorization to perform. By default checks for the highest level of permission. * @param {object} [options] Additional options for this function. See the properties below. - * @property {function} options.onSuccess - A function to execute when the checkAuthority API is successfully completed - * @property {function} options.onError - A function to execute when the checkAuthority API returns an error, or when no PID or SID can be found for this object. - * @return {boolean} + * @property {Function} options.onSuccess - A function to execute when the checkAuthority API is successfully completed + * @property {Function} options.onError - A function to execute when the checkAuthority API returns an error, or when no PID or SID can be found for this object. + * @returns {boolean} */ - checkAuthority: function (action = "changePermission", options) { + checkAuthority(action = "changePermission", options) { try { // return false - if neither PID nor SID is present to check the authority if (this.get("id") == null && this.get("seriesId") == null) { return false; } - if (typeof options == "undefined") { + if (typeof options === "undefined") { var options = {}; } // If onError or onSuccess options were provided by the user, // check that they are functions first, so we don't try to use // some other type of variable as a function later on. - ["onError", "onSuccess"].forEach(function (userFunction) { + ["onError", "onSuccess"].forEach((userFunction) => { if (typeof options[userFunction] !== "function") { options[userFunction] = null; } }); // If PID is not present - check authority with seriesId - var identifier = this.get("id"); + let identifier = this.get("id"); if (identifier == null) { identifier = this.get("seriesId"); } - //If there are alt repositories configured, find the possible authoritative + // If there are alt repositories configured, find the possible authoritative // Member Node for this DataONEObject. if (MetacatUI.appModel.get("alternateRepositories").length) { - //Get the array of possible authoritative MNs - var possibleAuthMNs = this.get("possibleAuthMNs"); + // Get the array of possible authoritative MNs + const possibleAuthMNs = this.get("possibleAuthMNs"); - //If there are no possible authoritative MNs, use the auth service URL from the AppModel + // If there are no possible authoritative MNs, use the auth service URL from the AppModel if (!possibleAuthMNs.length) { baseUrl = MetacatUI.appModel.get("authServiceUrl"); } else { - //Use the auth service URL from the top possible auth MN + // Use the auth service URL from the top possible auth MN baseUrl = possibleAuthMNs[0].authServiceUrl; } } else { - //Get the auth service URL from the AppModel + // Get the auth service URL from the AppModel baseUrl = MetacatUI.appModel.get("authServiceUrl"); } @@ -976,41 +980,41 @@ define([ return false; } - var onSuccess = - options.onSuccess || - function (data, textStatus, xhr) { - model.set("isAuthorized_" + action, true); - model.set("isAuthorized", true); - model.trigger("change:isAuthorized"); - }, - onError = - options.onError || - function (xhr, textStatus, errorThrown) { - if (errorThrown == 404) { - var possibleAuthMNs = model.get("possibleAuthMNs"); - if (possibleAuthMNs.length) { - //Remove the first MN from the array, since it didn't contain the object, so it's not the auth MN - possibleAuthMNs.shift(); - } - - //If there are no other possible auth MNs to check, trigger this model as Not Found. - if (possibleAuthMNs.length == 0 || !possibleAuthMNs) { - model.set("notFound", true); - model.trigger("notFound"); - } - //If there's more MNs to check, try again - else { - model.checkAuthority(action, options); - } - } else { - model.set("isAuthorized_" + action, false); - model.set("isAuthorized", false); + const onSuccess = + options.onSuccess || + function (data, textStatus, xhr) { + model.set(`isAuthorized_${action}`, true); + model.set("isAuthorized", true); + model.trigger("change:isAuthorized"); + }; + const onError = + options.onError || + function (xhr, textStatus, errorThrown) { + if (errorThrown == 404) { + const possibleAuthMNs = model.get("possibleAuthMNs"); + if (possibleAuthMNs.length) { + // Remove the first MN from the array, since it didn't contain the object, so it's not the auth MN + possibleAuthMNs.shift(); + } + + // If there are no other possible auth MNs to check, trigger this model as Not Found. + if (possibleAuthMNs.length == 0 || !possibleAuthMNs) { + model.set("notFound", true); + model.trigger("notFound"); } - }; + // If there's more MNs to check, try again + else { + model.checkAuthority(action, options); + } + } else { + model.set(`isAuthorized_${action}`, false); + model.set("isAuthorized", false); + } + }; var model = this; - var requestSettings = { - url: baseUrl + encodeURIComponent(identifier) + "?action=" + action, + const requestSettings = { + url: `${baseUrl + encodeURIComponent(identifier)}?action=${action}`, type: "GET", success: onSuccess, error: onError, @@ -1022,7 +1026,7 @@ define([ ), ); } catch (e) { - //Log an error to the console + // Log an error to the console console.error("Couldn't check the authority for this user: ", e); // Track this error in our analytics @@ -1033,8 +1037,8 @@ define([ true, ); - //Set the user as unauthorized - model.set("isAuthorized_" + action, false); + // Set the user as unauthorized + model.set(`isAuthorized_${action}`, false); model.set("isAuthorized", false); return false; } @@ -1044,21 +1048,21 @@ define([ * Using the attributes set on this DataONEObject model, serializes the system metadata XML * @returns {string} */ - serializeSysMeta: function () { - //Get the system metadata XML that currently exists in the system - var sysMetaXML = this.get("sysMetaXML"), // sysmeta as string - xml, // sysmeta as DOM object - accessPolicyXML, // The generated access policy XML - previousSiblingNode, // A DOM node indicating any previous sibling - rightsHolderNode, // A DOM node for the rights holder field - accessPolicyNode, // A DOM node for the access policy - replicationPolicyNode, // A DOM node for the replication policy - obsoletesNode, // A DOM node for the obsoletes field - obsoletedByNode, // A DOM node for the obsoletedBy field - fileNameNode, // A DOM node for the file name - xmlString, // The system metadata document as a string - nodeNameMap, // The map of camelCase to lowercase attributes - extension; // the file name extension for this object + serializeSysMeta() { + // Get the system metadata XML that currently exists in the system + const sysMetaXML = this.get("sysMetaXML"); // sysmeta as string + let xml; // sysmeta as DOM object + let accessPolicyXML; // The generated access policy XML + let previousSiblingNode; // A DOM node indicating any previous sibling + let rightsHolderNode; // A DOM node for the rights holder field + let accessPolicyNode; // A DOM node for the access policy + let replicationPolicyNode; // A DOM node for the replication policy + let obsoletesNode; // A DOM node for the obsoletes field + let obsoletedByNode; // A DOM node for the obsoletedBy field + let fileNameNode; // A DOM node for the file name + let xmlString; // The system metadata document as a string + let nodeNameMap; // The map of camelCase to lowercase attributes + let extension; // the file name extension for this object if (typeof sysMetaXML === "undefined" || sysMetaXML === null) { xml = this.createSysMeta(); @@ -1066,7 +1070,7 @@ define([ xml = $($.parseHTML(sysMetaXML)); } - //Update the system metadata values + // Update the system metadata values xml.find("serialversion").text(this.get("serialVersion") || "0"); xml.find("identifier").text(this.get("newPid") || this.get("id")); xml @@ -1076,53 +1080,53 @@ define([ ); xml.find("formatid").text(this.get("formatId") || this.getFormatId()); - //If there is a seriesId, add it + // If there is a seriesId, add it if (this.get("seriesId")) { - //Get the seriesId XML node - var seriesIdNode = xml.find("seriesId"); + // Get the seriesId XML node + let seriesIdNode = xml.find("seriesId"); - //If it doesn't exist, create one + // If it doesn't exist, create one if (!seriesIdNode.length) { seriesIdNode = $(document.createElement("seriesid")); xml.find("identifier").before(seriesIdNode); } - //Add the seriesId string to the XML node + // Add the seriesId string to the XML node seriesIdNode.text(this.get("seriesId")); } - //If there is no size, get it + // If there is no size, get it if (!this.get("size") && this.get("uploadFile")) { this.set("size", this.get("uploadFile").size); } - //Get the size of the file, if there is one + // Get the size of the file, if there is one if (this.get("uploadFile")) { xml.find("size").text(this.get("uploadFile").size); } - //Otherwise, use the last known size + // Otherwise, use the last known size else { xml.find("size").text(this.get("size")); } - //Save the original checksum + // Save the original checksum if (!this.get("checksum") && this.get("originalChecksum")) { xml.find("checksum").text(this.get("originalChecksum")); } - //Update the checksum and checksum algorithm + // Update the checksum and checksum algorithm else { xml.find("checksum").text(this.get("checksum")); xml.find("checksum").attr("algorithm", this.get("checksumAlgorithm")); } - //Update the rightsholder + // Update the rightsholder xml .find("rightsholder") .text( this.get("rightsHolder") || MetacatUI.appUserModel.get("username"), ); - //Write the access policy + // Write the access policy accessPolicyXML = this.get("accessPolicy").serialize(); // Get the access policy node, if it exists @@ -1136,7 +1140,7 @@ define([ previousSiblingNode.after(accessPolicyNode); } - //Replace the old access policy with the new one if it exists + // Replace the old access policy with the new one if it exists if (accessPolicyXML) { accessPolicyNode.replaceWith(accessPolicyXML); } else { @@ -1168,10 +1172,8 @@ define([ ); previousSiblingNode.after(obsoletesNode); } - } else { - if (obsoletesNode) { - obsoletesNode.remove(); - } + } else if (obsoletesNode) { + obsoletesNode.remove(); } if (obsoletesNode) { @@ -1180,7 +1182,7 @@ define([ obsoletedByNode = xml.find("obsoletedby"); - //remove the obsoletedBy node if it exists + // remove the obsoletedBy node if it exists // TODO: Verify this is what we want to do if (obsoletedByNode) { obsoletedByNode.remove(); @@ -1191,41 +1193,38 @@ define([ .find("dateuploaded") .text(this.get("dateUploaded") || new Date().toISOString()); - //Get the filename node + // Get the filename node fileNameNode = xml.find("filename"); - //If the filename node doesn't exist, then create one + // If the filename node doesn't exist, then create one if (!fileNameNode.length) { fileNameNode = $(document.createElement("filename")); xml.find("dateuploaded").after(fileNameNode); } - //Set the object file name + // Set the object file name $(fileNameNode).text(this.get("fileName")); xmlString = $(document.createElement("div")).append(xml.clone()).html(); - //Now camel case the nodes + // Now camel case the nodes nodeNameMap = this.nodeNameMap(); _.each( Object.keys(nodeNameMap), - function (name, i) { - var originalXMLString = xmlString; + (name, i) => { + const originalXMLString = xmlString; - //Camel case node names - var regEx = new RegExp("<" + name, "g"); - xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name]); - var regEx = new RegExp(name + ">", "g"); - xmlString = xmlString.replace(regEx, nodeNameMap[name] + ">"); + // Camel case node names + var regEx = new RegExp(`<${name}`, "g"); + xmlString = xmlString.replace(regEx, `<${nodeNameMap[name]}`); + var regEx = new RegExp(`${name}>`, "g"); + xmlString = xmlString.replace(regEx, `${nodeNameMap[name]}>`); - //If node names haven't been changed, then find an attribute + // If node names haven't been changed, then find an attribute if (xmlString == originalXMLString) { - var regEx = new RegExp(" " + name + "=", "g"); - xmlString = xmlString.replace( - regEx, - " " + nodeNameMap[name] + "=", - ); + var regEx = new RegExp(` ${name}=`, "g"); + xmlString = xmlString.replace(regEx, ` ${nodeNameMap[name]}=`); } }, this, @@ -1239,16 +1238,16 @@ define([ /** * Get the object format identifier for this object */ - getFormatId: function () { - var formatId = "application/octet-stream", // default to untyped data - objectFormats = { - mediaTypes: [], // The list of potential formatIds based on mediaType matches - extensions: [], // The list of possible formatIds based onextension matches - }, - fileName = this.get("fileName"), // the fileName for this object - ext; // The extension of the filename for this object + getFormatId() { + let formatId = "application/octet-stream"; // default to untyped data + const objectFormats = { + mediaTypes: [], // The list of potential formatIds based on mediaType matches + extensions: [], // The list of possible formatIds based onextension matches + }; + const fileName = this.get("fileName"); // the fileName for this object + let ext; // The extension of the filename for this object - objectFormats["mediaTypes"] = MetacatUI.objectFormats.where({ + objectFormats.mediaTypes = MetacatUI.objectFormats.where({ formatId: this.get("mediaType"), }); if ( @@ -1260,17 +1259,17 @@ define([ fileName.lastIndexOf(".") + 1, fileName.length, ); - objectFormats["extensions"] = MetacatUI.objectFormats.where({ + objectFormats.extensions = MetacatUI.objectFormats.where({ extension: ext, }); } if ( - objectFormats["mediaTypes"].length > 0 && - objectFormats["extensions"].length > 0 + objectFormats.mediaTypes.length > 0 && + objectFormats.extensions.length > 0 ) { - var firstMediaType = objectFormats["mediaTypes"][0].get("formatId"); - var firstExtension = objectFormats["extensions"][0].get("formatId"); + const firstMediaType = objectFormats.mediaTypes[0].get("formatId"); + const firstExtension = objectFormats.extensions[0].get("formatId"); // Check if they're equal if (firstMediaType === firstExtension) { formatId = firstMediaType; @@ -1286,19 +1285,19 @@ define([ } } - if (objectFormats["mediaTypes"].length > 0) { - formatId = objectFormats["mediaTypes"][0].get("formatId"); + if (objectFormats.mediaTypes.length > 0) { + formatId = objectFormats.mediaTypes[0].get("formatId"); console.log("returning default mediaType"); console.log(formatId); return formatId; } - if (objectFormats["extensions"].length > 0) { - //If this is a "nc" file, assume it is a netCDF-3 file. + if (objectFormats.extensions.length > 0) { + // If this is a "nc" file, assume it is a netCDF-3 file. if (ext == "nc") { formatId = "netCDF-3"; } else { - formatId = objectFormats["extensions"][0].get("formatId"); + formatId = objectFormats.extensions[0].get("formatId"); } return formatId; } @@ -1311,8 +1310,8 @@ define([ * @returns format String * @since 2.28.0 */ - getFormat: function () { - var formatMap = { + getFormat() { + const formatMap = { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Microsoft Excel OpenXML", "application/vnd.openxmlformats-officedocument.wordprocessingml.document": @@ -1374,12 +1373,12 @@ define([ * Build a fresh system metadata document for this object when it is new * Return it as a DOM object */ - createSysMeta: function () { - var sysmetaDOM, // The DOM - sysmetaXML = []; // The document as a string array + createSysMeta() { + let sysmetaDOM; // The DOM + const sysmetaXML = []; // The document as a string array sysmetaXML.push( - //'', + // '', "', @@ -1401,30 +1400,30 @@ define([ /** * Create an access policy for this DataONEObject using the default access * policy set in the AppModel. - * * @param {Element} [accessPolicyXML] - An XML node * that contains a list of access rules. - * @return {AccessPolicy} - an AccessPolicy collection that represents the + * @returns {AccessPolicy} - an AccessPolicy collection that represents the * given XML or the default policy set in the AppModel. */ - createAccessPolicy: function (accessPolicyXML) { - //Create a new AccessPolicy collection - var accessPolicy = new AccessPolicy(); + createAccessPolicy(accessPolicyXML) { + // Create a new AccessPolicy collection + const accessPolicy = new AccessPolicy(); accessPolicy.dataONEObject = this; - //If there is no access policy XML sent, + // If there is no access policy XML sent, if (this.isNew() && !accessPolicyXML) { try { - //If the app is configured to inherit the access policy from the parent metadata, + // If the app is configured to inherit the access policy from the parent metadata, // then get the parent metadata and copy it's AccessPolicy - let scienceMetadata = this.get("isDocumentedByModels"); + const scienceMetadata = this.get("isDocumentedByModels"); if ( MetacatUI.appModel.get("inheritAccessPolicy") && scienceMetadata && scienceMetadata.length ) { - let sciMetaAccessPolicy = scienceMetadata[0].get("accessPolicy"); + const sciMetaAccessPolicy = + scienceMetadata[0].get("accessPolicy"); if (sciMetaAccessPolicy) { accessPolicy.copyAccessPolicy(sciMetaAccessPolicy); @@ -1432,7 +1431,7 @@ define([ accessPolicy.createDefaultPolicy(); } } - //Otherwise, set the default access policy using the AppModel configuration + // Otherwise, set the default access policy using the AppModel configuration else { accessPolicy.createDefaultPolicy(); } @@ -1444,12 +1443,12 @@ define([ accessPolicy.createDefaultPolicy(); } } else { - //Parse the access policy XML to create AccessRule models from the XML + // Parse the access policy XML to create AccessRule models from the XML accessPolicy.parse(accessPolicyXML); } - //Listen to changes on the collection and trigger a change on this model - var self = this; + // Listen to changes on the collection and trigger a change on this model + const self = this; this.listenTo(accessPolicy, "change update", function () { self.trigger("change"); this.addToUploadQueue(); @@ -1460,7 +1459,6 @@ define([ /** * Update identifiers for this object - * * @param {string} id - Optional identifier to update with. Generated * automatically when not given. * @@ -1473,44 +1471,42 @@ define([ * PID will be such as the case where we want to update a matching * EML entity when replacing files. */ - updateID: function (id) { + updateID(id) { // Only run once until oldPid is reset if (this.get("oldPid")) { return; } - //Save the attributes so we can reset the ID later + // Save the attributes so we can reset the ID later this.attributeCache = this.toJSON(); - //Set the old identifier - var oldPid = this.get("id"), - selfDocuments, - selfDocumentedBy, - documentedModels, - documentedModel, - index; + // Set the old identifier + const oldPid = this.get("id"); + let selfDocuments; + let selfDocumentedBy; + let documentedModels; + let documentedModel; + let index; - //Save the current id as the old pid + // Save the current id as the old pid this.set("oldPid", oldPid); - //Create a new seriesId, if there isn't one, and if this model specifies that one is required + // Create a new seriesId, if there isn't one, and if this model specifies that one is required if (!this.get("seriesId") && this.get("createSeriesId")) { - this.set("seriesId", "urn:uuid:" + uuid.v4()); + this.set("seriesId", `urn:uuid:${uuid.v4()}`); } // Check to see if the old pid documents or is documented by itself selfDocuments = _.contains(this.get("documents"), oldPid); selfDocumentedBy = _.contains(this.get("isDocumentedBy"), oldPid); - //Set the new identifier + // Set the new identifier if (id) { this.set("id", id); + } else if (this.get("type") == "DataPackage") { + this.set("id", `resource_map_urn:uuid:${uuid.v4()}`); } else { - if (this.get("type") == "DataPackage") { - this.set("id", "resource_map_urn:uuid:" + uuid.v4()); - } else { - this.set("id", "urn:uuid:" + uuid.v4()); - } + this.set("id", `urn:uuid:${uuid.v4()}`); } // Remove the old pid from the documents list if present @@ -1537,7 +1533,7 @@ define([ _.each( this.get("documents"), function (id) { - (documentedModels = MetacatUI.rootDataPackage.where({ id: id })), + (documentedModels = MetacatUI.rootDataPackage.where({ id })), documentedModel; if (documentedModels.length > 0) { @@ -1562,21 +1558,21 @@ define([ this.trigger("change:id"); - //Update the obsoletes and obsoletedBy + // Update the obsoletes and obsoletedBy this.set("obsoletes", oldPid); this.set("obsoletedBy", null); // Update the latest version of this object this.set("latestVersion", this.get("id")); - //Set the archived option to false + // Set the archived option to false this.set("archived", false); }, /** * Resets the identifier for this model. This undos all of the changes made in {DataONEObject#updateID} */ - resetID: function () { + resetID() { if (!this.attributeCache) return false; this.set("oldPid", this.attributeCache.oldPid, { silent: true }); @@ -1590,7 +1586,7 @@ define([ silent: true, }); - //Reset the attribute cache + // Reset the attribute cache this.attributeCache = {}; }, @@ -1598,28 +1594,28 @@ define([ * Checks if this system metadata XML has updates that need to be synced with the server. * @returns {boolean} */ - hasUpdates: function () { + hasUpdates() { if (this.isNew()) return true; // Compare the new system metadata XML to the old system metadata XML - //Check if there is system metadata first + // Check if there is system metadata first if (!this.get("sysMetaXML")) { return false; } - var D1ObjectClone = this.clone(), - // Make sure we are using the parse function in the DataONEObject model. - // Sometimes hasUpdates is called from extensions of the D1Object model, - // (e.g. from the portal model), and the parse function is overwritten - oldSysMetaAttrs = new DataONEObject().parse( - D1ObjectClone.get("sysMetaXML"), - ); + const D1ObjectClone = this.clone(); + // Make sure we are using the parse function in the DataONEObject model. + // Sometimes hasUpdates is called from extensions of the D1Object model, + // (e.g. from the portal model), and the parse function is overwritten + const oldSysMetaAttrs = new DataONEObject().parse( + D1ObjectClone.get("sysMetaXML"), + ); D1ObjectClone.set(oldSysMetaAttrs); - var oldSysMeta = D1ObjectClone.serializeSysMeta(); - var newSysMeta = this.serializeSysMeta(); + const oldSysMeta = D1ObjectClone.serializeSysMeta(); + const newSysMeta = this.serializeSysMeta(); if (oldSysMeta === "") return false; @@ -1632,42 +1628,42 @@ define([ @param {DataONEObject} [model] @param {object} options Furhter options for this function @property {boolean} options.force If true, a change will be handled regardless if the attribute actually changed - */ - handleChange: function (model, options) { + */ + handleChange(model, options) { if (!model) var model = this; - var sysMetaAttrs = [ - "serialVersion", - "identifier", - "formatId", - "formatType", - "size", - "checksum", - "checksumAlgorithm", - "submitter", - "rightsHolder", - "accessPolicy", - "replicationAllowed", - "replicationPolicy", - "obsoletes", - "obsoletedBy", - "archived", - "dateUploaded", - "dateSysMetadataModified", - "originMemberNode", - "authoritativeMemberNode", - "replica", - "seriesId", - "mediaType", - "fileName", - ], - nonSysMetaNonContentAttrs = _.difference( - model.get("originalAttrs"), - sysMetaAttrs, - ), - allChangedAttrs = Object.keys(model.changedAttributes()), - changedSysMetaOrContentAttrs = [], //sysmeta or content attributes that have changed - changedContentAttrs = []; // attributes from sub classes like ScienceMetadata or EML211 ... + const sysMetaAttrs = [ + "serialVersion", + "identifier", + "formatId", + "formatType", + "size", + "checksum", + "checksumAlgorithm", + "submitter", + "rightsHolder", + "accessPolicy", + "replicationAllowed", + "replicationPolicy", + "obsoletes", + "obsoletedBy", + "archived", + "dateUploaded", + "dateSysMetadataModified", + "originMemberNode", + "authoritativeMemberNode", + "replica", + "seriesId", + "mediaType", + "fileName", + ]; + const nonSysMetaNonContentAttrs = _.difference( + model.get("originalAttrs"), + sysMetaAttrs, + ); + const allChangedAttrs = Object.keys(model.changedAttributes()); + let changedSysMetaOrContentAttrs = []; // sysmeta or content attributes that have changed + let changedContentAttrs = []; // attributes from sub classes like ScienceMetadata or EML211 ... // Get a list of all changed sysmeta and content attributes changedSysMetaOrContentAttrs = _.difference( @@ -1706,19 +1702,19 @@ define([ /** * Returns true if this DataONE object is new. A DataONE object is new * if there is no upload date and it's been synced (i.e. been fetched) - * @return {boolean} + * @returns {boolean} */ - isNew: function () { - //If the model is explicitly marked as not new, return false + isNew() { + // If the model is explicitly marked as not new, return false if (this.get("isNew") === false) { return false; } - //If the model is explicitly marked as new, return true - else if (this.get("isNew") === true) { + // If the model is explicitly marked as new, return true + if (this.get("isNew") === true) { return true; } - //Check if there is an upload date that was retrieved from the server + // Check if there is an upload date that was retrieved from the server return ( this.get("dateUploaded") === this.defaults().dateUploaded && this.get("synced") @@ -1728,12 +1724,12 @@ define([ /** * Updates the upload status attribute on this model and marks the collection as changed */ - addToUploadQueue: function () { + addToUploadQueue() { if (!this.get("synced")) { return; } - //Add this item to the queue + // Add this item to the queue if ( this.get("uploadStatus") == "c" || this.get("uploadStatus") == "e" || @@ -1741,10 +1737,10 @@ define([ ) { this.set("uploadStatus", "q"); - //Mark each DataPackage collection this model is in as changed + // Mark each DataPackage collection this model is in as changed _.each( this.get("collections"), - function (collection) { + (collection) => { if (collection.packageModel) collection.packageModel.set("changed", true); }, @@ -1757,12 +1753,12 @@ define([ * Updates the progress percentage when the model is getting uploaded * @param {ProgressEvent} e - The ProgressEvent when this file is being uploaded */ - updateProgress: function (e) { + updateProgress(e) { if (e.lengthComputable) { - var max = e.total; - var current = e.loaded; + const max = e.total; + const current = e.loaded; - var Percentage = (current * 100) / max; + const Percentage = (current * 100) / max; if (Percentage >= 100) { // process completed @@ -1773,25 +1769,28 @@ define([ /** * Updates the relationships with other models when this model has been updated */ - updateRelationships: function () { + updateRelationships() { _.each( this.get("collections"), function (collection) { - //Get the old id for this model - var oldId = this.get("oldPid"); + // Get the old id for this model + const oldId = this.get("oldPid"); if (!oldId) return; - //Find references to the old id in the documents relationship - var outdatedModels = collection.filter(function (m) { - return _.contains(m.get("documents"), oldId); - }); + // Find references to the old id in the documents relationship + const outdatedModels = collection.filter((m) => + _.contains(m.get("documents"), oldId), + ); - //Update the documents array in each model + // Update the documents array in each model _.each( outdatedModels, function (model) { - var updatedDocuments = _.without(model.get("documents"), oldId); + const updatedDocuments = _.without( + model.get("documents"), + oldId, + ); updatedDocuments.push(this.get("id")); model.set("documents", updatedDocuments); @@ -1804,19 +1803,19 @@ define([ }, /** - * Finds the latest version of this object by travesing the obsolescence chain - * @param {string} [latestVersion] - The identifier of the latest known object in the version chain. + * Finds the latest version of this object by travesing the obsolescence chain + * @param {string} [latestVersion] - The identifier of the latest known object in the version chain. If not supplied, this model's `id` will be used. - * @param {string} [possiblyNewer] - The identifier of the object that obsoletes the latestVersion. It's "possibly" newer, because it may be private/inaccessible - */ - findLatestVersion: function (latestVersion, possiblyNewer) { - var baseUrl = "", - activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); - //Use the meta service URL from the alt repo + * @param {string} [possiblyNewer] - The identifier of the object that obsoletes the latestVersion. It's "possibly" newer, because it may be private/inaccessible + */ + findLatestVersion(latestVersion, possiblyNewer) { + let baseUrl = ""; + const activeAltRepo = MetacatUI.appModel.getActiveAltRepo(); + // Use the meta service URL from the alt repo if (activeAltRepo) { baseUrl = activeAltRepo.metaServiceUrl; } - //If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel + // If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel else { baseUrl = MetacatUI.appModel.get("metaServiceUrl"); } @@ -1825,7 +1824,7 @@ define([ return; } - //If there is no system metadata, then retrieve it first + // If there is no system metadata, then retrieve it first if (!this.get("sysMetaXML")) { this.once("sync", this.findLatestVersion); this.once("systemMetadataSync", this.findLatestVersion); @@ -1837,47 +1836,47 @@ define([ return; } - //If no pid was supplied, use this model's id - if (!latestVersion || typeof latestVersion != "string") { + // If no pid was supplied, use this model's id + if (!latestVersion || typeof latestVersion !== "string") { var latestVersion = this.get("id"); var possiblyNewer = this.get("obsoletedBy"); } - //If this isn't obsoleted by anything, then there is no newer version - if (!possiblyNewer || typeof latestVersion != "string") { + // If this isn't obsoleted by anything, then there is no newer version + if (!possiblyNewer || typeof latestVersion !== "string") { this.set("latestVersion", latestVersion); - //Trigger an event that will fire whether or not the latestVersion + // Trigger an event that will fire whether or not the latestVersion // attribute was actually changed this.trigger("latestVersionFound", this); - //Remove the listeners now that we found the latest version + // Remove the listeners now that we found the latest version this.stopListening("sync", this.findLatestVersion); this.stopListening("systemMetadataSync", this.findLatestVersion); return; } - var model = this; + const model = this; - //Get the system metadata for the possibly newer version - var requestSettings = { + // Get the system metadata for the possibly newer version + const requestSettings = { url: baseUrl + encodeURIComponent(possiblyNewer), type: "GET", - success: function (data) { + success(data) { // the response may have an obsoletedBy element - var obsoletedBy = $(data).find("obsoletedBy").text(); + const obsoletedBy = $(data).find("obsoletedBy").text(); - //If there is an even newer version, then get it and rerun this function + // If there is an even newer version, then get it and rerun this function if (obsoletedBy) { model.findLatestVersion(possiblyNewer, obsoletedBy); } - //If there isn't a newer version, then this is it + // If there isn't a newer version, then this is it else { model.set("latestVersion", possiblyNewer); model.trigger("latestVersionFound", model); - //Remove the listeners now that we found the latest version + // Remove the listeners now that we found the latest version model.stopListening("sync", model.findLatestVersion); model.stopListening( "systemMetadataSync", @@ -1885,8 +1884,8 @@ define([ ); } }, - error: function (xhr) { - //If this newer version isn't accessible, link to the latest version that is + error(xhr) { + // If this newer version isn't accessible, link to the latest version that is if (xhr.status == "401") { model.set("latestVersion", latestVersion); model.trigger("latestVersionFound", model); @@ -1907,139 +1906,100 @@ define([ * @param {string|Element} xml - The XML to format * @returns {string} The formatted XML string */ - formatXML: function (xml) { - var nodeNameMap = this.nodeNameMap(), - xmlString = ""; + formatXML(xml) { + const nodeNameMap = this.nodeNameMap(); + let xmlString = ""; - //XML must be provided for this function + // XML must be provided for this function if (!xml) return ""; - //Support XML strings - else if (typeof xml == "string") xmlString = xml; - //Support DOMs - else if (typeof xml == "object" && xml.nodeType) { - //XML comments should be formatted with start and end carets - if (xml.nodeType == 8) xmlString = "<" + xml.nodeValue + ">"; - //XML nodes have the entire XML string available in the outerHTML attribute + // Support XML strings + if (typeof xml === "string") xmlString = xml; + // Support DOMs + else if (typeof xml === "object" && xml.nodeType) { + // XML comments should be formatted with start and end carets + if (xml.nodeType == 8) xmlString = `<${xml.nodeValue}>`; + // XML nodes have the entire XML string available in the outerHTML attribute else if (xml.nodeType == 1) xmlString = xml.outerHTML; - //Text node types are left as-is + // Text node types are left as-is else if (xml.nodeType == 3) return xml.nodeValue; } - //Return empty strings if something went wrong + // Return empty strings if something went wrong if (!xmlString) return ""; _.each( Object.keys(nodeNameMap), - function (name, i) { - var originalXMLString = xmlString; - - //Check for this node name whe it's an opening XML node, e.g. `` - var regEx = new RegExp("<" + name + ">", "g"); - xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + ">"); - - //Check for this node name when it's an opening XML node, e.g. `` - regEx = new RegExp(":" + name + ">", "g"); - xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + ">"); - - //Check for this node name when it's a closing XML tag, e.g. `` - regEx = new RegExp("", "g"); - xmlString = xmlString.replace( - regEx, - "", - ); + (name, i) => { + const originalXMLString = xmlString; - //If node names haven't been changed, then find an attribute, e.g. ` name=` + // Check for this node name whe it's an opening XML node, e.g. `` + let regEx = new RegExp(`<${name}>`, "g"); + xmlString = xmlString.replace(regEx, `<${nodeNameMap[name]}>`); + + // Check for this node name when it's an opening XML node, e.g. `` + regEx = new RegExp(`:${name}>`, "g"); + xmlString = xmlString.replace(regEx, `:${nodeNameMap[name]}>`); + + // Check for this node name when it's a closing XML tag, e.g. `` + regEx = new RegExp(``, "g"); + xmlString = xmlString.replace(regEx, ``); + + // If node names haven't been changed, then find an attribute, e.g. ` name=` if (xmlString == originalXMLString) { - regEx = new RegExp(" " + name + "=", "g"); - xmlString = xmlString.replace( - regEx, - " " + nodeNameMap[name] + "=", - ); + regEx = new RegExp(` ${name}=`, "g"); + xmlString = xmlString.replace(regEx, ` ${nodeNameMap[name]}=`); } }, this, ); - //Take each XML node text value and decode any XML entities - var regEx = new RegExp("&[0-9a-zA-Z]+;", "g"); - xmlString = xmlString.replace(regEx, function (match) { - return he.encode(he.decode(match)); - }); + // Take each XML node text value and decode any XML entities + const regEx = new RegExp("&[0-9a-zA-Z]+;", "g"); + xmlString = xmlString.replace(regEx, (match) => + he.encode(he.decode(match)), + ); return xmlString; }, - /** - * Converts the number of bytes into a human readable format and - * updates the `sizeStr` attribute - * @returns: None - * - */ - bytesToSize: function () { - var kibibyte = 1024; - var mebibyte = kibibyte * 1024; - var gibibyte = mebibyte * 1024; - var tebibyte = gibibyte * 1024; - var precision = 0; - - var bytes = this.get("size"); - - if (bytes >= 0 && bytes < kibibyte) { - this.set("sizeStr", bytes + " B"); - } else if (bytes >= kibibyte && bytes < mebibyte) { - this.set("sizeStr", (bytes / kibibyte).toFixed(precision) + " KiB"); - } else if (bytes >= mebibyte && bytes < gibibyte) { - precision = 2; - this.set("sizeStr", (bytes / mebibyte).toFixed(precision) + " MiB"); - } else if (bytes >= gibibyte && bytes < tebibyte) { - precision = 2; - this.set("sizeStr", (bytes / gibibyte).toFixed(precision) + " GiB"); - } else if (bytes >= tebibyte) { - precision = 2; - this.set("sizeStr", (bytes / tebibyte).toFixed(precision) + " TiB"); - } else { - this.set("sizeStr", bytes + " B"); - } - }, - /** * This method will download this object while * sending the user's auth token in the request. * @returns None * @since: 2.28.0 */ - downloadWithCredentials: function () { - //if(this.get("isPublic")) return; + downloadWithCredentials() { + // if(this.get("isPublic")) return; - //Get info about this object - var url = this.get("url"), - model = this; + // Get info about this object + const url = this.get("url"); + const model = this; - //Create an XHR - var xhr = new XMLHttpRequest(); + // Create an XHR + const xhr = new XMLHttpRequest(); - //Open and send the request with the user's auth token + // Open and send the request with the user's auth token xhr.open("GET", url); if (MetacatUI.appUserModel.get("loggedIn")) xhr.withCredentials = true; - //When the XHR is ready, create a link with the raw data (Blob) and click the link to download + // When the XHR is ready, create a link with the raw data (Blob) and click the link to download xhr.onload = function () { if (this.status == 404) { this.onerror.call(this); return; } - //Get the file name to save this file as - var filename = xhr.getResponseHeader("Content-Disposition"); + // Get the file name to save this file as + let filename = xhr.getResponseHeader("Content-Disposition"); if (!filename) { filename = @@ -2052,16 +2012,16 @@ define([ .substring(filename.indexOf("filename=") + 9) .replace(/"/g, ""); - //Replace any whitespaces + // Replace any whitespaces filename = filename.trim().replace(/ /g, "_"); - //For IE, we need to use the navigator API + // For IE, we need to use the navigator API if (navigator && navigator.msSaveOrOpenBlob) { navigator.msSaveOrOpenBlob(xhr.response, filename); } - //Other browsers can download it via a link + // Other browsers can download it via a link else { - var a = document.createElement("a"); + const a = document.createElement("a"); a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob // Set the file name. @@ -2096,7 +2056,7 @@ define([ xhr.onprogress = function (e) { if (e.lengthComputable) { - var percent = (e.loaded / e.total) * 100; + const percent = (e.loaded / e.total) * 100; model.set("downloadPercent", percent); } }; @@ -2106,7 +2066,7 @@ define([ if (MetacatUI.appUserModel.get("loggedIn")) xhr.setRequestHeader( "Authorization", - "Bearer " + MetacatUI.appUserModel.get("token"), + `Bearer ${MetacatUI.appUserModel.get("token")}`, ); xhr.send(); @@ -2115,8 +2075,10 @@ define([ /** * Creates a file name for this DataONEObject and updates the `fileName` attribute */ - setMissingFileName: function () { - var objectFormats, filename, extension; + setMissingFileName() { + let objectFormats; + let filename; + let extension; objectFormats = MetacatUI.objectFormats.where({ formatId: this.get("formatId"), @@ -2125,28 +2087,28 @@ define([ extension = objectFormats[0].get("extension"); } - //Science metadata file names will use the title + // Science metadata file names will use the title if (this.get("type") == "Metadata") { filename = Array.isArray(this.get("title")) && this.get("title").length ? this.get("title")[0] : this.get("id"); } - //Resource maps will use a "resource_map_" prefix + // Resource maps will use a "resource_map_" prefix else if (this.get("type") == "DataPackage") { - filename = "resource_map_" + this.get("id"); + filename = `resource_map_${this.get("id")}`; extension = ".rdf.xml"; } - //All other object types will just use the id + // All other object types will just use the id else { filename = this.get("id"); } - //Replace all non-alphanumeric characters with underscores + // Replace all non-alphanumeric characters with underscores filename = filename.replace(/[^a-zA-Z0-9]/g, "_"); if (typeof extension !== "undefined") { - filename = filename + "." + extension; + filename = `${filename}.${extension}`; } this.set("fileName", filename); @@ -2154,24 +2116,22 @@ define([ /** * Creates a URL for viewing more information about this object - * @return {string} + * @returns {string} */ - createViewURL: function () { - return ( - MetacatUI.root + - "/view/" + - encodeURIComponent(this.get("seriesId") || this.get("id")) - ); + createViewURL() { + return `${MetacatUI.root}/view/${encodeURIComponent( + this.get("seriesId") || this.get("id"), + )}`; }, /** * Check if the seriesID or PID matches a DOI regex, and if so, return * a canonical IRI for the DOI. - * @return {string|null} - The canonical IRI for the DOI, or null if + * @returns {string|null} - The canonical IRI for the DOI, or null if * neither the seriesId nor the PID match a DOI regex. * @since 2.26.0 */ - getCanonicalDOIIRI: function () { + getCanonicalDOIIRI() { const id = this.get("id"); const seriesId = this.get("seriesId"); let DOI = null; @@ -2183,14 +2143,14 @@ define([ /** * Converts the identifier string to a string safe to use in an XML id attribute * @param {string} [id] - The ID string - * @return {string} - The XML-safe string + * @returns {string} - The XML-safe string */ - getXMLSafeID: function (id) { - if (typeof id == "undefined") { + getXMLSafeID(id) { + if (typeof id === "undefined") { var id = this.get("id"); } - //Replace XML id attribute invalid characters and patterns in the identifier + // Replace XML id attribute invalid characters and patterns in the identifier id = id .replace(/ -1; - }); + const programClass = _.filter( + instanceOfClass, + (className) => className.indexOf("#Program") > -1, + ); if (typeof programClass !== "undefined" && programClass.length) return "program"; - } else { - if (this.get("prov_generated").length || this.get("prov_used").length) - return "program"; - } + } else if ( + this.get("prov_generated").length || + this.get("prov_used").length + ) + return "program"; - //Determine the type via file format + // Determine the type via file format if (this.isSoftware()) return "program"; if (this.isData()) return "data"; @@ -2283,20 +2245,20 @@ define([ if (_.contains(pdfIds, this.get("formatId"))) return "PDF"; if (_.contains(annotationIds, this.get("formatId"))) return "annotation"; - else return "data"; + return "data"; }, /** * Checks the formatId of this model and determines if it is an image. * @returns {boolean} true if this data object is an image, false if it is other */ - isImage: function () { - //The list of formatIds that are images - var imageIds = ["image/gif", "image/jp2", "image/jpeg", "image/png"]; + isImage() { + // The list of formatIds that are images + const imageIds = ["image/gif", "image/jp2", "image/jpeg", "image/png"]; - //Does this data object match one of these IDs? + // Does this data object match one of these IDs? if (_.indexOf(imageIds, this.get("formatId")) == -1) return false; - else return true; + return true; }, /** @@ -2306,8 +2268,8 @@ define([ * as images {@link DataONEObject#isImage} or software {@link DataONEObject#isSoftware}. * @returns {boolean} true if this data object is a data file, false if it is other */ - isData: function () { - var dataIds = [ + isData() { + const dataIds = [ "application/atom+xml", "application/mathematica", "application/msword", @@ -2354,9 +2316,9 @@ define([ "video/x-ms-wmv", ]; - //Does this data object match one of these IDs? + // Does this data object match one of these IDs? if (_.indexOf(dataIds, this.get("formatId")) == -1) return false; - else return true; + return true; }, /** @@ -2366,9 +2328,9 @@ define([ * as images {@link DataONEObject#isImage} for display purposes. * @returns {boolean} true if this data object is a software file, false if it is other */ - isSoftware: function () { - //The list of formatIds that are programs - var softwareIds = [ + isSoftware() { + // The list of formatIds that are programs + const softwareIds = [ "text/x-python", "text/x-rsrc", "text/x-matlab", @@ -2376,22 +2338,22 @@ define([ "application/R", "application/x-ipynb+json", ]; - //Does this data object match one of these IDs? + // Does this data object match one of these IDs? if (_.indexOf(softwareIds, this.get("formatId")) == -1) return false; - else return true; + return true; }, /** * Checks the formatId of this model and determines if it a PDF. * @returns {boolean} true if this data object is a pdf, false if it is other */ - isPDF: function () { - //The list of formatIds that are images - var ids = ["application/pdf"]; + isPDF() { + // The list of formatIds that are images + const ids = ["application/pdf"]; - //Does this data object match one of these IDs? + // Does this data object match one of these IDs? if (_.indexOf(ids, this.get("formatId")) == -1) return false; - else return true; + return true; }, /** @@ -2403,7 +2365,7 @@ define([ * see https://github.com/DataONEorg/sem-prov-ontologies/blob/master/provenance/ProvONE/v1/provone.html * @param {string} className */ - setProvClass: function (className) { + setProvClass(className) { className = className.toLowerCase(); className = className.charAt(0).toUpperCase() + className.slice(1); @@ -2434,10 +2396,7 @@ define([ ) { this.set("prov_instanceOfClass", [this.PROV + className]); } else { - message = - "The given class name: " + - className + - " is not in the known ProvONE or PROV classes."; + message = `The given class name: ${className} is not in the known ProvONE or PROV classes.`; throw new Error(message); } }, @@ -2445,17 +2404,17 @@ define([ /** * Calculate a checksum for the object * @param {string} [algorithm] The algorithm to use, defaults to MD5 - * @return {string} A checksum plain JS object with value and algorithm attributes + * @returns {string} A checksum plain JS object with value and algorithm attributes */ - calculateChecksum: function (algorithm) { + calculateChecksum(algorithm) { var algorithm = algorithm || "MD5"; - var checksum = { algorithm: undefined, value: undefined }; - var hash; // The checksum hash - var file; // The file to be read by slicing - var reader; // The FileReader used to read each slice - var offset = 0; // Byte offset for reading slices - var sliceSize = Math.pow(2, 20); // 1MB slices - var model = this; + const checksum = { algorithm: undefined, value: undefined }; + let hash; // The checksum hash + let file; // The file to be read by slicing + let reader; // The FileReader used to read each slice + let offset = 0; // Byte offset for reading slices + const sliceSize = 2 ** 20; // 1MB slices + const model = this; // Do we have a file? if (this.get("uploadFile") instanceof Blob) { @@ -2463,7 +2422,7 @@ define([ reader = new FileReader(); /* Handle load errors */ reader.onerror = function (event) { - console.log("Error reading: " + event); + console.log(`Error reading: ${event}`); }; /* Show progress */ reader.onprogress = function (event) {}; @@ -2494,8 +2453,7 @@ define([ // TODO: Support SHA-1 // break; default: - message = - "The given algorithm: " + algorithm + " is not supported."; + message = `The given algorithm: ${algorithm} is not supported.`; throw new Error(message); } @@ -2503,9 +2461,12 @@ define([ * A helper function internal to calculateChecksum() used to slice * the file at the next offset by slice size */ + /** + * + */ function _seek() { - var calculated = false; - var slice; + let calculated = false; + let slice; // Digest the checksum when we're done calculating if (offset >= file.size) { hash.digest(); @@ -2521,11 +2482,10 @@ define([ /** * Checks if the pid or sid or given string is a DOI - * * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model * @returns {boolean} True if it is a DOI */ - isDOI: function (customString) { + isDOI(customString) { return ( isDOI(customString) || isDOI(this.get("id")) || @@ -2537,58 +2497,55 @@ define([ * Creates an array of objects that represent Member Nodes that could possibly be this * object's authoritative MN. This function updates the `possibleAuthMNs` attribute on this model. */ - setPossibleAuthMNs: function () { - //Only do this for Coordinating Node MetacatUIs. + setPossibleAuthMNs() { + // Only do this for Coordinating Node MetacatUIs. if (MetacatUI.appModel.get("alternateRepositories").length) { - //Set the possibleAuthMNs attribute - var possibleAuthMNs = []; + // Set the possibleAuthMNs attribute + const possibleAuthMNs = []; - //If a datasource is already found for this Portal, move that to the top of the list of auth MNs - var datasource = this.get("datasource") || ""; + // If a datasource is already found for this Portal, move that to the top of the list of auth MNs + const datasource = this.get("datasource") || ""; if (datasource) { - //Find the MN object that matches the datasource node ID - var datasourceMN = _.findWhere( + // Find the MN object that matches the datasource node ID + const datasourceMN = _.findWhere( MetacatUI.appModel.get("alternateRepositories"), { identifier: datasource }, ); if (datasourceMN) { - //Clone the MN object and add it to the array - var clonedDatasourceMN = Object.assign({}, datasourceMN); + // Clone the MN object and add it to the array + const clonedDatasourceMN = { ...datasourceMN }; possibleAuthMNs.push(clonedDatasourceMN); } } - //If there is an active alternate repo, move that to the top of the list of auth MNs - var activeAltRepo = + // If there is an active alternate repo, move that to the top of the list of auth MNs + const activeAltRepo = MetacatUI.appModel.get("activeAlternateRepositoryId") || ""; if (activeAltRepo) { - var activeAltRepoMN = _.findWhere( + const activeAltRepoMN = _.findWhere( MetacatUI.appModel.get("alternateRepositories"), { identifier: activeAltRepo }, ); if (activeAltRepoMN) { - //Clone the MN object and add it to the array - var clonedActiveAltRepoMN = Object.assign({}, activeAltRepoMN); + // Clone the MN object and add it to the array + const clonedActiveAltRepoMN = { ...activeAltRepoMN }; possibleAuthMNs.push(clonedActiveAltRepoMN); } } - //Add all the other alternate repositories to the list of auth MNs - var otherPossibleAuthMNs = _.reject( + // Add all the other alternate repositories to the list of auth MNs + const otherPossibleAuthMNs = _.reject( MetacatUI.appModel.get("alternateRepositories"), - function (mn) { - return ( - mn.identifier == datasource || mn.identifier == activeAltRepo - ); - }, + (mn) => + mn.identifier == datasource || mn.identifier == activeAltRepo, ); - //Clone each MN object and add to the array - _.each(otherPossibleAuthMNs, function (mn) { - var clonedMN = Object.assign({}, mn); + // Clone each MN object and add to the array + _.each(otherPossibleAuthMNs, (mn) => { + const clonedMN = { ...mn }; possibleAuthMNs.push(clonedMN); }); - //Update this model + // Update this model this.set("possibleAuthMNs", possibleAuthMNs); } }, @@ -2600,13 +2557,13 @@ define([ * models were created with `id`s with new line and white space characters (e.g. `\n urn:uuid:1234...`) * @param {object} json - The Solr document as a JS Object, which will be directly altered */ - removeWhiteSpaceFromSolrFields: function (json) { - if (typeof json.resourceMap == "string") { + removeWhiteSpaceFromSolrFields(json) { + if (typeof json.resourceMap === "string") { json.resourceMap = json.resourceMap.trim(); } else if (Array.isArray(json.resourceMap)) { - let newResourceMapIds = []; - _.each(json.resourceMap, function (rMapId) { - if (typeof rMapId == "string") { + const newResourceMapIds = []; + _.each(json.resourceMap, (rMapId) => { + if (typeof rMapId === "string") { newResourceMapIds.push(rMapId.trim()); } }); @@ -2621,15 +2578,15 @@ define([ * Generate a unique identifier to be used as an XML id attribute * @returns {string} The identifier string that was generated */ - generateId: function () { - var idStr = ""; // the id to return - var length = 30; // the length of the generated string - var chars = + generateId() { + let idStr = ""; // the id to return + const length = 30; // the length of the generated string + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split( "", ); - for (var i = 0; i < length; i++) { + for (let i = 0; i < length; i++) { idStr += chars[Math.floor(Math.random() * chars.length)]; } return idStr; diff --git a/src/js/models/PackageModel.js b/src/js/models/PackageModel.js index c17cd3bed..7ed421469 100644 --- a/src/js/models/PackageModel.js +++ b/src/js/models/PackageModel.js @@ -6,19 +6,19 @@ define([ "md5", "rdflib", "models/SolrResult", -], function ($, _, Backbone, uuid, md5, rdf, SolrResult) { +], ($, _, Backbone, uuid, md5, rdf, SolrResult) => { // Package Model // ------------------ var PackageModel = Backbone.Model.extend( /** @lends PackageModel.prototype */ { // This model contains information about a package/resource map - defaults: function () { + defaults() { return { - id: null, //The id of the resource map/package itself - url: null, //the URL to retrieve this package - memberId: null, //An id of a member of the data package - indexDoc: null, //A SolrResult object representation of the resource map - size: 0, //The number of items aggregated in this package + id: null, // The id of the resource map/package itself + url: null, // the URL to retrieve this package + memberId: null, // An id of a member of the data package + indexDoc: null, // A SolrResult object representation of the resource map + size: 0, // The number of items aggregated in this package totalSize: null, formattedSize: "", formatId: null, @@ -35,14 +35,14 @@ define([ derivationPackages: [], sourceDocs: [], derivationDocs: [], - relatedModels: [], //A condensed list of all SolrResult models related to this package in some way + relatedModels: [], // A condensed list of all SolrResult models related to this package in some way parentPackageMetadata: null, - //If true, when the member objects are retrieved, archived content will be included + // If true, when the member objects are retrieved, archived content will be included getArchivedMembers: false, }; }, - //Define the namespaces + // Define the namespaces namespaces: { RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", FOAF: "http://xmlns.com/foaf/0.1/", @@ -82,14 +82,14 @@ define([ // The RDF graph representing this data package dataPackageGraph: null, - initialize: function (options) { + initialize(options) { this.setURL(); // Create an initial RDF graph this.dataPackageGraph = rdf.graph(); }, - setURL: function () { + setURL() { if (MetacatUI.appModel.get("packageServiceUrl")) this.set( "url", @@ -101,7 +101,7 @@ define([ /* * Set the URL for fetch */ - url: function () { + url() { return ( MetacatUI.appModel.get("objectServiceUrl") + encodeURIComponent(this.get("id")) @@ -109,39 +109,34 @@ define([ }, /* Retrieve the id of the resource map/package that this id belongs to */ - getMembersByMemberID: function (id) { + getMembersByMemberID(id) { this.pending = true; if (typeof id === "undefined" || !id) var id = this.memberId; - var model = this; - - //Get the id of the resource map for this member - var provFlList = - MetacatUI.appSearchModel.getProvFlList() + "prov_instanceOfClass,"; - var query = - "fl=resourceMap,fileName,read:read_count_i,obsoletedBy,size,formatType,formatId,id,datasource,title,origin,pubDate,dateUploaded,isPublic,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription," + - provFlList + - "&rows=1" + - "&q=id:%22" + - encodeURIComponent(id) + - "%22" + - "&wt=json"; - - var requestSettings = { + const model = this; + + // Get the id of the resource map for this member + const provFlList = `${MetacatUI.appSearchModel.getProvFlList()}prov_instanceOfClass,`; + const query = + `fl=resourceMap,fileName,read:read_count_i,obsoletedBy,size,formatType,formatId,id,datasource,title,origin,pubDate,dateUploaded,isPublic,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,${provFlList}&rows=1` + + `&q=id:%22${encodeURIComponent(id)}%22` + + `&wt=json`; + + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { - //There should be only one response since we searched by id + success(data, textStatus, xhr) { + // There should be only one response since we searched by id if (typeof data.response.docs !== "undefined") { - var doc = data.response.docs[0]; + const doc = data.response.docs[0]; - //Is this document a resource map itself? + // Is this document a resource map itself? if (doc.formatId == "http://www.openarchives.org/ore/terms") { - model.set("id", doc.id); //this is the package model ID - model.set("members", new Array()); //Reset the member list + model.set("id", doc.id); // this is the package model ID + model.set("members", new Array()); // Reset the member list model.getMembers(); } - //If there is no resource map, then this is the only document to in this package + // If there is no resource map, then this is the only document to in this package else if ( typeof doc.resourceMap === "undefined" || !doc.resourceMap @@ -169,35 +164,33 @@ define([ /* Get all the members of a resource map/package based on the id attribute of this model. * Create a SolrResult model for each member and save it in the members[] attribute of this model. */ - getMembers: function (options) { + getMembers(options) { this.pending = true; - var model = this, - members = [], - pids = []; //Keep track of each object pid - - //*** Find all the files that are a part of this resource map and the resource map itself - var provFlList = MetacatUI.appSearchModel.getProvFlList(); - var query = - "fl=resourceMap,fileName,obsoletes,obsoletedBy,size,formatType,formatId,id,datasource," + - "rightsHolder,dateUploaded,archived,title,origin,prov_instanceOfClass,isDocumentedBy,isPublic" + - "&rows=1000" + - "&q=%28resourceMap:%22" + - encodeURIComponent(this.id) + - "%22%20OR%20id:%22" + - encodeURIComponent(this.id) + - "%22%29" + - "&wt=json"; + const model = this; + const members = []; + const pids = []; // Keep track of each object pid + + //* ** Find all the files that are a part of this resource map and the resource map itself + const provFlList = MetacatUI.appSearchModel.getProvFlList(); + let query = + `fl=resourceMap,fileName,obsoletes,obsoletedBy,size,formatType,formatId,id,datasource,` + + `rightsHolder,dateUploaded,archived,title,origin,prov_instanceOfClass,isDocumentedBy,isPublic` + + `&rows=1000` + + `&q=%28resourceMap:%22${encodeURIComponent( + this.id, + )}%22%20OR%20id:%22${encodeURIComponent(this.id)}%22%29` + + `&wt=json`; if (this.get("getArchivedMembers")) { query += "&archived=archived:*"; } - var requestSettings = { + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { - //Separate the resource maps from the data/metadata objects - _.each(data.response.docs, function (doc) { + success(data, textStatus, xhr) { + // Separate the resource maps from the data/metadata objects + _.each(data.response.docs, (doc) => { if (doc.id == model.get("id")) { model.set("indexDoc", doc); model.set(doc); @@ -211,7 +204,7 @@ define([ pids.push(doc.id); if (doc.formatType == "RESOURCE") { - var newPckg = new PackageModel(doc); + const newPckg = new PackageModel(doc); newPckg.set("parentPackage", model); members.push(newPckg); } else members.push(new SolrResult(doc)); @@ -240,12 +233,12 @@ define([ /* * Send custom options to the Backbone.Model.fetch() function */ - fetch: function (options) { + fetch(options) { if (!options) var options = {}; - var fetchOptions = _.extend({ dataType: "text" }, options); + let fetchOptions = _.extend({ dataType: "text" }, options); - //Add the authorization options + // Add the authorization options fetchOptions = _.extend( fetchOptions, MetacatUI.appUserModel.createAjaxSettings(), @@ -257,25 +250,25 @@ define([ /* * Deserialize a Package from OAI-ORE RDF XML */ - parse: function (response, options) { - //Save the raw XML in case it needs to be used later + parse(response, options) { + // Save the raw XML in case it needs to be used later this.set("objectXML", $.parseHTML(response)); - //Define the namespaces - var RDF = rdf.Namespace(this.namespaces.RDF), - FOAF = rdf.Namespace(this.namespaces.FOAF), - OWL = rdf.Namespace(this.namespaces.OWL), - DC = rdf.Namespace(this.namespaces.DC), - ORE = rdf.Namespace(this.namespaces.ORE), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - CITO = rdf.Namespace(this.namespaces.CITO); - - var memberStatements = [], - memberURIParts, - memberPIDStr, - memberPID, - memberModel, - models = []; // the models returned by parse() + // Define the namespaces + const RDF = rdf.Namespace(this.namespaces.RDF); + const FOAF = rdf.Namespace(this.namespaces.FOAF); + const OWL = rdf.Namespace(this.namespaces.OWL); + const DC = rdf.Namespace(this.namespaces.DC); + const ORE = rdf.Namespace(this.namespaces.ORE); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const CITO = rdf.Namespace(this.namespaces.CITO); + + let memberStatements = []; + let memberURIParts; + let memberPIDStr; + let memberPID; + let memberModel; + const models = []; // the models returned by parse() try { rdf.parse( @@ -295,15 +288,15 @@ define([ undefined, ); - var memberPIDs = [], - members = [], - currentMembers = this.get("members"), - model = this; + const memberPIDs = []; + const members = []; + const currentMembers = this.get("members"); + const model = this; // Get system metadata for each member to eval the formatId _.each( memberStatements, - function (memberStatement) { + (memberStatement) => { memberURIParts = memberStatement.object.value.split("/"); memberPIDStr = _.last(memberURIParts); memberPID = decodeURIComponent(memberPIDStr); @@ -311,16 +304,17 @@ define([ if (memberPID) { memberPIDs.push(memberPID); - //Get the current model from the member list, if it exists - var existingModel = _.find(currentMembers, function (m) { - return m.get("id") == decodeURIComponent(memberPID); - }); + // Get the current model from the member list, if it exists + const existingModel = _.find( + currentMembers, + (m) => m.get("id") == decodeURIComponent(memberPID), + ); - //Add the existing model to the new member list + // Add the existing model to the new member list if (existingModel) { members.push(existingModel); } - //Or create a new SolrResult model + // Or create a new SolrResult model else { members.push( new SolrResult({ @@ -333,32 +327,31 @@ define([ this, ); - //Get the documents relationships - var documentedByStatements = this.dataPackageGraph.statementsMatching( + // Get the documents relationships + const documentedByStatements = + this.dataPackageGraph.statementsMatching( undefined, CITO("isDocumentedBy"), undefined, undefined, - ), - metadataPids = []; + ); + const metadataPids = []; _.each( documentedByStatements, - function (statement) { - //Get the data object that is documentedBy metadata - var dataPid = decodeURIComponent( - _.last(statement.subject.value.split("/")), - ), - dataObj = _.find(members, function (m) { - return m.get("id") == dataPid; - }), - metadataPid = _.last(statement.object.value.split("/")); + (statement) => { + // Get the data object that is documentedBy metadata + const dataPid = decodeURIComponent( + _.last(statement.subject.value.split("/")), + ); + const dataObj = _.find(members, (m) => m.get("id") == dataPid); + const metadataPid = _.last(statement.object.value.split("/")); - //Save this as a metadata model + // Save this as a metadata model metadataPids.push(metadataPid); - //Set the isDocumentedBy field - var isDocBy = dataObj.get("isDocumentedBy"); + // Set the isDocumentedBy field + let isDocBy = dataObj.get("isDocumentedBy"); if (isDocBy && Array.isArray(isDocBy)) isDocBy.push(metadataPid); else if (isDocBy && !Array.isArray(isDocBy)) isDocBy = [isDocBy, metadataPid]; @@ -369,13 +362,13 @@ define([ this, ); - //Get the metadata models and mark them as metadata - var metadataModels = _.filter(members, function (m) { - return _.contains(metadataPids, m.get("id")); - }); + // Get the metadata models and mark them as metadata + const metadataModels = _.filter(members, (m) => + _.contains(metadataPids, m.get("id")), + ); _.invoke(metadataModels, "set", "formatType", "METADATA"); - //Keep the pids in the collection for easy access later + // Keep the pids in the collection for easy access later this.set("memberIds", memberPIDs); this.set("members", members); } catch (error) { @@ -387,17 +380,17 @@ define([ /* * Overwrite the Backbone.Model.save() function to set custom options */ - save: function (attrs, options) { + save(attrs, options) { if (!options) var options = {}; - //Get the system metadata first + // Get the system metadata first if (!this.get("hasSystemMetadata")) { - var model = this; + const model = this; var requestSettings = { url: MetacatUI.appModel.get("metaServiceUrl") + encodeURIComponent(this.get("id")), - success: function (response) { + success(response) { model.parseSysMeta(response); model.set("hasSystemMetadata", true); @@ -414,26 +407,26 @@ define([ return; } - //Create a new pid if we are updating the object + // Create a new pid if we are updating the object if (!options.sysMetaOnly) { - //Set a new id + // Set a new id var oldPid = this.get("id"); this.set("oldPid", oldPid); - this.set("id", "urn:uuid:" + uuid.v4()); + this.set("id", `urn:uuid:${uuid.v4()}`); this.set("obsoletes", oldPid); this.set("obsoletedBy", null); this.set("archived", false); } - //Create the system metadata - var sysMetaXML = this.serializeSysMeta(); + // Create the system metadata + const sysMetaXML = this.serializeSysMeta(); - //Send the new pid, old pid, and system metadata - var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); - var formData = new FormData(); + // Send the new pid, old pid, and system metadata + const xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); + const formData = new FormData(); formData.append("sysmeta", xmlBlob, "sysmeta"); - //Let's try updating the system metadata for now + // Let's try updating the system metadata for now if (options.sysMetaOnly) { formData.append("pid", this.get("id")); @@ -444,8 +437,8 @@ define([ contentType: false, processData: false, data: formData, - success: function (response) {}, - error: function (data) { + success(response) {}, + error(data) { console.log("error updating system metadata"); }, }; @@ -456,20 +449,20 @@ define([ ), ); } else { - //Add the ids to the form data + // Add the ids to the form data formData.append("newPid", this.get("id")); formData.append("pid", oldPid); - //Create the resource map XML - var mapXML = this.serialize(); - var mapBlob = new Blob([mapXML], { type: "application/xml" }); + // Create the resource map XML + const mapXML = this.serialize(); + const mapBlob = new Blob([mapXML], { type: "application/xml" }); formData.append("object", mapBlob); - //Get the size of the new resource map + // Get the size of the new resource map this.set("size", mapBlob.size); - //Get the new checksum of the resource map - var checksum = md5(mapXML); + // Get the new checksum of the resource map + const checksum = md5(mapXML); this.set("checksum", checksum); var requestSettings = { @@ -479,8 +472,8 @@ define([ contentType: false, processData: false, data: formData, - success: function (response) {}, - error: function (data) { + success(response) {}, + error(data) { console.log("error udpating object"); }, }; @@ -493,15 +486,15 @@ define([ } }, - parseSysMeta: function (response) { + parseSysMeta(response) { this.set("sysMetaXML", $.parseHTML(response)); - var responseDoc = $.parseHTML(response), - systemMetadata, - prependXML = "", - appendXML = ""; + const responseDoc = $.parseHTML(response); + let systemMetadata; + const prependXML = ""; + const appendXML = ""; - for (var i = 0; i < responseDoc.length; i++) { + for (let i = 0; i < responseDoc.length; i++) { if ( responseDoc[i].nodeType == 1 && responseDoc[i].localName.indexOf("systemmetadata") > -1 @@ -509,10 +502,10 @@ define([ systemMetadata = responseDoc[i]; } - //Parse the XML to JSON - var sysMetaValues = this.toJson(systemMetadata), - camelCasedValues = {}; - //Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code + // Parse the XML to JSON + const sysMetaValues = this.toJson(systemMetadata); + const camelCasedValues = {}; + // Convert the JSON to a camel-cased version, which matches Solr and is easier to work with in code _.each( Object.keys(sysMetaValues), function (key) { @@ -521,72 +514,74 @@ define([ this, ); - //Set the values on the model + // Set the values on the model this.set(camelCasedValues); }, - serialize: function () { - //Create an RDF serializer + serialize() { + // Create an RDF serializer var serializer = rdf.Serializer(); serializer.store = this.dataPackageGraph; - //Define the namespaces - var ORE = rdf.Namespace(this.namespaces.ORE), - CITO = rdf.Namespace(this.namespaces.CITO); + // Define the namespaces + const ORE = rdf.Namespace(this.namespaces.ORE); + const CITO = rdf.Namespace(this.namespaces.CITO); - //Get the pid of this package - depends on whether we are updating or creating a resource map - var pid = this.get("id"), - oldPid = this.get("oldPid"), - updating = oldPid ? true : false; + // Get the pid of this package - depends on whether we are updating or creating a resource map + const pid = this.get("id"); + const oldPid = this.get("oldPid"); + const updating = !!oldPid; - //Update the pids in the RDF graph only if we are updating the resource map with a new pid + // Update the pids in the RDF graph only if we are updating the resource map with a new pid if (updating) { - //Find the identifier statement in the resource map - var idNode = rdf.lit(oldPid), - idStatement = this.dataPackageGraph.statementsMatching( - undefined, - undefined, - idNode, - ); + // Find the identifier statement in the resource map + const idNode = rdf.lit(oldPid); + const idStatement = this.dataPackageGraph.statementsMatching( + undefined, + undefined, + idNode, + ); - //Get the CN Resolve Service base URL from the resource map (mostly important in dev environments where it will not always be cn.dataone.org) - var cnResolveUrl = idStatement[0].subject.value.substring( + // Get the CN Resolve Service base URL from the resource map (mostly important in dev environments where it will not always be cn.dataone.org) + const cnResolveUrl = idStatement[0].subject.value.substring( 0, idStatement[0].subject.value.indexOf(oldPid), ); this.dataPackageGraph.cnResolveUrl = cnResolveUrl; - //Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph - var oldPidVariations = [ + // Create variations of the resource map ID using the resolve URL so we can always find it in the RDF graph + const oldPidVariations = [ oldPid, encodeURIComponent(oldPid), cnResolveUrl + encodeURIComponent(oldPid), ]; - //Get all the isAggregatedBy statements - var aggregationNode = rdf.sym( - cnResolveUrl + encodeURIComponent(oldPid) + "#aggregation", - ), - aggByStatements = this.dataPackageGraph.statementsMatching( - undefined, - ORE("isAggregatedBy"), - ); + // Get all the isAggregatedBy statements + const aggregationNode = rdf.sym( + `${cnResolveUrl + encodeURIComponent(oldPid)}#aggregation`, + ); + const aggByStatements = this.dataPackageGraph.statementsMatching( + undefined, + ORE("isAggregatedBy"), + ); - //Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph - var idsFromXML = []; + // Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph + const idsFromXML = []; _.each( aggByStatements, - function (statement) { - //Check if the resource map ID is the old existing id, so we don't collect ids that are not about this resource map + (statement) => { + // Check if the resource map ID is the old existing id, so we don't collect ids that are not about this resource map if ( - _.find(oldPidVariations, function (oldPidV) { - return oldPidV + "#aggregation" == statement.object.value; - }) + _.find( + oldPidVariations, + (oldPidV) => + `${oldPidV}#aggregation` == statement.object.value, + ) ) { - var statementID = statement.subject.value; + const statementID = statement.subject.value; idsFromXML.push(statementID); - //Add variations of the ID so we make sure we account for all the ways they exist in the RDF XML + // Add variations of the ID so we make sure we account for all the ways they exist in the RDF XML if (statementID.indexOf(cnResolveUrl) > -1) idsFromXML.push( statementID.substring(statementID.lastIndexOf("/") + 1), @@ -600,67 +595,66 @@ define([ this, ); - //Get all the ids from this model - var idsFromModel = _.invoke(this.get("members"), "get", "id"); + // Get all the ids from this model + const idsFromModel = _.invoke(this.get("members"), "get", "id"); - //Find the difference between the model IDs and the XML IDs to get a list of added members - var addedIds = _.without( + // Find the difference between the model IDs and the XML IDs to get a list of added members + const addedIds = _.without( _.difference(idsFromModel, idsFromXML), oldPidVariations, ); - //Create variations of all these ids too - var allMemberIds = idsFromModel; - _.each(idsFromModel, function (id) { + // Create variations of all these ids too + const allMemberIds = idsFromModel; + _.each(idsFromModel, (id) => { allMemberIds.push(cnResolveUrl + encodeURIComponent(id)); }); - //Remove any other isAggregatedBy statements that are not listed as members of this model + // Remove any other isAggregatedBy statements that are not listed as members of this model _.each( aggByStatements, function (statement) { if (!_.contains(allMemberIds, statement.subject.value)) this.removeFromAggregation(statement.subject.value); else if ( - _.find(oldPidVariations, function (oldPidV) { - return oldPidV + "#aggregation" == statement.object.value; - }) + _.find( + oldPidVariations, + (oldPidV) => + `${oldPidV}#aggregation` == statement.object.value, + ) ) - statement.object.value = - cnResolveUrl + encodeURIComponent(pid) + "#aggregation"; + statement.object.value = `${cnResolveUrl + encodeURIComponent(pid)}#aggregation`; }, this, ); - //Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID - var aggregationSubjStatements = + // Change all the statements in the RDF where the aggregation is the subject, to reflect the new resource map ID + const aggregationSubjStatements = this.dataPackageGraph.statementsMatching(aggregationNode); - _.each(aggregationSubjStatements, function (statement) { - statement.subject.value = - cnResolveUrl + encodeURIComponent(pid) + "#aggregation"; + _.each(aggregationSubjStatements, (statement) => { + statement.subject.value = `${cnResolveUrl + encodeURIComponent(pid)}#aggregation`; }); - //Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID - var aggregationObjStatements = + // Change all the statements in the RDF where the aggregation is the object, to reflect the new resource map ID + const aggregationObjStatements = this.dataPackageGraph.statementsMatching( undefined, undefined, aggregationNode, ); - _.each(aggregationObjStatements, function (statement) { - statement.object.value = - cnResolveUrl + encodeURIComponent(pid) + "#aggregation"; + _.each(aggregationObjStatements, (statement) => { + statement.object.value = `${cnResolveUrl + encodeURIComponent(pid)}#aggregation`; }); - //Change all the resource map subject nodes in the RDF graph - var rMapNode = rdf.sym(cnResolveUrl + encodeURIComponent(oldPid)); - var rMapStatements = + // Change all the resource map subject nodes in the RDF graph + const rMapNode = rdf.sym(cnResolveUrl + encodeURIComponent(oldPid)); + const rMapStatements = this.dataPackageGraph.statementsMatching(rMapNode); - _.each(rMapStatements, function (statement) { + _.each(rMapStatements, (statement) => { statement.subject.value = cnResolveUrl + encodeURIComponent(pid); }); - //Change the idDescribedBy statement - var isDescribedByStatements = + // Change the idDescribedBy statement + const isDescribedByStatements = this.dataPackageGraph.statementsMatching( undefined, ORE("isDescribedBy"), @@ -669,7 +663,7 @@ define([ if (isDescribedByStatements[0]) isDescribedByStatements[0].object.value = pid; - //Add nodes for new package members + // Add nodes for new package members _.each( addedIds, function (id) { @@ -678,26 +672,26 @@ define([ this, ); - //Change all the resource map identifier literal node in the RDF graph + // Change all the resource map identifier literal node in the RDF graph if (idStatement[0]) idStatement[0].object.value = pid; } - //Now serialize the RDF XML + // Now serialize the RDF XML var serializer = rdf.Serializer(); serializer.store = this.dataPackageGraph; - var xmlString = serializer.statementsToXML( + const xmlString = serializer.statementsToXML( this.dataPackageGraph.statements, ); return xmlString; }, - serializeSysMeta: function () { - //Get the system metadata XML that currently exists in the system - var xml = $(this.get("sysMetaXML")); + serializeSysMeta() { + // Get the system metadata XML that currently exists in the system + const xml = $(this.get("sysMetaXML")); - //Update the system metadata values + // Update the system metadata values xml.find("serialversion").text(this.get("serialVersion") || "0"); xml.find("identifier").text(this.get("newPid") || this.get("id")); xml.find("formatid").text(this.get("formatId")); @@ -743,49 +737,48 @@ define([ xml.find("obsoletedby").text(this.get("obsoletedBy")); else xml.find("obsoletedby").remove(); - //Write the access policy - var accessPolicyXML = "\n"; - _.each(this.get("accesspolicy"), function (policy, policyType, all) { - var fullPolicy = all[policyType]; + // Write the access policy + let accessPolicyXML = "\n"; + _.each(this.get("accesspolicy"), (policy, policyType, all) => { + const fullPolicy = all[policyType]; - _.each(fullPolicy, function (policyPart) { - accessPolicyXML += "\t<" + policyType + ">\n"; + _.each(fullPolicy, (policyPart) => { + accessPolicyXML += `\t<${policyType}>\n`; - accessPolicyXML += - "\t\t" + policyPart.subject + "\n"; + accessPolicyXML += `\t\t${policyPart.subject}\n`; - var permissions = Array.isArray(policyPart.permission) + const permissions = Array.isArray(policyPart.permission) ? policyPart.permission : [policyPart.permission]; - _.each(permissions, function (perm) { - accessPolicyXML += "\t\t" + perm + "\n"; + _.each(permissions, (perm) => { + accessPolicyXML += `\t\t${perm}\n`; }); - accessPolicyXML += "\t\n"; + accessPolicyXML += `\t\n`; }); }); accessPolicyXML += ""; - //Replace the old access policy with the new one + // Replace the old access policy with the new one xml.find("accesspolicy").replaceWith(accessPolicyXML); - var xmlString = $(document.createElement("div")) + let xmlString = $(document.createElement("div")) .append(xml.clone()) .html(); - //Now camel case the nodes + // Now camel case the nodes _.each( Object.keys(this.sysMetaNodeMap), function (name, i, allNodeNames) { - var regEx = new RegExp("<" + name, "g"); + var regEx = new RegExp(`<${name}`, "g"); xmlString = xmlString.replace( regEx, - "<" + this.sysMetaNodeMap[name], + `<${this.sysMetaNodeMap[name]}`, ); - var regEx = new RegExp(name + ">", "g"); + var regEx = new RegExp(`${name}>`, "g"); xmlString = xmlString.replace( regEx, - this.sysMetaNodeMap[name] + ">", + `${this.sysMetaNodeMap[name]}>`, ); }, this, @@ -796,8 +789,8 @@ define([ return xmlString; }, - //Adds a new object to the resource map RDF graph - addToAggregation: function (id) { + // Adds a new object to the resource map RDF graph + addToAggregation(id) { if (id.indexOf(this.dataPackageGraph.cnResolveUrl) < 0) var fullID = this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id); @@ -808,80 +801,78 @@ define([ ); } - //Initialize the namespaces - var ORE = rdf.Namespace(this.namespaces.ORE), - DCTERMS = rdf.Namespace(this.namespaces.DCTERMS), - XML = rdf.Namespace(this.namespaces.XML), - CITO = rdf.Namespace(this.namespaces.CITO); - - //Create a node for this object, the identifier, the resource map, and the aggregation - var objectNode = rdf.sym(fullID), - mapNode = rdf.sym( - this.dataPackageGraph.cnResolveUrl + - encodeURIComponent(this.get("id")), - ), - aggNode = rdf.sym( + // Initialize the namespaces + const ORE = rdf.Namespace(this.namespaces.ORE); + const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); + const XML = rdf.Namespace(this.namespaces.XML); + const CITO = rdf.Namespace(this.namespaces.CITO); + + // Create a node for this object, the identifier, the resource map, and the aggregation + const objectNode = rdf.sym(fullID); + const mapNode = rdf.sym( + this.dataPackageGraph.cnResolveUrl + + encodeURIComponent(this.get("id")), + ); + const aggNode = rdf.sym( + `${ this.dataPackageGraph.cnResolveUrl + - encodeURIComponent(this.get("id")) + - "#aggregation", - ), - idNode = rdf.literal(id, undefined, XML("string")); + encodeURIComponent(this.get("id")) + }#aggregation`, + ); + const idNode = rdf.literal(id, undefined, XML("string")); - //Add the statement: this object isAggregatedBy the resource map aggregation + // Add the statement: this object isAggregatedBy the resource map aggregation this.dataPackageGraph.addStatement( rdf.st(objectNode, ORE("isAggregatedBy"), aggNode), ); - //Add the statement: The resource map aggregation aggregates this object + // Add the statement: The resource map aggregation aggregates this object this.dataPackageGraph.addStatement( rdf.st(aggNode, ORE("aggregates"), objectNode), ); - //Add the statement: This object has the identifier {id} + // Add the statement: This object has the identifier {id} this.dataPackageGraph.addStatement( rdf.st(objectNode, DCTERMS("identifier"), idNode), ); - //Find the metadata doc that describes this object - var model = _.find(this.get("members"), function (m) { - return m.get("id") == id; - }), - isDocBy = model.get("isDocumentedBy"); + // Find the metadata doc that describes this object + const model = _.find(this.get("members"), (m) => m.get("id") == id); + const isDocBy = model.get("isDocumentedBy"); - //If this object is documented by any metadata... + // If this object is documented by any metadata... if (isDocBy) { - //Get the ids of all the metadata objects in this package - var metadataInPackage = _.compact( - _.map(this.get("members"), function (m) { + // Get the ids of all the metadata objects in this package + const metadataInPackage = _.compact( + _.map(this.get("members"), (m) => { if (m.get("formatType") == "METADATA") return m.get("id"); }), ); - //Find the metadata IDs that are in this package that also documents this data object - var metadataIds = Array.isArray(isDocBy) + // Find the metadata IDs that are in this package that also documents this data object + const metadataIds = Array.isArray(isDocBy) ? _.intersection(metadataInPackage, isDocBy) : _.intersection(metadataInPackage, [isDocBy]); - //For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement + // For each metadata that documents this object, add a CITO:isDocumentedBy and CITO:documents statement _.each( metadataIds, function (metaId) { - //Create the named nodes and statements - var memberNode = rdf.sym( - this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id), - ), - metadataNode = rdf.sym( - this.dataPackageGraph.cnResolveUrl + - encodeURIComponent(metaId), - ), - isDocByStatement = rdf.st( - memberNode, - CITO("isDocumentedBy"), - metadataNode, - ), - documentsStatement = rdf.st( - metadataNode, - CITO("documents"), - memberNode, - ); - //Add the statements + // Create the named nodes and statements + const memberNode = rdf.sym( + this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id), + ); + const metadataNode = rdf.sym( + this.dataPackageGraph.cnResolveUrl + encodeURIComponent(metaId), + ); + const isDocByStatement = rdf.st( + memberNode, + CITO("isDocumentedBy"), + metadataNode, + ); + const documentsStatement = rdf.st( + metadataNode, + CITO("documents"), + memberNode, + ); + // Add the statements this.dataPackageGraph.addStatement(isDocByStatement); this.dataPackageGraph.addStatement(documentsStatement); }, @@ -890,108 +881,108 @@ define([ } }, - removeFromAggregation: function (id) { + removeFromAggregation(id) { if (!id.indexOf(this.dataPackageGraph.cnResolveUrl)) id = this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id); - var removedObjNode = rdf.sym(id), - statements = _.union( - this.dataPackageGraph.statementsMatching( - undefined, - undefined, - removedObjNode, - ), - this.dataPackageGraph.statementsMatching(removedObjNode), - ); + const removedObjNode = rdf.sym(id); + const statements = _.union( + this.dataPackageGraph.statementsMatching( + undefined, + undefined, + removedObjNode, + ), + this.dataPackageGraph.statementsMatching(removedObjNode), + ); this.dataPackageGraph.removeStatements(statements); }, - getParentMetadata: function () { - var rMapIds = this.get("resourceMap"); + getParentMetadata() { + const rMapIds = this.get("resourceMap"); - //Create a query that searches for any resourceMap with an id matching one of the parents OR an id that matches one of the parents. - //This will return all members of the parent resource maps AND the parent resource maps themselves - var rMapQuery = "", - idQuery = ""; + // Create a query that searches for any resourceMap with an id matching one of the parents OR an id that matches one of the parents. + // This will return all members of the parent resource maps AND the parent resource maps themselves + let rMapQuery = ""; + let idQuery = ""; if (Array.isArray(rMapIds) && rMapIds.length > 1) { - _.each(rMapIds, function (id, i, ids) { - //At the begininng of the list of ids + _.each(rMapIds, (id, i, ids) => { + // At the begininng of the list of ids if (rMapQuery.length == 0) { rMapQuery += "resourceMap:("; idQuery += "id:("; } - //The id - rMapQuery += "%22" + encodeURIComponent(id) + "%22"; - idQuery += "%22" + encodeURIComponent(id) + "%22"; + // The id + rMapQuery += `%22${encodeURIComponent(id)}%22`; + idQuery += `%22${encodeURIComponent(id)}%22`; - //At the end of the list of ids + // At the end of the list of ids if (i + 1 == ids.length) { rMapQuery += ")"; idQuery += ")"; } - //In-between each id + // In-between each id else { rMapQuery += " OR "; idQuery += " OR "; } }); } else { - //When there is just one parent, the query is simple - var rMapId = Array.isArray(rMapIds) ? rMapIds[0] : rMapIds; - rMapQuery += "resourceMap:%22" + encodeURIComponent(rMapId) + "%22"; - idQuery += "id:%22" + encodeURIComponent(rMapId) + "%22"; + // When there is just one parent, the query is simple + const rMapId = Array.isArray(rMapIds) ? rMapIds[0] : rMapIds; + rMapQuery += `resourceMap:%22${encodeURIComponent(rMapId)}%22`; + idQuery += `id:%22${encodeURIComponent(rMapId)}%22`; } - var query = - "fl=title,id,obsoletedBy,resourceMap" + - "&wt=json" + - "&group=true&group.field=formatType&group.limit=-1" + - "&q=((formatType:METADATA AND " + - rMapQuery + - ") OR " + - idQuery + - ")"; - - var model = this; - var requestSettings = { + const query = + `fl=title,id,obsoletedBy,resourceMap` + + `&wt=json` + + `&group=true&group.field=formatType&group.limit=-1` + + `&q=((formatType:METADATA AND ${rMapQuery}) OR ${idQuery})`; + + const model = this; + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { - var results = data.grouped.formatType.groups, - resourceMapGroup = _.where(results, { - groupValue: "RESOURCE", - })[0], - rMapList = resourceMapGroup ? resourceMapGroup.doclist : null, - rMaps = rMapList ? rMapList.docs : [], - rMapIds = _.pluck(rMaps, "id"), - parents = [], - parentIds = []; - - //As long as this map isn't obsoleted by another map in our results list, we will show it - _.each(rMaps, function (map) { + success(data, textStatus, xhr) { + const results = data.grouped.formatType.groups; + const resourceMapGroup = _.where(results, { + groupValue: "RESOURCE", + })[0]; + const rMapList = resourceMapGroup ? resourceMapGroup.doclist : null; + const rMaps = rMapList ? rMapList.docs : []; + const rMapIds = _.pluck(rMaps, "id"); + const parents = []; + const parentIds = []; + + // As long as this map isn't obsoleted by another map in our results list, we will show it + _.each(rMaps, (map) => { if (!(map.obsoletedBy && _.contains(rMapIds, map.obsoletedBy))) { parents.push(map); parentIds.push(map.id); } }); - var metadataList = _.where(results, { groupValue: "METADATA" })[0], - metadata = - metadataList && metadataList.doclist - ? metadataList.doclist.docs - : [], - metadataModels = []; - - //As long as this map isn't obsoleted by another map in our results list, we will show it - _.each(metadata, function (m) { - //Find the metadata doc that obsoletes this one - var isObsoletedBy = _.findWhere(metadata, { id: m.obsoletedBy }); - - //If one isn't found, then this metadata doc is the most recent - if (typeof isObsoletedBy == "undefined") { - //If this metadata doc is in one of the filtered parent resource maps + const metadataList = _.where(results, { + groupValue: "METADATA", + })[0]; + const metadata = + metadataList && metadataList.doclist + ? metadataList.doclist.docs + : []; + const metadataModels = []; + + // As long as this map isn't obsoleted by another map in our results list, we will show it + _.each(metadata, (m) => { + // Find the metadata doc that obsoletes this one + const isObsoletedBy = _.findWhere(metadata, { + id: m.obsoletedBy, + }); + + // If one isn't found, then this metadata doc is the most recent + if (typeof isObsoletedBy === "undefined") { + // If this metadata doc is in one of the filtered parent resource maps if (_.intersection(parentIds, m.resourceMap).length) { - //Create a SolrResult model and add to an array + // Create a SolrResult model and add to an array metadataModels.push(new SolrResult(m)); } } @@ -1010,27 +1001,28 @@ define([ ); }, - //Create the URL string that is used to download this package - getURL: function () { - var url = null; + // Create the URL string that is used to download this package + getURL() { + let url = null; - //If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from + // If we haven't set a packageServiceURL upon app initialization and we are querying a CN, then the packageServiceURL is dependent on the MN this package is from if ( MetacatUI.appModel.get("d1Service").toLowerCase().indexOf("cn/") > -1 && MetacatUI.nodeModel.get("members").length ) { - var source = this.get("datasource"), - node = _.find(MetacatUI.nodeModel.get("members"), { - identifier: source, - }); + const source = this.get("datasource"); + const node = _.find(MetacatUI.nodeModel.get("members"), { + identifier: source, + }); - //If this node has MNRead v2 services... + // If this node has MNRead v2 services... if (node && node.readv2) - url = - node.baseURL + - "/v2/packages/application%2Fbagit-097/" + - encodeURIComponent(this.get("id")); + url = `${ + node.baseURL + }/v2/packages/application%2Fbagit-097/${encodeURIComponent( + this.get("id"), + )}`; } else if (MetacatUI.appModel.get("packageServiceUrl")) url = MetacatUI.appModel.get("packageServiceUrl") + @@ -1040,60 +1032,59 @@ define([ return url; }, - createNestedPackages: function () { - var parentPackage = this, - nestedPackages = this.getNestedPackages(), - numNestedPackages = nestedPackages.length, - numComplete = 0; + createNestedPackages() { + const parentPackage = this; + const nestedPackages = this.getNestedPackages(); + const numNestedPackages = nestedPackages.length; + let numComplete = 0; - _.each(nestedPackages, function (nestedPackage, i, nestedPackages) { - //Flag the parent model as complete when all the nested package info is ready - nestedPackage.on("complete", function () { + _.each(nestedPackages, (nestedPackage, i, nestedPackages) => { + // Flag the parent model as complete when all the nested package info is ready + nestedPackage.on("complete", () => { numComplete++; - //This is the last package in this package - finish up details and flag as complete + // This is the last package in this package - finish up details and flag as complete if (numNestedPackages == numComplete) { - var sorted = _.sortBy(parentPackage.get("members"), function (p) { - return p.get("id"); - }); + const sorted = _.sortBy(parentPackage.get("members"), (p) => + p.get("id"), + ); parentPackage.set("members", sorted); parentPackage.flagComplete(); } }); - //Only look one-level deep at all times to avoid going down a rabbit hole + // Only look one-level deep at all times to avoid going down a rabbit hole if ( nestedPackage.get("parentPackage") && nestedPackage.get("parentPackage").get("parentPackage") ) { nestedPackage.flagComplete(); - return; } else { - //Get the members of this nested package + // Get the members of this nested package nestedPackage.getMembers(); } }); }, - getNestedPackages: function () { + getNestedPackages() { return _.where(this.get("members"), { type: "Package" }); }, - getMemberNames: function () { - var metadata = this.getMetadata(); + getMemberNames() { + const metadata = this.getMetadata(); if (!metadata) return false; - //Load the rendered metadata from the view service - var viewService = + // Load the rendered metadata from the view service + const viewService = MetacatUI.appModel.get("viewServiceUrl") + encodeURIComponent(metadata.get("id")); - var requestSettings = { + const requestSettings = { url: viewService, - success: function (data, response, xhr) { + success(data, response, xhr) { if (solrResult.get("formatType") == "METADATA") entityName = solrResult.get("title"); else { - var container = viewRef.findEntityDetailsContainer( + const container = viewRef.findEntityDetailsContainer( solrResult.get("id"), ); if (container && container.length > 0) { @@ -1125,40 +1116,36 @@ define([ * Will query for the derivations of this package, and sort all entities in the prov trace * into sources and derivations. */ - getProvTrace: function () { - var model = this; + getProvTrace() { + const model = this; - //See if there are any prov fields in our index before continuing + // See if there are any prov fields in our index before continuing if (!MetacatUI.appSearchModel.getProvFields()) return this; - //Start keeping track of the sources and derivations - var sources = new Array(), - derivations = new Array(); + // Start keeping track of the sources and derivations + let sources = new Array(); + let derivations = new Array(); - //Search for derivations of this package - var derivationsQuery = - MetacatUI.appSearchModel.getGroupedQuery( - "prov_wasDerivedFrom", - _.map(this.get("members"), function (m) { - return m.get("id"); - }), - "OR", - ) + "%20-obsoletedBy:*"; + // Search for derivations of this package + const derivationsQuery = `${MetacatUI.appSearchModel.getGroupedQuery( + "prov_wasDerivedFrom", + _.map(this.get("members"), (m) => m.get("id")), + "OR", + )}%20-obsoletedBy:*`; - var requestSettings = { + const requestSettings = { url: - MetacatUI.appModel.get("queryServiceUrl") + - "&q=" + - derivationsQuery + - "&wt=json&rows=1000" + - "&fl=id,resourceMap,documents,isDocumentedBy,prov_wasDerivedFrom", - success: function (data) { - _.each(data.response.docs, function (result) { + `${MetacatUI.appModel.get( + "queryServiceUrl", + )}&q=${derivationsQuery}&wt=json&rows=1000` + + `&fl=id,resourceMap,documents,isDocumentedBy,prov_wasDerivedFrom`, + success(data) { + _.each(data.response.docs, (result) => { derivations.push(result.id); }); - //Make arrays of unique IDs of objects that are sources or derivations of this package. - _.each(model.get("members"), function (member, i) { + // Make arrays of unique IDs of objects that are sources or derivations of this package. + _.each(model.get("members"), (member, i) => { if (member.type == "Package") return; if (member.hasProvTrace()) { @@ -1167,11 +1154,11 @@ define([ } }); - //Save the arrays of sources and derivations + // Save the arrays of sources and derivations model.set("sources", sources); model.set("derivations", derivations); - //Now get metadata about all the entities in the prov trace not in this package + // Now get metadata about all the entities in the prov trace not in this package model.getExternalProvTrace(); }, }; @@ -1183,97 +1170,88 @@ define([ ); }, - getExternalProvTrace: function () { - var model = this; + getExternalProvTrace() { + const model = this; - //Compact our list of ids that are in the prov trace by combining the sources and derivations and removing ids of members of this package - var externalProvEntities = _.difference( + // Compact our list of ids that are in the prov trace by combining the sources and derivations and removing ids of members of this package + const externalProvEntities = _.difference( _.union(this.get("sources"), this.get("derivations")), this.get("memberIds"), ); - //If there are no sources or derivations, then we do not need to find resource map ids for anything + // If there are no sources or derivations, then we do not need to find resource map ids for anything if (!externalProvEntities.length) { - //Save this prov trace on a package-member/document/object level. + // Save this prov trace on a package-member/document/object level. if (this.get("sources").length || this.get("derivations").length) this.setMemberProvTrace(); - //Flag that the provenance trace is complete + // Flag that the provenance trace is complete this.set("provenanceFlag", "complete"); return this; - } else { - //Create a query where we retrieve the ID of the resource map of each source and derivation - var idQuery = MetacatUI.appSearchModel.getGroupedQuery( - "id", - externalProvEntities, - "OR", - ); - - //Create a query where we retrieve the metadata for each source and derivation - var metadataQuery = MetacatUI.appSearchModel.getGroupedQuery( - "documents", - externalProvEntities, - "OR", - ); } + // Create a query where we retrieve the ID of the resource map of each source and derivation + const idQuery = MetacatUI.appSearchModel.getGroupedQuery( + "id", + externalProvEntities, + "OR", + ); - //TODO: Find the products of programs/executions + // Create a query where we retrieve the metadata for each source and derivation + const metadataQuery = MetacatUI.appSearchModel.getGroupedQuery( + "documents", + externalProvEntities, + "OR", + ); - //Make a comma-separated list of the provenance field names - var provFieldList = ""; + // TODO: Find the products of programs/executions + + // Make a comma-separated list of the provenance field names + let provFieldList = ""; _.each( MetacatUI.appSearchModel.getProvFields(), - function (fieldName, i, list) { + (fieldName, i, list) => { provFieldList += fieldName; if (i < list.length - 1) provFieldList += ","; }, ); - //Combine the two queries with an OR operator + // Combine the two queries with an OR operator if (idQuery.length && metadataQuery.length) - var combinedQuery = idQuery + "%20OR%20" + metadataQuery; + var combinedQuery = `${idQuery}%20OR%20${metadataQuery}`; else return this; - //the full and final query in Solr syntax - var query = - "q=" + - combinedQuery + - "&fl=id,resourceMap,documents,isDocumentedBy,formatType,formatId,dateUploaded,rightsHolder,datasource,prov_instanceOfClass," + - provFieldList + - "&rows=100&wt=json"; + // the full and final query in Solr syntax + const query = `q=${combinedQuery}&fl=id,resourceMap,documents,isDocumentedBy,formatType,formatId,dateUploaded,rightsHolder,datasource,prov_instanceOfClass,${provFieldList}&rows=100&wt=json`; - //Send the query to the query service - var requestSettings = { + // Send the query to the query service + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { - //Do any of our docs have multiple resource maps? - var hasMultipleMaps = _.filter(data.response.docs, function (doc) { - return ( + success(data, textStatus, xhr) { + // Do any of our docs have multiple resource maps? + const hasMultipleMaps = _.filter( + data.response.docs, + (doc) => typeof doc.resourceMap !== "undefined" && - doc.resourceMap.length > 1 - ); - }); - //If so, we want to find the latest version of each resource map and only represent that one in the Prov Chart + doc.resourceMap.length > 1, + ); + // If so, we want to find the latest version of each resource map and only represent that one in the Prov Chart if (typeof hasMultipleMaps !== "undefined") { - var allMapIDs = _.uniq( + const allMapIDs = _.uniq( _.flatten(_.pluck(hasMultipleMaps, "resourceMap")), ); if (allMapIDs.length) { - var query = - "q=+-obsoletedBy:*+" + - MetacatUI.appSearchModel.getGroupedQuery( + const query = + `q=+-obsoletedBy:*+${MetacatUI.appSearchModel.getGroupedQuery( "id", allMapIDs, "OR", - ) + - "&fl=obsoletes,id" + - "&wt=json"; - var requestSettings = { + )}&fl=obsoletes,id` + `&wt=json`; + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (mapData, textStatus, xhr) { - //Create a list of resource maps that are not obsoleted by any other resource map retrieved - var resourceMaps = mapData.response.docs; + success(mapData, textStatus, xhr) { + // Create a list of resource maps that are not obsoleted by any other resource map retrieved + const resourceMaps = mapData.response.docs; model.obsoletedResourceMaps = _.pluck( resourceMaps, @@ -1308,21 +1286,21 @@ define([ return this; }, - sortProvTrace: function (docs) { - var model = this; + sortProvTrace(docs) { + const model = this; - //Start an array to hold the packages in the prov trace - var sourcePackages = new Array(), - derPackages = new Array(), - sourceDocs = new Array(), - derDocs = new Array(), - sourceIDs = this.get("sources"), - derivationIDs = this.get("derivations"); + // Start an array to hold the packages in the prov trace + const sourcePackages = new Array(); + const derPackages = new Array(); + const sourceDocs = new Array(); + const derDocs = new Array(); + const sourceIDs = this.get("sources"); + const derivationIDs = this.get("derivations"); - //Separate the results into derivations and sources and group by their resource map. - _.each(docs, function (doc, i) { - var docModel = new SolrResult(doc), - mapIds = docModel.get("resourceMap"); + // Separate the results into derivations and sources and group by their resource map. + _.each(docs, (doc, i) => { + const docModel = new SolrResult(doc); + const mapIds = docModel.get("resourceMap"); if ( (typeof mapIds === "undefined" || !mapIds) && @@ -1330,7 +1308,7 @@ define([ (typeof docModel.get("isDocumentedBy") === "undefined" || !docModel.get("isDocumentedBy")) ) { - //If this object is not in a resource map and does not have metadata, it is a "naked" data doc, so save it by itself + // If this object is not in a resource map and does not have metadata, it is a "naked" data doc, so save it by itself if (_.contains(sourceIDs, doc.id)) sourceDocs.push(docModel); if (_.contains(derivationIDs, doc.id)) derDocs.push(docModel); } else if ( @@ -1338,23 +1316,24 @@ define([ docModel.get("formatType") == "DATA" && docModel.get("isDocumentedBy") ) { - //If this data doc does not have a resource map but has a metadata doc that documents it, create a blank package model and save it - var p = new PackageModel({ + // If this data doc does not have a resource map but has a metadata doc that documents it, create a blank package model and save it + const p = new PackageModel({ members: new Array(docModel), }); - //Add this package model to the sources and/or derivations packages list + // Add this package model to the sources and/or derivations packages list if (_.contains(sourceIDs, docModel.get("id"))) sourcePackages[docModel.get("id")] = p; if (_.contains(derivationIDs, docModel.get("id"))) derPackages[docModel.get("id")] = p; } else if (mapIds.length) { - //If this doc has a resource map, create a package model and SolrResult model and store it - var id = docModel.get("id"); + // If this doc has a resource map, create a package model and SolrResult model and store it + const id = docModel.get("id"); - //Some of these objects may have multiple resource maps - _.each(mapIds, function (mapId, i, list) { + // Some of these objects may have multiple resource maps + _.each(mapIds, (mapId, i, list) => { if (!_.contains(model.obsoletedResourceMaps, mapId)) { - var documentsSource, documentsDerivation; + let documentsSource; + let documentsDerivation; if (docModel.get("formatType") == "METADATA") { if ( _.intersection(docModel.get("documents"), sourceIDs).length @@ -1367,19 +1346,19 @@ define([ documentsDerivation = true; } - //Is this a source object or a metadata doc of a source object? + // Is this a source object or a metadata doc of a source object? if (_.contains(sourceIDs, id) || documentsSource) { - //Have we encountered this source package yet? + // Have we encountered this source package yet? if (!sourcePackages[mapId] && mapId != model.get("id")) { - //Now make a new package model for it + // Now make a new package model for it var p = new PackageModel({ id: mapId, members: new Array(docModel), }); - //Add to the array of source packages + // Add to the array of source packages sourcePackages[mapId] = p; } - //If so, add this member to its package model + // If so, add this member to its package model else if (mapId != model.get("id")) { var memberList = sourcePackages[mapId].get("members"); memberList.push(docModel); @@ -1387,19 +1366,19 @@ define([ } } - //Is this a derivation object or a metadata doc of a derivation object? + // Is this a derivation object or a metadata doc of a derivation object? if (_.contains(derivationIDs, id) || documentsDerivation) { - //Have we encountered this derivation package yet? + // Have we encountered this derivation package yet? if (!derPackages[mapId] && mapId != model.get("id")) { - //Now make a new package model for it + // Now make a new package model for it var p = new PackageModel({ id: mapId, members: new Array(docModel), }); - //Add to the array of source packages + // Add to the array of source packages derPackages[mapId] = p; } - //If so, add this member to its package model + // If so, add this member to its package model else if (mapId != model.get("id")) { var memberList = derPackages[mapId].get("members"); memberList.push(docModel); @@ -1411,13 +1390,13 @@ define([ } }); - //Transform our associative array (Object) of packages into an array - var newArrays = new Array(); + // Transform our associative array (Object) of packages into an array + const newArrays = new Array(); _.each( new Array(sourcePackages, derPackages, sourceDocs, derDocs), - function (provObject) { - var newArray = new Array(), - key; + (provObject) => { + const newArray = new Array(); + let key; for (key in provObject) { newArray.push(provObject[key]); } @@ -1425,114 +1404,114 @@ define([ }, ); - //We now have an array of source packages and an array of derivation packages. + // We now have an array of source packages and an array of derivation packages. model.set("sourcePackages", newArrays[0]); model.set("derivationPackages", newArrays[1]); model.set("sourceDocs", newArrays[2]); model.set("derivationDocs", newArrays[3]); - //Save this prov trace on a package-member/document/object level. + // Save this prov trace on a package-member/document/object level. model.setMemberProvTrace(); - //Flag that the provenance trace is complete + // Flag that the provenance trace is complete model.set("provenanceFlag", "complete"); }, - setMemberProvTrace: function () { - var model = this, - relatedModels = this.get("relatedModels"), - relatedModelIDs = new Array(); + setMemberProvTrace() { + const model = this; + const relatedModels = this.get("relatedModels"); + const relatedModelIDs = new Array(); - //Now for each doc, we want to find which member it is related to - _.each(this.get("members"), function (member, i, members) { + // Now for each doc, we want to find which member it is related to + _.each(this.get("members"), (member, i, members) => { if (member.type == "Package") return; - //Get the sources and derivations of this member - var memberSourceIDs = member.getSources(); - var memberDerIDs = member.getDerivations(); + // Get the sources and derivations of this member + const memberSourceIDs = member.getSources(); + const memberDerIDs = member.getDerivations(); - //Look through each source package, derivation package, source doc, and derivation doc. - _.each(model.get("sourcePackages"), function (pkg, i) { - _.each(pkg.get("members"), function (sourcePkgMember, i) { - //Is this package member a direct source of this package member? + // Look through each source package, derivation package, source doc, and derivation doc. + _.each(model.get("sourcePackages"), (pkg, i) => { + _.each(pkg.get("members"), (sourcePkgMember, i) => { + // Is this package member a direct source of this package member? if (_.contains(memberSourceIDs, sourcePkgMember.get("id"))) - //Save this source package member as a source of this member + // Save this source package member as a source of this member member.set( "provSources", _.union(member.get("provSources"), [sourcePkgMember]), ); - //Save this in the list of related models + // Save this in the list of related models if (!_.contains(relatedModelIDs, sourcePkgMember.get("id"))) { relatedModels.push(sourcePkgMember); relatedModelIDs.push(sourcePkgMember.get("id")); } }); }); - _.each(model.get("derivationPackages"), function (pkg, i) { - _.each(pkg.get("members"), function (derPkgMember, i) { - //Is this package member a direct source of this package member? + _.each(model.get("derivationPackages"), (pkg, i) => { + _.each(pkg.get("members"), (derPkgMember, i) => { + // Is this package member a direct source of this package member? if (_.contains(memberDerIDs, derPkgMember.get("id"))) - //Save this derivation package member as a derivation of this member + // Save this derivation package member as a derivation of this member member.set( "provDerivations", _.union(member.get("provDerivations"), [derPkgMember]), ); - //Save this in the list of related models + // Save this in the list of related models if (!_.contains(relatedModelIDs, derPkgMember.get("id"))) { relatedModels.push(derPkgMember); relatedModelIDs.push(derPkgMember.get("id")); } }); }); - _.each(model.get("sourceDocs"), function (doc, i) { - //Is this package member a direct source of this package member? + _.each(model.get("sourceDocs"), (doc, i) => { + // Is this package member a direct source of this package member? if (_.contains(memberSourceIDs, doc.get("id"))) - //Save this source package member as a source of this member + // Save this source package member as a source of this member member.set( "provSources", _.union(member.get("provSources"), [doc]), ); - //Save this in the list of related models + // Save this in the list of related models if (!_.contains(relatedModelIDs, doc.get("id"))) { relatedModels.push(doc); relatedModelIDs.push(doc.get("id")); } }); - _.each(model.get("derivationDocs"), function (doc, i) { - //Is this package member a direct derivation of this package member? + _.each(model.get("derivationDocs"), (doc, i) => { + // Is this package member a direct derivation of this package member? if (_.contains(memberDerIDs, doc.get("id"))) - //Save this derivation package member as a derivation of this member + // Save this derivation package member as a derivation of this member member.set( "provDerivations", _.union(member.get("provDerivations"), [doc]), ); - //Save this in the list of related models + // Save this in the list of related models if (!_.contains(relatedModelIDs, doc.get("id"))) { relatedModels.push(doc); relatedModelIDs.push(doc.get("id")); } }); - _.each(members, function (otherMember, i) { - //Is this other package member a direct derivation of this package member? + _.each(members, (otherMember, i) => { + // Is this other package member a direct derivation of this package member? if (_.contains(memberDerIDs, otherMember.get("id"))) - //Save this other derivation package member as a derivation of this member + // Save this other derivation package member as a derivation of this member member.set( "provDerivations", _.union(member.get("provDerivations"), [otherMember]), ); - //Is this other package member a direct source of this package member? + // Is this other package member a direct source of this package member? if (_.contains(memberSourceIDs, otherMember.get("id"))) - //Save this other source package member as a source of this member + // Save this other source package member as a source of this member member.set( "provSources", _.union(member.get("provSources"), [otherMember]), ); - //Is this other package member an indirect source or derivation? + // Is this other package member an indirect source or derivation? if ( otherMember.get("type") == "program" && _.contains( @@ -1540,17 +1519,15 @@ define([ otherMember.get("id"), ) ) { - var indirectSources = _.filter(members, function (m) { - return _.contains(otherMember.getInputs(), m.get("id")); - }); - indirectSourcesIds = _.each(indirectSources, function (m) { - return m.get("id"); - }); + const indirectSources = _.filter(members, (m) => + _.contains(otherMember.getInputs(), m.get("id")), + ); + indirectSourcesIds = _.each(indirectSources, (m) => m.get("id")); member.set( "prov_wasDerivedFrom", _.union(member.get("prov_wasDerivedFrom"), indirectSourcesIds), ); - //otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")])); + // otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")])); member.set( "provSources", _.union(member.get("provSources"), indirectSources), @@ -1563,14 +1540,11 @@ define([ otherMember.get("id"), ) ) { - var indirectDerivations = _.filter(members, function (m) { - return _.contains(otherMember.getOutputs(), m.get("id")); - }); - indirectDerivationsIds = _.each( - indirectDerivations, - function (m) { - return m.get("id"); - }, + const indirectDerivations = _.filter(members, (m) => + _.contains(otherMember.getOutputs(), m.get("id")), + ); + indirectDerivationsIds = _.each(indirectDerivations, (m) => + m.get("id"), ); member.set( "prov_hasDerivations", @@ -1579,7 +1553,7 @@ define([ indirectDerivationsIds, ), ); - //otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")])); + // otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")])); member.set( "provDerivations", _.union(member.get("provDerivations"), indirectDerivationsIds), @@ -1587,41 +1561,41 @@ define([ } }); - //Add this member to the list of related models + // Add this member to the list of related models if (!_.contains(relatedModelIDs, member.get("id"))) { relatedModels.push(member); relatedModelIDs.push(member.get("id")); } - //Clear out any duplicates + // Clear out any duplicates member.set("provSources", _.uniq(member.get("provSources"))); member.set("provDerivations", _.uniq(member.get("provDerivations"))); }); - //Update the list of related models + // Update the list of related models this.set("relatedModels", relatedModels); }, - downloadWithCredentials: function () { - //Get info about this object - var url = this.get("url"), - model = this; + downloadWithCredentials() { + // Get info about this object + const url = this.get("url"); + const model = this; - //Create an XHR - var xhr = new XMLHttpRequest(); + // Create an XHR + const xhr = new XMLHttpRequest(); xhr.withCredentials = true; - //When the XHR is ready, create a link with the raw data (Blob) and click the link to download + // When the XHR is ready, create a link with the raw data (Blob) and click the link to download xhr.onload = function () { - //Get the file name from the Content-Disposition header - var filename = xhr.getResponseHeader("Content-Disposition"); + // Get the file name from the Content-Disposition header + let filename = xhr.getResponseHeader("Content-Disposition"); - //As a backup, use the system metadata file name or the id + // As a backup, use the system metadata file name or the id if (!filename) { filename = model.get("filename") || model.get("id"); } - //Add a ".zip" extension if it doesn't exist + // Add a ".zip" extension if it doesn't exist if ( filename.indexOf(".zip") < 0 || filename.indexOf(".zip") != filename.length - 4 @@ -1629,11 +1603,11 @@ define([ filename += ".zip"; } - //For IE, we need to use the navigator API + // For IE, we need to use the navigator API if (navigator && navigator.msSaveOrOpenBlob) { navigator.msSaveOrOpenBlob(xhr.response, filename); } else { - var a = document.createElement("a"); + const a = document.createElement("a"); a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob a.download = filename; // Set the file name. a.style.display = "none"; @@ -1654,7 +1628,7 @@ define([ xhr.onprogress = function (e) { if (e.lengthComputable) { - var percent = (e.loaded / e.total) * 100; + const percent = (e.loaded / e.total) * 100; model.set("downloadPercent", percent); } }; @@ -1669,48 +1643,47 @@ define([ model.get("id"), ); }; - //Open and send the request with the user's auth token + // Open and send the request with the user's auth token xhr.open("GET", url); xhr.responseType = "blob"; xhr.setRequestHeader( "Authorization", - "Bearer " + MetacatUI.appUserModel.get("token"), + `Bearer ${MetacatUI.appUserModel.get("token")}`, ); xhr.send(); }, /* Returns the SolrResult that represents the metadata doc */ - getMetadata: function () { - var members = this.get("members"); - for (var i = 0; i < members.length; i++) { + getMetadata() { + const members = this.get("members"); + for (let i = 0; i < members.length; i++) { if (members[i].get("formatType") == "METADATA") return members[i]; } - //If there are no metadata objects in this package, make sure we have searched for them already + // If there are no metadata objects in this package, make sure we have searched for them already if (!this.complete && !this.pending) this.getMembers(); return false; }, - //Check authority of the Metadata SolrResult model instead - checkAuthority: function () { - //Call the auth service - var authServiceUrl = MetacatUI.appModel.get("authServiceUrl"); + // Check authority of the Metadata SolrResult model instead + checkAuthority() { + // Call the auth service + const authServiceUrl = MetacatUI.appModel.get("authServiceUrl"); if (!authServiceUrl) return false; - var model = this; + const model = this; - var requestSettings = { - url: - authServiceUrl + - encodeURIComponent(this.get("id")) + - "?action=write", + const requestSettings = { + url: `${ + authServiceUrl + encodeURIComponent(this.get("id")) + }?action=write`, type: "GET", - success: function (data, textStatus, xhr) { + success(data, textStatus, xhr) { model.set("isAuthorized", true); model.trigger("change:isAuthorized"); }, - error: function (xhr, textStatus, errorThrown) { + error(xhr, textStatus, errorThrown) { model.set("isAuthorized", false); }, }; @@ -1722,7 +1695,7 @@ define([ ); }, - flagComplete: function () { + flagComplete() { this.complete = true; this.pending = false; this.trigger("complete", this); @@ -1734,42 +1707,42 @@ define([ * @param xml {DOM Element} - An XML or HTML DOM element to convert to json * @returns {object} - A literal JS object that represents the given XML */ - toJson: function (xml) { + toJson(xml) { // Create the return object - var obj = {}; + let obj = {}; // do children if (xml.hasChildNodes()) { - for (var i = 0; i < xml.childNodes.length; i++) { - var item = xml.childNodes.item(i); + for (let i = 0; i < xml.childNodes.length; i++) { + const item = xml.childNodes.item(i); - //If it's an empty text node, skip it + // If it's an empty text node, skip it if (item.nodeType == 3 && !item.nodeValue.trim()) continue; - //Get the node name - var nodeName = item.localName; + // Get the node name + const nodeName = item.localName; - //If it's a new container node, convert it to JSON and add as a new object attribute - if (typeof obj[nodeName] == "undefined" && item.nodeType == 1) { + // If it's a new container node, convert it to JSON and add as a new object attribute + if (typeof obj[nodeName] === "undefined" && item.nodeType == 1) { obj[nodeName] = this.toJson(item); } - //If it's a new text node, just store the text value and add as a new object attribute + // If it's a new text node, just store the text value and add as a new object attribute else if ( - typeof obj[nodeName] == "undefined" && + typeof obj[nodeName] === "undefined" && item.nodeType == 3 ) { obj = item.nodeValue; } - //If this node name is already stored as an object attribute... - else if (typeof obj[nodeName] != "undefined") { - //Cache what we have now - var old = obj[nodeName]; + // If this node name is already stored as an object attribute... + else if (typeof obj[nodeName] !== "undefined") { + // Cache what we have now + let old = obj[nodeName]; if (!Array.isArray(old)) old = [old]; - //Create a new object to store this node info + // Create a new object to store this node info var newNode = {}; - //Add the new node info to the existing array we have now + // Add the new node info to the existing array we have now if (item.nodeType == 1) { newNode = this.toJson(item); var newArray = old.concat(newNode); @@ -1778,36 +1751,36 @@ define([ var newArray = old.concat(newNode); } - //Store the attributes for this node - _.each(item.attributes, function (attr) { + // Store the attributes for this node + _.each(item.attributes, (attr) => { newNode[attr.localName] = attr.nodeValue; }); - //Replace the old array with the updated one + // Replace the old array with the updated one obj[nodeName] = newArray; - //Exit + // Exit continue; } - //Store the attributes for this node - /*_.each(item.attributes, function(attr){ + // Store the attributes for this node + /* _.each(item.attributes, function(attr){ obj[nodeName][attr.localName] = attr.nodeValue; - });*/ + }); */ } } return obj; }, - //Sums up the byte size of each member - getTotalSize: function () { + // Sums up the byte size of each member + getTotalSize() { if (this.get("totalSize")) return this.get("totalSize"); if (this.get("members").length == 1) { var totalSize = this.get("members")[0].get("size"); } else { - var totalSize = _.reduce(this.get("members"), function (sum, member) { - if (typeof sum == "object") sum = sum.get("size"); + var totalSize = _.reduce(this.get("members"), (sum, member) => { + if (typeof sum === "object") sum = sum.get("size"); return sum + member.get("size"); }); @@ -1816,37 +1789,6 @@ define([ this.set("totalSize", totalSize); return totalSize; }, - - /****************************/ - /** - * Convert number of bytes into human readable format - * - * @param integer bytes Number of bytes to convert - * @param integer precision Number of digits after the decimal separator - * @return string - */ - bytesToSize: function (bytes, precision) { - var kibibyte = 1024; - var mebibyte = kibibyte * 1024; - var gibibyte = mebibyte * 1024; - var tebibyte = gibibyte * 1024; - - if (typeof bytes === "undefined") var bytes = this.get("size"); - - if (bytes >= 0 && bytes < kibibyte) { - return bytes + " B"; - } else if (bytes >= kibibyte && bytes < mebibyte) { - return (bytes / kibibyte).toFixed(precision) + " KiB"; - } else if (bytes >= mebibyte && bytes < gibibyte) { - return (bytes / mebibyte).toFixed(precision) + " MiB"; - } else if (bytes >= gibibyte && bytes < tebibyte) { - return (bytes / gibibyte).toFixed(precision) + " GiB"; - } else if (bytes >= tebibyte) { - return (bytes / tebibyte).toFixed(precision) + " TiB"; - } else { - return bytes + " B"; - } - }, }, ); return PackageModel; diff --git a/src/js/models/SolrResult.js b/src/js/models/SolrResult.js index 3c55466d9..4bf2cb00a 100644 --- a/src/js/models/SolrResult.js +++ b/src/js/models/SolrResult.js @@ -859,38 +859,6 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) { //If nothing works so far, return an empty array return []; }, - - /****************************/ - - /** - * Convert number of bytes into human readable format - * - * @param integer bytes Number of bytes to convert - * @param integer precision Number of digits after the decimal separator - * @return string - */ - bytesToSize: function (bytes, precision) { - var kibibyte = 1024; - var mebibyte = kibibyte * 1024; - var gibibyte = mebibyte * 1024; - var tebibyte = gibibyte * 1024; - - if (typeof bytes === "undefined") var bytes = this.get("size"); - - if (bytes >= 0 && bytes < kibibyte) { - return bytes + " B"; - } else if (bytes >= kibibyte && bytes < mebibyte) { - return (bytes / kibibyte).toFixed(precision) + " KiB"; - } else if (bytes >= mebibyte && bytes < gibibyte) { - return (bytes / mebibyte).toFixed(precision) + " MiB"; - } else if (bytes >= gibibyte && bytes < tebibyte) { - return (bytes / gibibyte).toFixed(precision) + " GiB"; - } else if (bytes >= tebibyte) { - return (bytes / tebibyte).toFixed(precision) + " TiB"; - } else { - return bytes + " B"; - } - }, }, ); return SolrResult; diff --git a/src/js/templates/mdqRun.html b/src/js/templates/mdqRun.html index eff1a3cd9..2a5923ee1 100644 --- a/src/js/templates/mdqRun.html +++ b/src/js/templates/mdqRun.html @@ -1,311 +1,117 @@
-<% if (typeof objectIdentifier === 'undefined') { %> -
-
- -
-
-
- -<% } %> - -
- -<% if (typeof groupedResults !== 'undefined') { %> - - - -
-
- - -

- After running your metadata against our standard set of metadata, data, and congruency checks, - we have found the following potential issues. - Please assist us in improving the discoverability and reusability of your research data by addressing the issues below. -

-
- -
- -
- -
-

- Assessment suite: - -

+ <% if (typeof objectIdentifier === 'undefined') { %> +
+
+ +
+
+
+ <% } %> + +
+ <% if (typeof groupedResults !== 'undefined') { %> + + + +
+
+

+ After running your metadata against our standard set of metadata, data, and congruency checks, + we have found the following potential issues. + Please assist us in improving the discoverability and reusability of your research data by addressing the issues below. +

+
+ +
+ +
+
+

+ Assessment suite: + +

- <% - var types = _.keys(groupedByType); - _.each(types, function(type) { - var results = groupedByType[type]; - var total = results.length; - var success = 0; - _.each(results, function(result) { - if (result.get("status") == "SUCCESS") { - success++; - } - }); - var width = (success/total) * 100; - width = width.toFixed(0); - - %> - <%=type%>: <%=width%>% complete -
-
- -
-
- <% }); %> -
-
- - <% - - var total = checkCount; - if (groupedResults.BLUE) { - total = total - groupedResults.BLUE.length; - } - - %> -
    - <% if (groupedResults.GREEN) { %> - - - - - - <% - _.each(groupedResults.GREEN, function(result) { - %> - -
  • - - - <% - _.each(result.get("output"), function(output) { - %> - <% if (output.type && output.type.includes('image')) { %> - - - <% } else { %> -
    <%- output.value %>
    - <% } %> - <% - }); - %> -
    - - " - data-content="<%= result.get("check").description %>"> - - - - - <%= result.get("status") %> - <%= result.get("check").level %> - <%= result.get("check").type %> - -
  • - <% }); %> - <% } %> - - <% if (groupedResults.ORANGE) { %> - - <% - _.each(groupedResults.ORANGE, function(result) { - %> - -
  • - - - - - - - <% - _.each(result.get("output"), function(output) { - %> - <% if (output.type && output.type.includes('image')) { %> - - - <% } else { %> -
    <%- output.value %>
    - <% } %> - <% - }); - %> -
    - - " - data-content="<%= result.get("check").description %>"> - - - - - <%= result.get("status") %> - <%= result.get("check").level %> - <%= result.get("check").type %> - -
  • - <% }); %> - <% } %> - - <% if (groupedResults.RED) { %> -
  • -

    - - - Failed <%=groupedResults.RED.length%> check<% if (groupedResults.RED.length != 1) {print('s')}%>. <% if (groupedResults.RED.length > 0) {print('Please correct these issues.')}%> -

    -
  • - - <% - _.each(groupedResults.RED, function(result) { - %> - -
  • - - - - - - <% - _.each(result.get("output"), function(output) { - %> - <% if (output.type && output.type.includes('image')) { %> - - - <% } else { %> -
    <%- output.value %>
    - <% } %> - <% - }); - %> -
    - - " - data-content="<%= result.get("check").description %>"> - - - - - <%= result.get("status") %> - <%= result.get("check").level %> - <%= result.get("check").type %> - -
  • - <% }); %> - <% } %> - - <% if (groupedResults.BLUE) { %> - - - <% - _.each(groupedResults.BLUE, function(result) { - %> -
  • - - - - - - <% - _.each(result.get("output"), function(output) { - %> - <% if (output.type && output.type.includes('image')) { %> - - - <% } else { %> -
    <%- output.value %>
    - <% } %> - <% - }); - %> -
    - - " - data-content="<%= result.get("check").description %>"> - - - - - <%= result.get("status") %> - <%= result.get("check").level %> - <%= result.get("check").type %> - -
  • - <% }); %> - <% } %> -
+ <% + var types = _.keys(groupedByType); + _.each(types, function(type) { + var results = groupedByType[type]; + var total = results.length; + var success = 0; + _.each(results, function(result) { + if (result.get("status") == "SUCCESS") { + success++; + } + }); + var width = (success/total) * 100; + width = width.toFixed(0); + %> + <%=type%>: <%=width%>% complete +
+
+
+ <% }); %> +
+ +
+ +
    + + + + + + + + + +
+
+ + <% } %>
- -
- -<% } %> -
-
+ \ No newline at end of file diff --git a/src/js/views/DataCatalogView.js b/src/js/views/DataCatalogView.js index 60bc4aabb..e2811e632 100644 --- a/src/js/views/DataCatalogView.js +++ b/src/js/views/DataCatalogView.js @@ -40,8 +40,8 @@ define([ /** * @class DataCatalogView * @classcategory Views - * @extends Backbone.View - * @constructor + * @augments Backbone.View + * @class * @deprecated * @description This view is deprecated and will eventually be removed in a future version (likely 3.0.0) */ @@ -55,7 +55,7 @@ define([ /** * If true, the view height will be adjusted to fit the height of the window * If false, the view height will be fixed via CSS - * @type {Boolean} + * @type {boolean} */ fixedHeight: false, @@ -156,13 +156,13 @@ define([ "mouseover .prevent-popover-runoff": "preventPopoverRunoff", }, - initialize: function (options) { - var view = this; + initialize(options) { + const view = this; // Get all the options and apply them to this view if (options) { - var optionKeys = Object.keys(options); - _.each(optionKeys, function (key, i) { + const optionKeys = Object.keys(options); + _.each(optionKeys, (key, i) => { view[key] = options[key]; }); } @@ -171,13 +171,13 @@ define([ // Render the main view and/or re-render subviews. Don't call .html() here // so we don't lose state, rather use .setElement(). Delegate rendering // and event handling to sub views - render: function () { + render() { // Use the global models if there are no other models specified at time of render if ( MetacatUI.appModel.get("searchHistory").length > 0 && (!this.searchModel || Object.keys(this.searchModel).length == 0) ) { - var lastSearchModels = _.last( + const lastSearchModels = _.last( MetacatUI.appModel.get("searchHistory"), ); @@ -243,12 +243,12 @@ define([ } // Populate the search template with some model attributes - var loadingHTML = this.loadingTemplate({ + const loadingHTML = this.loadingTemplate({ msg: "Retrieving member nodes...", }); - var templateVars = { - gmaps: gmaps, + const templateVars = { + gmaps, mode: MetacatUI.appModel.get("searchMode"), useMapBounds: this.searchModel.get("useGeohash"), username: MetacatUI.appUserModel.get("username"), @@ -263,16 +263,16 @@ define([ dataSourceTitle: MetacatUI.theme == "dataone" ? "Member Node" : "Data source", }; - var cel = this.template( + const cel = this.template( _.extend(this.searchModel.toJSON(), templateVars), ); this.$el.html(cel); - //Hide the filters that are disabled in the AppModel settings + // Hide the filters that are disabled in the AppModel settings _.each( this.$(".filter-contain[data-category]"), - function (filterEl) { + (filterEl) => { if ( !_.contains( MetacatUI.appModel.get("defaultSearchFilters"), @@ -296,18 +296,18 @@ define([ this.renderMap(); // Initialize the tooltips - var tooltips = $(".tooltip-this"); + const tooltips = $(".tooltip-this"); // Find the tooltips that are on filter labels - add a slight delay to those - var groupedTooltips = _.groupBy(tooltips, function (t) { - return ( + const groupedTooltips = _.groupBy( + tooltips, + (t) => ($(t).prop("tagName") == "LABEL" || $(t).parent().prop("tagName") == "LABEL") && - $(t).parents(".filter-container").length > 0 - ); - }); - var forFilterLabel = true, - forOtherElements = false; + $(t).parents(".filter-container").length > 0, + ); + const forFilterLabel = true; + const forOtherElements = false; $(groupedTooltips[forFilterLabel]).tooltip({ delay: { @@ -328,7 +328,7 @@ define([ this.toggleFilterCollapse(); // Iterate through each search model text attribute and show UI filter for each - var categories = [ + const categories = [ "all", "attribute", "creator", @@ -339,26 +339,26 @@ define([ "annotation", "isPrivate", ]; - var thisTerm = null; + let thisTerm = null; - for (var i = 0; i < categories.length; i++) { + for (let i = 0; i < categories.length; i++) { thisTerm = this.searchModel.get(categories[i]); if (thisTerm === undefined || thisTerm === null) break; - for (var x = 0; x < thisTerm.length; x++) { + for (let x = 0; x < thisTerm.length; x++) { this.showFilter(categories[i], thisTerm[x]); } } // List the Member Node filters - var view = this; + const view = this; _.each( _.contains( MetacatUI.appModel.get("defaultSearchFilters"), "dataSource", ), - function (source, i) { + (source, i) => { view.showFilter("dataSource", source); }, ); @@ -439,10 +439,10 @@ define([ }, // Linked Data Object for appending the jsonld into the browser DOM - getLinkedData: function () { + getLinkedData() { // Find the MN info from the CN Node list - var members = MetacatUI.nodeModel.get("members"); - for (var i = 0; i < members.length; i++) { + const members = MetacatUI.nodeModel.get("members"); + for (let i = 0; i < members.length; i++) { if ( members[i].identifier == MetacatUI.nodeModel.get("currentMemberNode") @@ -452,7 +452,7 @@ define([ } // JSON Linked Data Object - let elJSON = { + const elJSON = { "@context": { "@vocab": "http://schema.org/", }, @@ -461,7 +461,7 @@ define([ if (nodeModelObject) { // "keywords": "", // "provider": "", - let conditionalData = { + const conditionalData = { description: nodeModelObject.description, identifier: nodeModelObject.identifier, image: nodeModelObject.logo, @@ -474,22 +474,21 @@ define([ // Check if the jsonld already exists from the previous data view // If not create a new script tag and append otherwise replace the text for the script if (!document.getElementById("jsonld")) { - var el = document.createElement("script"); + const el = document.createElement("script"); el.type = "application/ld+json"; el.id = "jsonld"; el.text = JSON.stringify(elJSON); document.querySelector("head").appendChild(el); } else { - var script = document.getElementById("jsonld"); + const script = document.getElementById("jsonld"); script.text = JSON.stringify(elJSON); } - return; }, /* * Sets the height on elements in the main content area to fill up the entire area minus header and footer */ - setAutoHeight: function () { + setAutoHeight() { // If we are in list mode, don't determine the height of any elements because we are not "full screen" if ( MetacatUI.appModel.get("searchMode") == "list" || @@ -501,14 +500,14 @@ define([ // Get the heights of the header, navbar, and footer var otherHeight = 0; - $(".auto-height-member").each(function (i, el) { + $(".auto-height-member").each((i, el) => { if ($(el).css("display") != "none") { otherHeight += $(el).outerHeight(true); } }); // Get the remaining height left based on the window size - var remainingHeight = $(window).outerHeight(true) - otherHeight; + let remainingHeight = $(window).outerHeight(true) - otherHeight; if (remainingHeight < 0) remainingHeight = $(window).outerHeight(true) || 300; else if (remainingHeight <= 120) @@ -525,12 +524,12 @@ define([ var otherHeight = 0; $("#map-container.auto-height") .children() - .each(function (i, el) { + .each((i, el) => { if ($(el).attr("id") != "map-canvas") { otherHeight += $(el).outerHeight(true); } }); - var newMapHeight = remainingHeight - otherHeight; + const newMapHeight = remainingHeight - otherHeight; if (newMapHeight > 100) { $("#map-canvas").height(remainingHeight - otherHeight); } @@ -547,9 +546,9 @@ define([ * PERFORMING SEARCH * ================================================================================================== */ - triggerSearch: function () { + triggerSearch() { // Set the sort order - var sortOrder = $("#sortOrder").val(); + const sortOrder = $("#sortOrder").val(); if (sortOrder) { this.searchModel.set("sortOrder", sortOrder); } @@ -559,7 +558,7 @@ define([ if (!this.isSubView) { // make sure the browser knows where we are - var route = Backbone.history.fragment; + const route = Backbone.history.fragment; if (route.indexOf("data") < 0) { MetacatUI.uiRouter.navigate("data", { trigger: false, @@ -574,7 +573,7 @@ define([ return false; }, - triggerOnEnter: function (e) { + triggerOnEnter(e) { if (e.keyCode != 13) return; // Update the filters @@ -585,15 +584,15 @@ define([ * getResults gets all the current search filters from the searchModel, creates a Solr query, and runs that query. * @param {number} page - The page of search results to get results for */ - getResults: function (page) { + getResults(page) { // Set the sort order based on user choice - var sortOrder = this.searchModel.get("sortOrder"); + const sortOrder = this.searchModel.get("sortOrder"); if (sortOrder) { this.searchResults.setSort(sortOrder); } // Specify which fields to retrieve - var fields = ""; + let fields = ""; fields += "id,"; fields += "seriesId,"; fields += "title,"; @@ -624,13 +623,12 @@ define([ this.searchResults.setfields(fields); // Get the query - var query = this.searchModel.getQuery(); + const query = this.searchModel.getQuery(); // Specify which facets to retrieve if (gmaps && this.map) { // If we have Google Maps enabled - var geohashLevel = - "geohash_" + this.mapModel.determineGeohashLevel(this.map.zoom); + const geohashLevel = `geohash_${this.mapModel.determineGeohashLevel(this.map.zoom)}`; this.searchResults.facet.push(geohashLevel); } @@ -662,18 +660,18 @@ define([ * After the search results have been returned, * check if any of them are derived data or have derivations */ - checkForProv: function () { - var maps = [], - hasSources = [], - hasDerivations = [], - mainSearchResults = this.searchResults; + checkForProv() { + let maps = []; + let hasSources = []; + let hasDerivations = []; + const mainSearchResults = this.searchResults; // Get a list of all the resource map IDs from the SolrResults collection maps = this.searchResults.pluck("resourceMap"); maps = _.compact(_.flatten(maps)); // Create a new Search model with a search that finds all members of these packages/resource maps - var provSearchModel = new SearchModel({ + const provSearchModel = new SearchModel({ formatType: [ { value: "DATA", @@ -686,30 +684,25 @@ define([ }); // Create a new Solr Results model to store the results of this supplemental query - var provSearchResults = new SearchResults(null, { + const provSearchResults = new SearchResults(null, { query: provSearchModel.getQuery(), searchLogs: false, usePOST: true, rows: 150, - fields: provSearchModel.getProvFlList() + ",id,resourceMap", + fields: `${provSearchModel.getProvFlList()},id,resourceMap`, }); // Trigger a search on that Solr Results model - this.listenTo(provSearchResults, "reset", function (results) { + this.listenTo(provSearchResults, "reset", (results) => { if (results.models.length == 0) return; // See if any of the results have a value for a prov field - results.forEach(function (result) { + results.forEach((result) => { if (!result.getSources().length || !result.getDerivations()) return; - _.each(result.get("resourceMap"), function (rMapID) { + _.each(result.get("resourceMap"), (rMapID) => { if (_.contains(maps, rMapID)) { - var match = mainSearchResults.filter( - function (mainSearchResult) { - return _.contains( - mainSearchResult.get("resourceMap"), - rMapID, - ); - }, + const match = mainSearchResults.filter((mainSearchResult) => + _.contains(mainSearchResult.get("resourceMap"), rMapID), ); if (match && match.length && result.getSources().length > 0) hasSources.push(match[0].get("id")); @@ -725,16 +718,16 @@ define([ // If they do, find their corresponding result row here and add // the prov icon (or just change the class to active) - _.each(hasSources, function (metadataID) { - var metadataDoc = mainSearchResults.findWhere({ + _.each(hasSources, (metadataID) => { + const metadataDoc = mainSearchResults.findWhere({ id: metadataID, }); if (metadataDoc) { metadataDoc.set("prov_hasSources", true); } }); - _.each(hasDerivations, function (metadataID) { - var metadataDoc = mainSearchResults.findWhere({ + _.each(hasDerivations, (metadataID) => { + const metadataDoc = mainSearchResults.findWhere({ id: metadataID, }); if (metadataDoc) { @@ -745,7 +738,7 @@ define([ provSearchResults.toPage(0); }, - cacheSearch: function () { + cacheSearch() { MetacatUI.appModel.get("searchHistory").push({ search: this.searchModel.clone(), map: this.mapModel ? this.mapModel.clone() : null, @@ -758,15 +751,15 @@ define([ * FILTERS * ================================================================================================== */ - updateCheckboxFilter: function (e, category, value) { + updateCheckboxFilter(e, category, value) { if (!this.filters) return; - var checkbox = e.target; - var checked = $(checkbox).prop("checked"); + const checkbox = e.target; + const checked = $(checkbox).prop("checked"); - if (typeof category == "undefined") + if (typeof category === "undefined") var category = $(checkbox).attr("data-category"); - if (typeof value == "undefined") var value = $(checkbox).attr("value"); + if (typeof value === "undefined") var value = $(checkbox).attr("value"); // If the user just unchecked the box, then remove this filter if (!checked) { @@ -775,28 +768,28 @@ define([ } // If the user just checked the box, then add this filter else { - var currentValue = this.searchModel.get(category); + const currentValue = this.searchModel.get(category); // Get the description - var desc = + let desc = $(checkbox).attr("data-description") || $(checkbox).attr("title"); - if (typeof desc == "undefined" || !desc) desc = ""; + if (typeof desc === "undefined" || !desc) desc = ""; // Get the label - var labl = $(checkbox).attr("data-label"); - if (typeof labl == "undefined" || !labl) labl = ""; + let labl = $(checkbox).attr("data-label"); + if (typeof labl === "undefined" || !labl) labl = ""; // Make the filter object - var filter = { + const filter = { description: desc, label: labl, - value: value, + value, }; // If this filter category is an array, add this value to the array if (Array.isArray(currentValue)) { currentValue.push(filter); this.searchModel.set(category, currentValue); - this.searchModel.trigger("change:" + category); + this.searchModel.trigger(`change:${category}`); } else { // If it isn't an array, then just update the model with a simple value this.searchModel.set(category, filter); @@ -816,13 +809,13 @@ define([ this.triggerSearch(); }, - updateBooleanFilters: function (e) { + updateBooleanFilters(e) { if (!this.filters) return; // Get the category - var checkbox = e.target; - var category = $(checkbox).attr("data-category"); - var currentValue = this.searchModel.get(category); + const checkbox = e.target; + const category = $(checkbox).attr("data-category"); + const currentValue = this.searchModel.get(category); // If this filter is not enabled, exit this function if ( @@ -831,15 +824,16 @@ define([ return false; } - //The year filter is handled in a different way + // The year filter is handled in a different way if (category == "pubYear" || category == "dataYear") return; // If the checkbox has a value, then update as a string value not boolean - var value = $(checkbox).attr("value"); + let value = $(checkbox).attr("value"); if (value) { this.updateCheckboxFilter(e, category, value); return; - } else value = $(checkbox).prop("checked"); + } + value = $(checkbox).prop("checked"); this.searchModel.set(category, value); @@ -862,42 +856,42 @@ define([ this.triggerSearch(); // Track this event - MetacatUI.analytics?.trackEvent("search", "filter, " + category, value); + MetacatUI.analytics?.trackEvent("search", `filter, ${category}`, value); }, // Update the UI year slider and input values // Also update the model - updateYearRange: function (e) { + updateYearRange(e) { if (!this.filters) return; - var viewRef = this, - userAction = !(typeof e === "undefined"), - model = this.searchModel, - pubYearChecked = $("#publish_year").prop("checked"), - dataYearChecked = $("#data_year").prop("checked"); + const viewRef = this; + const userAction = !(typeof e === "undefined"); + const model = this.searchModel; + const pubYearChecked = $("#publish_year").prop("checked"); + const dataYearChecked = $("#data_year").prop("checked"); // If the year range slider has not been created yet if (!userAction && !$("#year-range").hasClass("ui-slider")) { var defaultMin = - typeof this.searchModel.defaults == "function" - ? this.searchModel.defaults().yearMin - : 1800, - defaultMax = - typeof this.searchModel.defaults == "function" - ? this.searchModel.defaults().yearMax - : new Date().getUTCFullYear(); - - //jQueryUI slider + typeof this.searchModel.defaults === "function" + ? this.searchModel.defaults().yearMin + : 1800; + var defaultMax = + typeof this.searchModel.defaults === "function" + ? this.searchModel.defaults().yearMax + : new Date().getUTCFullYear(); + + // jQueryUI slider $("#year-range").slider({ range: true, disabled: false, - min: defaultMin, //sets the minimum on the UI slider on initialization - max: defaultMax, //sets the maximum on the UI slider on initialization + min: defaultMin, // sets the minimum on the UI slider on initialization + max: defaultMax, // sets the maximum on the UI slider on initialization values: [ this.searchModel.get("yearMin"), this.searchModel.get("yearMax"), - ], //where the left and right slider handles are - stop: function (event, ui) { + ], // where the left and right slider handles are + stop(event, ui) { // When the slider is changed, update the input values $("#min_year").val(ui.values[0]); $("#max_year").val(ui.values[1]); @@ -924,7 +918,7 @@ define([ $("#publish_year").attr("data-category"), true, false, - ui.values[0] + " to " + ui.values[1], + `${ui.values[0]} to ${ui.values[1]}`, { replace: true, }, @@ -935,7 +929,7 @@ define([ $("#data_year").attr("data-category"), true, false, - ui.values[0] + " to " + ui.values[1], + `${ui.values[0]} to ${ui.values[1]}`, { replace: true, }, @@ -962,7 +956,7 @@ define([ }); return; } - var year = new Date( + const year = new Date( this.statsModel.get("firstBeginDate"), ).getUTCFullYear(); if (typeof year !== "undefined") { @@ -984,7 +978,7 @@ define([ "pubYear", true, false, - $("#min_year").val() + " to " + $("#max_year").val(), + `${$("#min_year").val()} to ${$("#max_year").val()}`, { replace: true, }, @@ -995,7 +989,7 @@ define([ "dataYear", true, false, - $("#min_year").val() + " to " + $("#max_year").val(), + `${$("#min_year").val()} to ${$("#max_year").val()}`, { replace: true, }, @@ -1014,7 +1008,7 @@ define([ }); return; } - var year = new Date( + const year = new Date( this.statsModel.get("lastEndDate"), ).getUTCFullYear(); if (typeof year !== "undefined") { @@ -1036,7 +1030,7 @@ define([ "pubYear", true, false, - $("#min_year").val() + " to " + $("#max_year").val(), + `${$("#min_year").val()} to ${$("#max_year").val()}`, { replace: true, }, @@ -1047,7 +1041,7 @@ define([ "dataYear", true, false, - $("#min_year").val() + " to " + $("#max_year").val(), + `${$("#min_year").val()} to ${$("#max_year").val()}`, { replace: true, }, @@ -1099,8 +1093,8 @@ define([ } // If either of the year inputs have changed or if just one of the year types were unchecked else { - var minVal = $("#min_year").val(); - var maxVal = $("#max_year").val(); + const minVal = $("#min_year").val(); + const maxVal = $("#max_year").val(); // Update the search model to match what is in the text inputs this.searchModel.set("yearMin", minVal); @@ -1121,7 +1115,7 @@ define([ $("#data_year").attr("data-category"), true, true, - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, { replace: true, }, @@ -1131,7 +1125,7 @@ define([ MetacatUI.analytics?.trackEvent( "search", "filter, Data Year", - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, ); } else { // Add the filter elements @@ -1140,7 +1134,7 @@ define([ $("#publish_year").attr("data-category"), true, true, - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, { replace: true, }, @@ -1150,7 +1144,7 @@ define([ MetacatUI.analytics?.trackEvent( "search", "filter, Publication Year", - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, ); } else { this.hideFilter($("#publish_year").attr("data-category"), true); @@ -1161,7 +1155,7 @@ define([ $("#data_year").attr("data-category"), true, true, - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, { replace: true, }, @@ -1171,7 +1165,7 @@ define([ MetacatUI.analytics?.trackEvent( "search", "filter, Data Year", - minVal + " to " + maxVal, + `${minVal} to ${maxVal}`, ); } else { this.hideFilter($("#data_year").attr("data-category"), true); @@ -1187,15 +1181,15 @@ define([ } }, - updateTextFilters: function (e, item) { + updateTextFilters(e, item) { if (!this.filters) return; // Get the search/filter category - var category = $(e.target).attr("data-category"); + let category = $(e.target).attr("data-category"); // Try the parent elements if not found if (!category) { - var parents = $(e.target) + const parents = $(e.target) .parents() .each(function () { category = $(this).attr("data-category"); @@ -1210,12 +1204,12 @@ define([ } // Get the input element - var input = this.$el.find("#" + category + "_input"); + const input = this.$el.find(`#${category}_input`); // Get the value of the associated input - var term = !item || !item.value ? input.val() : item.value; - var label = !item || !item.filterLabel ? null : item.filterLabel; - var filterDesc = !item || !item.desc ? null : item.desc; + const term = !item || !item.value ? input.val() : item.value; + const label = !item || !item.filterLabel ? null : item.filterLabel; + const filterDesc = !item || !item.desc ? null : item.desc; // Check that something was actually entered if (term == "" || term == " ") { @@ -1231,20 +1225,18 @@ define([ } // Get the current searchModel array for this category - var filtersArray = _.clone(this.searchModel.get(category)); + const filtersArray = _.clone(this.searchModel.get(category)); - if (typeof filtersArray == "undefined") { + if (typeof filtersArray === "undefined") { console.error( - "The filter category '" + - category + - "' does not exist in the Search model. Not sending this search term.", + `The filter category '${category}' does not exist in the Search model. Not sending this search term.`, ); return false; } // Check if this entry is a duplicate - var duplicate = (function () { - for (var i = 0; i < filtersArray.length; i++) { + const duplicate = (function () { + for (let i = 0; i < filtersArray.length; i++) { if (filtersArray[i].value === term) { return true; } @@ -1253,14 +1245,14 @@ define([ if (duplicate) { // Display a quick message - if ($("#duplicate-" + category + "-alert").length <= 0) { - $("#current-" + category + "-filters").prepend( + if ($(`#duplicate-${category}-alert`).length <= 0) { + $(`#current-${category}-filters`).prepend( "
" + "You are already using that filter" + "
", ); - $("#duplicate-" + category + "-alert") + $(`#duplicate-${category}-alert`) .delay(2000) .fadeOut(500, function () { this.remove(); @@ -1271,10 +1263,10 @@ define([ } // Add the new entry to the array of current filters - var filter = { + const filter = { value: term, filterLabel: label, - label: label, + label, description: filterDesc, }; filtersArray.push(filter); @@ -1295,19 +1287,19 @@ define([ this.triggerSearch(); // Track this event - MetacatUI.analytics?.trackEvent("search", "filter, " + category, term); + MetacatUI.analytics?.trackEvent("search", `filter, ${category}`, term); }, // Removes a specific filter term from the searchModel - removeFilter: function (e) { + removeFilter(e) { // Get the parent element that stores the filter term - var filterNode = $(e.target).parent(); + const filterNode = $(e.target).parent(); // Find this filter's category and value - var category = - filterNode.attr("data-category") || - filterNode.parent().attr("data-category"), - value = $(filterNode).attr("data-term"); + const category = + filterNode.attr("data-category") || + filterNode.parent().attr("data-category"); + const value = $(filterNode).attr("data-term"); // Remove this filter from the searchModel this.searchModel.removeFromModel(category, value); @@ -1316,25 +1308,26 @@ define([ this.hideFilter(category, value); // If there is an associated checkbox with this filter, uncheck it - var assocCheckbox, - checkboxes = this.$( - "input[type='checkbox'][data-category='" + category + "']", - ); + let assocCheckbox; + const checkboxes = this.$( + `input[type='checkbox'][data-category='${category}']`, + ); - //If there are more than one checkboxes in this category, match by value + // If there are more than one checkboxes in this category, match by value if (checkboxes.length > 1) { - assocCheckbox = _.find(checkboxes, function (checkbox) { - return $(checkbox).val() == value; - }); + assocCheckbox = _.find( + checkboxes, + (checkbox) => $(checkbox).val() == value, + ); } - //If there is only one checkbox in this category, default to it + // If there is only one checkbox in this category, default to it else if (checkboxes.length == 1) { assocCheckbox = checkboxes[0]; } - //If there is an associated checkbox, uncheck it + // If there is an associated checkbox, uncheck it if (assocCheckbox) { - //Uncheck it + // Uncheck it $(assocCheckbox).prop("checked", false); } @@ -1346,8 +1339,8 @@ define([ }, // Clear all the currently applied filters - resetFilters: function () { - var viewRef = this; + resetFilters() { + const viewRef = this; this.allowSearch = true; @@ -1362,7 +1355,7 @@ define([ // Then reset the model this.searchModel.clear(); - //Reset the map model + // Reset the map model if (this.mapModel) { this.mapModel.clear(); } @@ -1372,7 +1365,7 @@ define([ this.searchModel.get("yearMin"), this.searchModel.get("yearMax"), ]); - //and the year inputs + // and the year inputs $("#min_year").val(this.searchModel.get("yearMin")); $("#max_year").val(this.searchModel.get("yearMax")); @@ -1397,32 +1390,30 @@ define([ this.triggerSearch(); }, - hideEl: function (element) { + hideEl(element) { // Fade out and remove the element - $(element).fadeOut("slow", function () { + $(element).fadeOut("slow", () => { $(element).remove(); }); }, // Removes a specified filter node from the DOM - hideFilter: function (category, value) { + hideFilter(category, value) { if (!this.filters) return; if (typeof value === "undefined") { var filterNode = this.$( - ".current-filters[data-category='" + category + "']", + `.current-filters[data-category='${category}']`, ).children(".current-filter"); } else { var filterNode = this.$( - ".current-filters[data-category='" + category + "']", - ).children("[data-term='" + value + "']"); + `.current-filters[data-category='${category}']`, + ).children(`[data-term='${value}']`); } // Try finding it a different way if (!filterNode || !filterNode.length) { - filterNode = this.$( - ".current-filter[data-category='" + category + "']", - ); + filterNode = this.$(`.current-filter[data-category='${category}']`); } // Remove the filter node from the DOM @@ -1430,29 +1421,21 @@ define([ }, // Adds a specified filter node to the DOM - showFilter: function ( - category, - term, - checkForDuplicates, - label, - options, - ) { + showFilter(category, term, checkForDuplicates, label, options) { if (!this.filters) return; - var viewRef = this; + const viewRef = this; if (typeof term === "undefined") return false; // Get the element to add the UI filter node to // The pattern is #current--filters - var filterContainer = this.$el.find( - "#current-" + category + "-filters", - ); + const filterContainer = this.$el.find(`#current-${category}-filters`); // Allow the option to only display this exact filter category and term once to the DOM // Helpful when adding a filter that is not stored in the search model (for display only) if (checkForDuplicates) { - var duplicate = false; + let duplicate = false; // Get the current terms from the DOM and check against the new term filterContainer.children().each(function () { @@ -1467,8 +1450,8 @@ define([ } } - var value = null, - desc = null; + let value = null; + let desc = null; // See if this filter is an object and extract the filter attributes if (typeof term === "object") { @@ -1499,7 +1482,7 @@ define([ desc = label; } - var categoryLabel = this.searchModel.fieldLabels[category]; + let categoryLabel = this.searchModel.fieldLabels[category]; if ( typeof categoryLabel === "undefined" && category == "additionalCriteria" @@ -1508,7 +1491,7 @@ define([ if (typeof categoryLabel === "undefined") categoryLabel = category; // Add a filter node to the DOM - var filterEl = viewRef.currentFilterTemplate({ + const filterEl = viewRef.currentFilterTemplate({ category: Utilities.encodeHTML(categoryLabel), value: Utilities.encodeHTML(value), label: Utilities.encodeHTML(label), @@ -1517,7 +1500,7 @@ define([ // Add the filter to the page - either replace or tack on if (options && options.replace) { - var currentFilter = filterContainer.find(".current-filter"); + const currentFilter = filterContainer.find(".current-filter"); if (currentFilter.length > 0) { currentFilter.replaceWith(filterEl); } else { @@ -1533,53 +1516,46 @@ define([ show: 800, }, }); - - return; }, /* * Get the member node list from the model and list the members in the filter list */ - listDataSources: function () { + listDataSources() { if (!this.filters) return; if (MetacatUI.nodeModel.get("members").length < 1) return; // Get the member nodes - var members = _.sortBy( - MetacatUI.nodeModel.get("members"), - function (m) { - if (m.name) { - return m.name.toLowerCase(); - } else { - return ""; - } - }, - ); - var filteredMembers = _.reject(members, function (m) { - return m.status != "operational"; + const members = _.sortBy(MetacatUI.nodeModel.get("members"), (m) => { + if (m.name) { + return m.name.toLowerCase(); + } + return ""; }); + const filteredMembers = _.reject( + members, + (m) => m.status != "operational", + ); // Get the current search filters for data source - var currentFilters = this.searchModel.get("dataSource"); + const currentFilters = this.searchModel.get("dataSource"); // Create an HTML list - var listMax = 4, - numHidden = filteredMembers.length - listMax, - list = $(document.createElement("ul")).addClass("checkbox-list"); + const listMax = 4; + const numHidden = filteredMembers.length - listMax; + const list = $(document.createElement("ul")).addClass("checkbox-list"); // Add a checkbox and label for each member node in the node model - _.each(filteredMembers, function (member, i) { - var listItem = document.createElement("li"), - input = document.createElement("input"), - label = document.createElement("label"); + _.each(filteredMembers, (member, i) => { + const listItem = document.createElement("li"); + const input = document.createElement("input"); + const label = document.createElement("label"); // If this member node is already a data source filter, then the checkbox is checked - var checked = _.findWhere(currentFilters, { + const checked = !!_.findWhere(currentFilters, { value: member.identifier, - }) - ? true - : false; + }); // Create a textual label for this data source $(label) @@ -1619,9 +1595,9 @@ define([ // Insert a "More" link after a certain amount to enable users to expand the list if (i == listMax) { - var moreLink = document.createElement("a"); + const moreLink = document.createElement("a"); $(moreLink) - .html("Show " + numHidden + " more") + .html(`Show ${numHidden} more`) .addClass("more-link pointer toggle-list") .append( $(document.createElement("i")).addClass("icon icon-expand-alt"), @@ -1635,7 +1611,7 @@ define([ }); if (numHidden > 0) { - var lessLink = document.createElement("a"); + const lessLink = document.createElement("a"); $(lessLink) .html("Collapse member nodes") .addClass("less-link toggle-list pointer hidden") @@ -1647,28 +1623,30 @@ define([ } // Add the list of checkboxes to the placeholder - var container = $(".member-nodes-placeholder"); + const container = $(".member-nodes-placeholder"); $(container).html(list); $(".tooltip-this").tooltip(); }, - resetDataSourceList: function () { + resetDataSourceList() { if (!this.filters) return; // Reset the Member Nodes checkboxes - var mnFilterContainer = $("#member-nodes-container"), - defaultMNs = this.searchModel.get("dataSource"); + const mnFilterContainer = $("#member-nodes-container"); + const defaultMNs = this.searchModel.get("dataSource"); // Make sure the member node filter exists if (!mnFilterContainer || mnFilterContainer.length == 0) return false; if (typeof defaultMNs === "undefined" || !defaultMNs) return false; // Reset each member node checkbox - var boxes = $(mnFilterContainer).find(".filter").prop("checked", false); + const boxes = $(mnFilterContainer) + .find(".filter") + .prop("checked", false); // Check the member node checkboxes that are defaults in the search model - _.each(defaultMNs, function (member, i) { - var value = null; + _.each(defaultMNs, (member, i) => { + let value = null; // Allow for string search model filter values and object filter values if (typeof member !== "object" && member) value = member; @@ -1677,20 +1655,20 @@ define([ else value = member.value; $(mnFilterContainer) - .find("checkbox[value='" + value + "']") + .find(`checkbox[value='${value}']`) .prop("checked", true); }); return true; }, - toggleList: function (e) { + toggleList(e) { if (!this.filters) return; - var link = e.target, - controls = $(link).parents("ul").find(".toggle-list"), - list = $(link).parents("ul"), - isHidden = !list.find(".more-link").is(".hidden"); + const link = e.target; + const controls = $(link).parents("ul").find(".toggle-list"); + const list = $(link).parents("ul"); + const isHidden = !list.find(".more-link").is(".hidden"); // Hide/Show the list if (isHidden) { @@ -1704,9 +1682,9 @@ define([ }, // add additional criteria to the search model based on link click - additionalCriteria: function (e) { + additionalCriteria(e) { // Get the clicked node - var targetNode = $(e.target); + const targetNode = $(e.target); // If this additional criteria is already applied, remove it if (targetNode.hasClass("active")) { @@ -1715,10 +1693,10 @@ define([ } // Get the filter criteria - var term = targetNode.attr("data-term"); + const term = targetNode.attr("data-term"); // Find this element's category in the data-category attribute - var category = targetNode.attr("data-category"); + const category = targetNode.attr("data-category"); // style the selection $(".keyword-search-link").removeClass("active"); @@ -1736,26 +1714,26 @@ define([ return false; }, - removeAdditionalCriteria: function (e) { + removeAdditionalCriteria(e) { // Get the clicked node - var targetNode = $(e.target); + const targetNode = $(e.target); // Reference to model - var model = this.searchModel; + const model = this.searchModel; // remove the styling $(".keyword-search-link").removeClass("active"); $(".keyword-search-link").parent().removeClass("active"); // Get the term - var term = targetNode.attr("data-term"); + const term = targetNode.attr("data-term"); // Get the current search model additional criteria - var current = this.searchModel.get("additionalCriteria"); + const current = this.searchModel.get("additionalCriteria"); // If this term is in the current search model (should be)... if (_.contains(current, term)) { - //then remove it - var newTerms = _.without(current, term); + // then remove it + const newTerms = _.without(current, term); model.set("additionalCriteria", newTerms); } @@ -1767,24 +1745,21 @@ define([ }, // Get the facet counts - getAutocompletes: function (e) { + getAutocompletes(e) { if (!e) return; // Get the text input to determine the filter type - var input = $(e.target), - category = input.attr("data-category"); + const input = $(e.target); + const category = input.attr("data-category"); if (!this.filters || !category) return; - var viewRef = this; + const viewRef = this; // Create the facet query by using our current search query - var facetQuery = - "q=" + - this.searchResults.currentquery + - "&rows=0" + - this.searchModel.getFacetQuery(category) + - "&wt=json&"; + const facetQuery = `q=${ + this.searchResults.currentquery + }&rows=0${this.searchModel.getFacetQuery(category)}&wt=json&`; // If we've cached these filter results, then use the cache instead of sending a new request if (!MetacatUI.appSearchModel.autocompleteCache) @@ -1798,25 +1773,25 @@ define([ } // Get the facet counts for the autocomplete - var requestSettings = { + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + facetQuery, type: "GET", dataType: "json", - success: function (data, textStatus, xhr) { - var suggestions = [], - facetLimit = 999; + success(data, textStatus, xhr) { + let suggestions = []; + const facetLimit = 999; // Get all the facet counts - _.each(category.split(","), function (c) { - if (typeof c == "string") c = [c]; - _.each(c, function (thisCategory) { + _.each(category.split(","), (c) => { + if (typeof c === "string") c = [c]; + _.each(c, (thisCategory) => { // Get the field name(s) - var fieldNames = + let fieldNames = MetacatUI.appSearchModel.facetNameMap[thisCategory]; - if (typeof fieldNames == "string") fieldNames = [fieldNames]; + if (typeof fieldNames === "string") fieldNames = [fieldNames]; // Get the facet counts - _.each(fieldNames, function (fieldName) { + _.each(fieldNames, (fieldName) => { suggestions.push(data.facet_counts.facet_fields[fieldName]); }); }); @@ -1824,24 +1799,24 @@ define([ suggestions = _.flatten(suggestions); // Format the suggestions - var rankedSuggestions = new Array(); + const rankedSuggestions = new Array(); for ( - var i = 0; + let i = 0; i < Math.min(suggestions.length - 1, facetLimit); i += 2 ) { - //The label is the item value - var label = suggestions[i]; + // The label is the item value + let label = suggestions[i]; - //For all categories except the 'all' category, display the facet count + // For all categories except the 'all' category, display the facet count if (category != "all") { - label += " (" + suggestions[i + 1] + ")"; + label += ` (${suggestions[i + 1]})`; } - //Push the autocomplete item to the array + // Push the autocomplete item to the array rankedSuggestions.push({ value: suggestions[i], - label: label, + label, }); } @@ -1861,14 +1836,14 @@ define([ ); }, - setupAutocomplete: function (input, rankedSuggestions) { - var viewRef = this; + setupAutocomplete(input, rankedSuggestions) { + const viewRef = this; - //Override the _renderItem() function which renders a single autocomplete item. + // Override the _renderItem() function which renders a single autocomplete item. // We want to use the 'title' HTML attribute on each item. // This method must create a new
  • element, append it to the menu, and return it. $.widget("custom.autocomplete", $.ui.autocomplete, { - _renderItem: function (ul, item) { + _renderItem(ul, item) { return $(document.createElement("li")) .attr("title", item.label) .append(item.label) @@ -1876,25 +1851,23 @@ define([ }, }); input.autocomplete({ - source: function (request, response) { - var term = $.ui.autocomplete.escapeRegex(request.term), - startsWithMatcher = new RegExp("^" + term, "i"), - startsWith = $.grep(rankedSuggestions, function (value) { - return startsWithMatcher.test( - value.label || value.value || value, - ); - }), - containsMatcher = new RegExp(term, "i"), - contains = $.grep(rankedSuggestions, function (value) { - return ( - $.inArray(value, startsWith) < 0 && - containsMatcher.test(value.label || value.value || value) - ); - }); + source(request, response) { + const term = $.ui.autocomplete.escapeRegex(request.term); + const startsWithMatcher = new RegExp(`^${term}`, "i"); + const startsWith = $.grep(rankedSuggestions, (value) => + startsWithMatcher.test(value.label || value.value || value), + ); + const containsMatcher = new RegExp(term, "i"); + const contains = $.grep( + rankedSuggestions, + (value) => + $.inArray(value, startsWith) < 0 && + containsMatcher.test(value.label || value.value || value), + ); response(startsWith.concat(contains)); }, - select: function (event, ui) { + select(event, ui) { // set the text field input.val(ui.item.value); // add to the filter immediately @@ -1910,7 +1883,7 @@ define([ }); }, - hideClearButton: function () { + hideClearButton() { if (!this.filters) return; // Hide the current filters panel @@ -1921,7 +1894,7 @@ define([ this.setAutoHeight(); }, - showClearButton: function () { + showClearButton() { if (!this.filters) return; // Show the current filters panel @@ -1945,7 +1918,7 @@ define([ * ================================================================================================== */ // Update all the statistics throughout the page - updateStats: function () { + updateStats() { if (this.searchResults.header != null) { this.$statcounts = this.$("#statcounts"); this.$statcounts.html( @@ -1963,9 +1936,9 @@ define([ this.updatePager(); }, - updatePager: function () { + updatePager() { if (this.searchResults.header != null) { - var pageCount = Math.ceil( + const pageCount = Math.ceil( this.searchResults.header.get("numFound") / this.searchResults.header.get("rows"), ); @@ -1984,10 +1957,10 @@ define([ this.$("#resultspager").html(""); this.$(".resultspager").html(""); } else { - var pages = new Array(pageCount); + const pages = new Array(pageCount); // mark current page correctly, avoid NaN - var currentPage = -1; + let currentPage = -1; try { currentPage = Math.floor( (this.searchResults.header.get("start") / @@ -1995,39 +1968,39 @@ define([ pageCount, ); } catch (ex) { - console.log("Exception when calculating pages:" + ex.message); + console.log(`Exception when calculating pages:${ex.message}`); } // Populate the pagination element in the UI this.$(".resultspager").html( this.pagerTemplate({ - pages: pages, - currentPage: currentPage, + pages, + currentPage, }), ); this.$("#resultspager").html( this.pagerTemplate({ - pages: pages, - currentPage: currentPage, + pages, + currentPage, }), ); } } }, - updatePageNumber: function (page) { + updatePageNumber(page) { MetacatUI.appModel.set("page", page); if (!this.isSubView) { - var route = Backbone.history.fragment, - subroutePos = route.indexOf("/page/"), - newPage = parseInt(page) + 1; + let route = Backbone.history.fragment; + const subroutePos = route.indexOf("/page/"); + const newPage = parseInt(page) + 1; - //replace the last number with the new one + // replace the last number with the new one if (page > 0 && subroutePos > -1) { route = route.replace(/\d+$/, newPage); } else if (page > 0) { - route += "/page/" + newPage; + route += `/page/${newPage}`; } else if (subroutePos >= 0) { route = route.substring(0, subroutePos); } @@ -2037,35 +2010,35 @@ define([ }, // Next page of results - nextpage: function () { + nextpage() { this.loading(); this.searchResults.nextpage(); this.$resultsview.show(); this.updateStats(); - var page = MetacatUI.appModel.get("page"); + let page = MetacatUI.appModel.get("page"); page++; this.updatePageNumber(page); }, // Previous page of results - prevpage: function () { + prevpage() { this.loading(); this.searchResults.prevpage(); this.$resultsview.show(); this.updateStats(); - var page = MetacatUI.appModel.get("page"); + let page = MetacatUI.appModel.get("page"); page--; this.updatePageNumber(page); }, - navigateToPage: function (event) { - var page = $(event.target).attr("page"); + navigateToPage(event) { + const page = $(event.target).attr("page"); this.showPage(page); }, - showPage: function (page) { + showPage(page) { this.loading(); this.searchResults.toPage(page); this.$resultsview.show(); @@ -2079,7 +2052,7 @@ define([ * THE MAP * ================================================================================================== */ - renderMap: function () { + renderMap() { // If gmaps isn't enabled or loaded with an error, use list mode if (!gmaps || this.mode == "list") { this.ready = true; @@ -2095,8 +2068,8 @@ define([ // Get the map options and create the map gmaps.visualRefresh = true; - var mapOptions = this.mapModel.get("mapOptions"); - var defaultZoom = mapOptions.zoom; + const mapOptions = this.mapModel.get("mapOptions"); + const defaultZoom = mapOptions.zoom; $("#map-container").append("
    "); this.map = new gmaps.Map($("#map-canvas")[0], mapOptions); this.mapModel.set("map", this.map); @@ -2107,61 +2080,61 @@ define([ this.$(this.mapFilterToggle).hide(); // Store references - var mapRef = this.map; - var viewRef = this; + const mapRef = this.map; + const viewRef = this; - google.maps.event.addListener(mapRef, "zoom_changed", function () { + google.maps.event.addListener(mapRef, "zoom_changed", () => { // If the map is zoomed in further than the default zoom level, // than we want to mark the map as zoomed in if (viewRef.map.getZoom() > defaultZoom) { viewRef.hasZoomed = true; } - //If we are at the default zoom level or higher, than do not mark the map + // If we are at the default zoom level or higher, than do not mark the map // as zoomed in else { viewRef.hasZoomed = false; } }); - google.maps.event.addListener(mapRef, "dragend", function () { + google.maps.event.addListener(mapRef, "dragend", () => { viewRef.hasDragged = true; }); - google.maps.event.addListener(mapRef, "idle", function () { + google.maps.event.addListener(mapRef, "idle", () => { // Remove all markers from the map - for (var i = 0; i < viewRef.resultMarkers.length; i++) { + for (let i = 0; i < viewRef.resultMarkers.length; i++) { viewRef.resultMarkers[i].setMap(null); } viewRef.resultMarkers = new Array(); - //Check if the user has interacted with the map just now, and if so, we + // Check if the user has interacted with the map just now, and if so, we // want to alter the geohash filter (changing the geohash values or resetting it completely) - var alterGeohashFilter = + const alterGeohashFilter = viewRef.allowSearch || viewRef.hasZoomed || viewRef.hasDragged; if (!alterGeohashFilter) { return; } - //Determine if the map needs to be recentered. The map only needs to be + // Determine if the map needs to be recentered. The map only needs to be // recentered if it is not at the default lat,long center point AND it // is not zoomed in or dragged to a new center point - var setGeohashFilter = + const setGeohashFilter = viewRef.hasZoomed && viewRef.isMapFilterEnabled(); - //If we are using the geohash filter defined by this map, then + // If we are using the geohash filter defined by this map, then // apply the filter and trigger a new search if (setGeohashFilter) { viewRef.$(viewRef.mapFilterToggle).show(); // Get the Google map bounding box - var boundingBox = mapRef.getBounds(); + const boundingBox = mapRef.getBounds(); // Set the search model spatial filters // Encode the Google Map bounding box into geohash - var north = boundingBox.getNorthEast().lat(), - west = boundingBox.getSouthWest().lng(), - south = boundingBox.getSouthWest().lat(), - east = boundingBox.getNorthEast().lng(); + const north = boundingBox.getNorthEast().lat(); + const west = boundingBox.getSouthWest().lng(); + const south = boundingBox.getSouthWest().lat(); + const east = boundingBox.getNorthEast().lng(); viewRef.searchModel.set("north", north); viewRef.searchModel.set("west", west); @@ -2173,12 +2146,12 @@ define([ viewRef.mapModel.get("mapOptions").zoom = mapRef.getZoom(); // Determine the precision of geohashes to search for - var zoom = mapRef.getZoom(); + const zoom = mapRef.getZoom(); - var precision = viewRef.mapModel.getSearchPrecision(zoom); + const precision = viewRef.mapModel.getSearchPrecision(zoom); // Get all the geohash tiles contained in the map bounds - var geohashBBoxes = nGeohash.bboxes( + const geohashBBoxes = nGeohash.bboxes( south, west, north, @@ -2190,10 +2163,10 @@ define([ viewRef.searchModel.set("geohashes", geohashBBoxes); viewRef.searchModel.set("geohashLevel", precision); - //Start back at page 0 + // Start back at page 0 MetacatUI.appModel.set("page", 0); - //Mark the view as ready to start a search + // Mark the view as ready to start a search viewRef.ready = true; // Trigger a new search @@ -2201,37 +2174,35 @@ define([ viewRef.allowSearch = false; } else { - //Reset the map filter + // Reset the map filter viewRef.resetMap(); - //Start back at page 0 + // Start back at page 0 MetacatUI.appModel.set("page", 0); - //Mark the view as ready to start a search + // Mark the view as ready to start a search viewRef.ready = true; // Trigger a new search viewRef.triggerSearch(); viewRef.allowSearch = false; - - return; } }); }, // Resets the model and view settings related to the map - resetMap: function () { + resetMap() { if (!gmaps) { return; } // First reset the model // The categories pertaining to the map - var categories = ["east", "west", "north", "south"]; + const categories = ["east", "west", "north", "south"]; // Loop through each and remove the filters from the model - for (var i = 0; i < categories.length; i++) { + for (let i = 0; i < categories.length; i++) { this.searchModel.set(categories[i], null); } @@ -2242,18 +2213,18 @@ define([ this.allowSearch = false; }, - isMapFilterEnabled: function () { - var toggleInput = this.$("input" + this.mapFilterToggle); + isMapFilterEnabled() { + const toggleInput = this.$(`input${this.mapFilterToggle}`); if (typeof toggleInput === "undefined" || !toggleInput) return; return $(toggleInput).prop("checked"); }, - toggleMapFilter: function (e, a) { - var toggleInput = this.$("input" + this.mapFilterToggle); + toggleMapFilter(e, a) { + const toggleInput = this.$(`input${this.mapFilterToggle}`); if (typeof toggleInput === "undefined" || !toggleInput) return; - var isOn = $(toggleInput).prop("checked"); + let isOn = $(toggleInput).prop("checked"); // If the user clicked on the label, then change the checkbox for them if (e.target.tagName != "INPUT") { @@ -2268,25 +2239,25 @@ define([ }, /** - * Show the marker, infoWindow, and bounding coordinates polygon on + * Show the marker, infoWindow, and bounding coordinates polygon on the map when the user hovers on the marker icon in the result list - * @param {Event} e - */ - showResultOnMap: function (e) { + * @param {Event} e + */ + showResultOnMap(e) { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Get the attributes about this dataset - var resultRow = e.target, - id = $(resultRow).attr("data-id"); + let resultRow = e.target; + let id = $(resultRow).attr("data-id"); // The mouseover event might be triggered by a nested element, so loop through the parents to find the id - if (typeof id == "undefined") { + if (typeof id === "undefined") { $(resultRow) .parents() .each(function () { - if (typeof $(this).attr("data-id") != "undefined") { + if (typeof $(this).attr("data-id") !== "undefined") { id = $(this).attr("data-id"); resultRow = this; } @@ -2294,24 +2265,25 @@ define([ } // Find the tile for this data set and highlight it on the map - var resultGeohashes = this.searchResults + const resultGeohashes = this.searchResults .findWhere({ - id: id, + id, }) .get("geohash_9"); - for (var i = 0; i < resultGeohashes.length; i++) { - var thisGeohash = resultGeohashes[i], - latLong = nGeohash.decode(thisGeohash), - position = new google.maps.LatLng( - latLong.latitude, - latLong.longitude, - ), - containingTileGeohash = _.find(this.tileGeohashes, function (g) { - return thisGeohash.indexOf(g) == 0; - }), - containingTile = _.findWhere(this.tiles, { - geohash: containingTileGeohash, - }); + for (let i = 0; i < resultGeohashes.length; i++) { + var thisGeohash = resultGeohashes[i]; + const latLong = nGeohash.decode(thisGeohash); + const position = new google.maps.LatLng( + latLong.latitude, + latLong.longitude, + ); + const containingTileGeohash = _.find( + this.tileGeohashes, + (g) => thisGeohash.indexOf(g) == 0, + ); + const containingTile = _.findWhere(this.tiles, { + geohash: containingTileGeohash, + }); // If this is a geohash for a georegion outside the map, do not highlight a tile or display a marker if (typeof containingTile === "undefined") continue; @@ -2319,40 +2291,40 @@ define([ this.highlightTile(containingTile); // Set up the options for each marker - var markerOptions = { - position: position, + const markerOptions = { + position, icon: this.mapModel.get("markerImage"), zIndex: 99999, map: this.map, }; // Create the marker and add to the map - var marker = new google.maps.Marker(markerOptions); + const marker = new google.maps.Marker(markerOptions); this.resultMarkers.push(marker); } }, /** - * Hide the marker, infoWindow, and bounding coordinates polygon on + * Hide the marker, infoWindow, and bounding coordinates polygon on the map when the user stops hovering on the marker icon in the result list - * @param {Event} e - The event that brought us to this function - */ - hideResultOnMap: function (e) { + * @param {Event} e - The event that brought us to this function + */ + hideResultOnMap(e) { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Get the attributes about this dataset - var resultRow = e.target, - id = $(resultRow).attr("data-id"); + let resultRow = e.target; + let id = $(resultRow).attr("data-id"); // The mouseover event might be triggered by a nested element, so loop through the parents to find the id - if (typeof id == "undefined") { + if (typeof id === "undefined") { $(e.target) .parents() .each(function () { - if (typeof $(this).attr("data-id") != "undefined") { + if (typeof $(this).attr("data-id") !== "undefined") { id = $(this).attr("data-id"); resultRow = this; } @@ -2360,19 +2332,20 @@ define([ } // Get the map tile for this result and un-highlight it - var resultGeohashes = this.searchResults + const resultGeohashes = this.searchResults .findWhere({ - id: id, + id, }) .get("geohash_9"); - for (var i = 0; i < resultGeohashes.length; i++) { - var thisGeohash = resultGeohashes[i], - containingTileGeohash = _.find(this.tileGeohashes, function (g) { - return thisGeohash.indexOf(g) == 0; - }), - containingTile = _.findWhere(this.tiles, { - geohash: containingTileGeohash, - }); + for (let i = 0; i < resultGeohashes.length; i++) { + var thisGeohash = resultGeohashes[i]; + const containingTileGeohash = _.find( + this.tileGeohashes, + (g) => thisGeohash.indexOf(g) == 0, + ); + const containingTile = _.findWhere(this.tiles, { + geohash: containingTileGeohash, + }); // If this is a geohash for a georegion outside the map, do not unhighlight a tile if (typeof containingTile === "undefined") continue; @@ -2382,7 +2355,7 @@ define([ } // Remove all markers from the map - _.each(this.resultMarkers, function (marker) { + _.each(this.resultMarkers, (marker) => { marker.setMap(null); }); this.resultMarkers = new Array(); @@ -2390,8 +2363,8 @@ define([ /** * Create a tile for each geohash facet. A separate tile label is added to the map with the count of the facet. - **/ - drawTiles: function () { + */ + drawTiles() { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; @@ -2399,6 +2372,10 @@ define([ TextOverlay.prototype = new google.maps.OverlayView(); + /** + * + * @param options + */ function TextOverlay(options) { // Now initialize all properties. this.bounds_ = options.bounds; @@ -2406,7 +2383,7 @@ define([ this.text = options.text; this.color = options.color; - var length = options.text.toString().length; + const { length } = options.text.toString(); if (length == 1) this.width = 8; else if (length == 2) this.width = 17; else if (length == 3) this.width = 25; @@ -2424,7 +2401,7 @@ define([ TextOverlay.prototype.onAdd = function () { // Create the DIV and set some basic attributes. - var div = document.createElement("div"); + const div = document.createElement("div"); div.style.color = this.color; div.style.fontSize = "15px"; div.style.position = "absolute"; @@ -2439,7 +2416,7 @@ define([ // We add an overlay to a map via one of the map's panes. // We'll add this overlay to the overlayLayer pane. - var panes = this.getPanes(); + const panes = this.getPanes(); panes.overlayLayer.appendChild(div); }; @@ -2447,28 +2424,28 @@ define([ // Size and position the overlay. We use a southwest and northeast // position of the overlay to peg it to the correct position and size. // We need to retrieve the projection from this overlay to do this. - var overlayProjection = this.getProjection(); + const overlayProjection = this.getProjection(); // Retrieve the southwest and northeast coordinates of this overlay // in latlngs and convert them to pixels coordinates. // We'll use these coordinates to resize the DIV. - var sw = overlayProjection.fromLatLngToDivPixel( + const sw = overlayProjection.fromLatLngToDivPixel( this.bounds_.getSouthWest(), ); - var ne = overlayProjection.fromLatLngToDivPixel( + const ne = overlayProjection.fromLatLngToDivPixel( this.bounds_.getNorthEast(), ); // Resize the image's DIV to fit the indicated dimensions. - var div = this.div_; - var width = this.width; - var height = 20; - - div.style.left = sw.x - width / 2 + "px"; - div.style.top = ne.y - height / 2 + "px"; - div.style.width = width + "px"; - div.style.height = height + "px"; - div.style.width = width + "px"; - div.style.height = height + "px"; + const div = this.div_; + const { width } = this; + const height = 20; + + div.style.left = `${sw.x - width / 2}px`; + div.style.top = `${ne.y - height / 2}px`; + div.style.width = `${width}px`; + div.style.height = `${height}px`; + div.style.width = `${width}px`; + div.style.height = `${height}px`; }; TextOverlay.prototype.onRemove = function () { @@ -2477,16 +2454,17 @@ define([ }; // Determine the geohash level we will use to draw tiles - var currentZoom = this.map.getZoom(), - geohashLevelNum = this.mapModel.determineGeohashLevel(currentZoom), - geohashLevel = "geohash_" + geohashLevelNum, - geohashes = this.searchResults.facetCounts[geohashLevel]; + const currentZoom = this.map.getZoom(); + const geohashLevelNum = + this.mapModel.determineGeohashLevel(currentZoom); + const geohashLevel = `geohash_${geohashLevelNum}`; + const geohashes = this.searchResults.facetCounts[geohashLevel]; // Save the current geohash level in the map model this.mapModel.set("tileGeohashLevel", geohashLevelNum); // Get all the geohashes contained in the map - var mapBBoxes = _.flatten( + const mapBBoxes = _.flatten( _.values(this.searchModel.get("geohashGroups")), ); @@ -2498,10 +2476,10 @@ define([ var filteredTileGeohashes = []; for (var i = 0; i < geohashes.length - 1; i += 2) { // Get the geohash for this tile - var tileGeohash = geohashes[i], - isInsideMap = false, - index = 0, - searchString = tileGeohash; + var tileGeohash = geohashes[i]; + let isInsideMap = false; + let index = 0; + let searchString = tileGeohash; // Find if any of the bounding boxes/geohashes inside our map contain this tile geohash while (!isInsideMap && searchString.length > 0) { @@ -2520,59 +2498,56 @@ define([ } } - //If there are no tiles on the page, the map may have failed to render, so exit. + // If there are no tiles on the page, the map may have failed to render, so exit. if ( - typeof filteredTileGeohashes == "undefined" || + typeof filteredTileGeohashes === "undefined" || !filteredTileGeohashes.length ) { return; } // Make a copy of the array that is geohash counts only - var countsOnly = []; + const countsOnly = []; for (var i = 1; i < filteredTileGeohashes.length; i += 2) { countsOnly.push(filteredTileGeohashes[i]); } // Create a range of lightness to make different colors on the tiles - var lightnessMin = this.mapModel.get("tileLightnessMin"), - lightnessMax = this.mapModel.get("tileLightnessMax"), - lightnessRange = lightnessMax - lightnessMin; + const lightnessMin = this.mapModel.get("tileLightnessMin"); + const lightnessMax = this.mapModel.get("tileLightnessMax"); + const lightnessRange = lightnessMax - lightnessMin; // Get some stats on our tile counts so we can normalize them to create a color scale - var findMedian = function (nums) { + const findMedian = function (nums) { if (nums.length % 2 == 0) { return (nums[nums.length / 2 - 1] + nums[nums.length / 2]) / 2; - } else { - return nums[nums.length / 2 - 0.5]; } + return nums[nums.length / 2 - 0.5]; }; - var sortedCounts = countsOnly.sort(function (a, b) { - return a - b; - }), - maxCount = sortedCounts[sortedCounts.length - 1], - minCount = sortedCounts[0]; + const sortedCounts = countsOnly.sort((a, b) => a - b); + const maxCount = sortedCounts[sortedCounts.length - 1]; + const minCount = sortedCounts[0]; - var viewRef = this; + const viewRef = this; // Now draw a tile for each geohash facet for (var i = 0; i < filteredTileGeohashes.length - 1; i += 2) { // Convert this geohash to lat,long values - var tileGeohash = filteredTileGeohashes[i], - decodedGeohash = nGeohash.decode(tileGeohash), - latLngCenter = new google.maps.LatLng( - decodedGeohash.latitude, - decodedGeohash.longitude, - ), - geohashBox = nGeohash.decode_bbox(tileGeohash), - swLatLng = new google.maps.LatLng(geohashBox[0], geohashBox[1]), - neLatLng = new google.maps.LatLng(geohashBox[2], geohashBox[3]), - bounds = new google.maps.LatLngBounds(swLatLng, neLatLng), - tileCount = filteredTileGeohashes[i + 1], - drawMarkers = this.mapModel.get("drawMarkers"), - marker, - count, - color; + var tileGeohash = filteredTileGeohashes[i]; + const decodedGeohash = nGeohash.decode(tileGeohash); + const latLngCenter = new google.maps.LatLng( + decodedGeohash.latitude, + decodedGeohash.longitude, + ); + const geohashBox = nGeohash.decode_bbox(tileGeohash); + const swLatLng = new google.maps.LatLng(geohashBox[0], geohashBox[1]); + const neLatLng = new google.maps.LatLng(geohashBox[2], geohashBox[3]); + const bounds = new google.maps.LatLngBounds(swLatLng, neLatLng); + const tileCount = filteredTileGeohashes[i + 1]; + const drawMarkers = this.mapModel.get("drawMarkers"); + var marker; + var count; + var color; // Normalize the range of tiles counts and convert them to a lightness domain of 20-70% lightness. if (maxCount - minCount == 0) { @@ -2584,11 +2559,10 @@ define([ lightnessMin; } - var color = - "hsl(" + this.mapModel.get("tileHue") + "," + lightness + "%,50%)"; + var color = `hsl(${this.mapModel.get("tileHue")},${lightness}%,50%)`; // Add the count to the tile - var countLocation = new google.maps.LatLngBounds( + const countLocation = new google.maps.LatLngBounds( latLngCenter, latLngCenter, ); @@ -2602,22 +2576,22 @@ define([ }); // Set up the default tile options - var tileOptions = { + const tileOptions = { fillColor: color, strokeColor: color, map: this.map, visible: true, - bounds: bounds, + bounds, }; // Merge these options with any tile options set in the map model - var modelTileOptions = this.mapModel.get("tileOptions"); - for (var attr in modelTileOptions) { + const modelTileOptions = this.mapModel.get("tileOptions"); + for (const attr in modelTileOptions) { tileOptions[attr] = modelTileOptions[attr]; } // Draw this tile - var tile = this.drawTile(tileOptions, tileGeohash, count); + const tile = this.drawTile(tileOptions, tileGeohash, count); // Save the geohashes for tiles in the view for later this.tileGeohashes.push(tileGeohash); @@ -2635,47 +2609,51 @@ define([ * @param {object} options * @param {string} geohash * @param {string} label - **/ - drawTile: function (options, geohash, label) { + */ + drawTile(options, geohash, label) { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Add the tile for these datasets to the map - var tile = new google.maps.Rectangle(options); + const tile = new google.maps.Rectangle(options); - var viewRef = this; + const viewRef = this; // Save our tiles in the view - var tileObject = { + const tileObject = { text: label, shape: tile, - geohash: geohash, - options: options, + geohash, + options, }; this.tiles.push(tileObject); // Change styles when the tile is hovered on - google.maps.event.addListener(tile, "mouseover", function (event) { + google.maps.event.addListener(tile, "mouseover", (event) => { viewRef.highlightTile(tileObject); }); // Change the styles back after the tile is hovered on - google.maps.event.addListener(tile, "mouseout", function (event) { + google.maps.event.addListener(tile, "mouseout", (event) => { viewRef.unhighlightTile(tileObject); }); // If we are at the max zoom, we will display an info window. If not, we will zoom in. if (!this.mapModel.isMaxZoom(viewRef.map)) { - /** Set up some helper functions for zooming in on the map **/ - var myFitBounds = function (myMap, bounds) { + /** + * Set up some helper functions for zooming in on the map + * @param myMap + * @param bounds + */ + const myFitBounds = function (myMap, bounds) { myMap.fitBounds(bounds); // calling fitBounds() here to center the map for the bounds - var overlayHelper = new google.maps.OverlayView(); + const overlayHelper = new google.maps.OverlayView(); overlayHelper.draw = function () { if (!this.ready) { - var extraZoom = getExtraZoom( + const extraZoom = getExtraZoom( this.getProjection(), bounds, myMap.getBounds(), @@ -2695,11 +2673,11 @@ define([ actualBounds, ) { // in: LatLngBounds bounds -> out: height and width as a Point - var getSizeInPixels = function (bounds) { - var sw = projection.fromLatLngToContainerPixel( + const getSizeInPixels = function (bounds) { + const sw = projection.fromLatLngToContainerPixel( bounds.getSouthWest(), ); - var ne = projection.fromLatLngToContainerPixel( + const ne = projection.fromLatLngToContainerPixel( bounds.getNorthEast(), ); return new google.maps.Point( @@ -2708,8 +2686,8 @@ define([ ); }; - var expectedSize = getSizeInPixels(expectedBounds), - actualSize = getSizeInPixels(actualBounds); + const expectedSize = getSizeInPixels(expectedBounds); + const actualSize = getSizeInPixels(actualBounds); if ( Math.floor(expectedSize.x) == 0 || @@ -2718,9 +2696,9 @@ define([ return 0; } - var qx = actualSize.x / expectedSize.x; - var qy = actualSize.y / expectedSize.y; - var min = Math.min(qx, qy); + const qx = actualSize.x / expectedSize.x; + const qy = actualSize.y / expectedSize.y; + const min = Math.min(qx, qy); if (min < 1) { return 0; @@ -2730,24 +2708,24 @@ define([ }; // Zoom in when the tile is clicked on - gmaps.event.addListener(tile, "click", function (clickEvent) { + gmaps.event.addListener(tile, "click", (clickEvent) => { // Change the center viewRef.map.panTo(clickEvent.latLng); // Get this tile's bounds - var tileBounds = tile.getBounds(); + const tileBounds = tile.getBounds(); // Get the current map bounds - var mapBounds = viewRef.map.getBounds(); + const mapBounds = viewRef.map.getBounds(); // Change the zoom - //viewRef.map.fitBounds(tileBounds); + // viewRef.map.fitBounds(tileBounds); myFitBounds(viewRef.map, tileBounds); // Track this event MetacatUI.analytics?.trackEvent( "map", "clickTile", - "geohash : " + tileObject.geohash, + `geohash : ${tileObject.geohash}`, ); }); } @@ -2755,12 +2733,12 @@ define([ return tile; }, - highlightTile: function (tile) { + highlightTile(tile) { // Change the tile style on hover tile.shape.setOptions(this.mapModel.get("tileOnHover")); // Change the label color on hover - var div = tile.text.div_; + const div = tile.text.div_; if (div) { div.style.color = this.mapModel.get("tileLabelColorOnHover"); tile.text.div_ = div; @@ -2768,12 +2746,12 @@ define([ } }, - unhighlightTile: function (tile) { + unhighlightTile(tile) { // Change back the tile to it's original styling tile.shape.setOptions(tile.options); // Change back the label color - var div = tile.text.div_; + const div = tile.text.div_; div.style.color = this.mapModel.get("tileLabelColor"); tile.text.div_ = div; $(div).css("color", this.mapModel.get("tileLabelColor")); @@ -2783,47 +2761,43 @@ define([ * Get the details on each marker * And create an infowindow for that marker */ - addMarkers: function () { + addMarkers() { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Clone the Search model - var searchModelClone = this.searchModel.clone(), - geohashLevel = this.mapModel.get("tileGeohashLevel"), - viewRef = this, - markers = this.markers; + const searchModelClone = this.searchModel.clone(); + const geohashLevel = this.mapModel.get("tileGeohashLevel"); + const viewRef = this; + const { markers } = this; // Change the geohash filter to match our tiles searchModelClone.set("geohashLevel", geohashLevel); searchModelClone.set("geohashes", this.markerGeohashes); // Now run a query to get a list of documents that are represented by our markers - var query = - "q=" + - searchModelClone.getQuery() + - "&fl=id,title,geohash_9,abstract,geohash_" + - geohashLevel + - "&rows=1000" + - "&wt=json"; - - var requestSettings = { + const query = + `q=${searchModelClone.getQuery()}&fl=id,title,geohash_9,abstract,geohash_${geohashLevel}&rows=1000` + + `&wt=json`; + + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { - var docs = data.response.docs; - var uniqueGeohashes = viewRef.markerGeohashes; + success(data, textStatus, xhr) { + const { docs } = data.response; + let uniqueGeohashes = viewRef.markerGeohashes; // Create a marker and infoWindow for each document - _.each(docs, function (doc, key, list) { - var marker, - drawMarkersAt = []; + _.each(docs, (doc, key, list) => { + let marker; + const drawMarkersAt = []; // Find the tile place that this document belongs to // For each geohash value at the current geohash level for this document, - _.each(doc.geohash_9, function (geohash, key, list) { + _.each(doc.geohash_9, (geohash, key, list) => { // Loop through each unique tile location to find its match - for (var i = 0; i <= uniqueGeohashes.length; i++) { + for (let i = 0; i <= uniqueGeohashes.length; i++) { if (uniqueGeohashes[i] == geohash.substr(0, geohashLevel)) { drawMarkersAt.push(geohash); uniqueGeohashes = _.without(uniqueGeohashes, geohash); @@ -2832,14 +2806,14 @@ define([ }); _.each(drawMarkersAt, function (markerGeohash, key, list) { - var decodedGeohash = nGeohash.decode(markerGeohash), - latLng = new google.maps.LatLng( - decodedGeohash.latitude, - decodedGeohash.longitude, - ); + const decodedGeohash = nGeohash.decode(markerGeohash); + const latLng = new google.maps.LatLng( + decodedGeohash.latitude, + decodedGeohash.longitude, + ); // Set up the options for each marker - var markerOptions = { + const markerOptions = { position: latLng, icon: this.mapModel.get("markerImage"), zIndex: 99999, @@ -2847,7 +2821,7 @@ define([ }; // Create the marker and add to the map - var marker = new google.maps.Marker(markerOptions); + const marker = new google.maps.Marker(markerOptions); }); }); }, @@ -2864,60 +2838,51 @@ define([ * Get the details on each tile - a list of ids and titles for each dataset contained in that tile * And create an infowindow for that tile */ - addTileInfoWindows: function () { + addTileInfoWindows() { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Clone the Search model - var searchModelClone = this.searchModel.clone(), - geohashLevel = this.mapModel.get("tileGeohashLevel"), - geohashName = "geohash_" + geohashLevel, - viewRef = this, - infoWindows = []; + const searchModelClone = this.searchModel.clone(); + const geohashLevel = this.mapModel.get("tileGeohashLevel"); + const geohashName = `geohash_${geohashLevel}`; + const viewRef = this; + const infoWindows = []; // Change the geohash filter to match our tiles searchModelClone.set("geohashLevel", geohashLevel); searchModelClone.set("geohashes", this.tileGeohashes); // Now run a query to get a list of documents that are represented by our tiles - var query = - "q=" + - searchModelClone.getQuery() + - "&fl=id,title,geohash_9," + - geohashName + - "&rows=1000" + - "&wt=json"; - - var requestSettings = { + const query = + `q=${searchModelClone.getQuery()}&fl=id,title,geohash_9,${geohashName}&rows=1000` + + `&wt=json`; + + const requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { + success(data, textStatus, xhr) { // Make an infoWindow for each doc - var docs = data.response.docs; + const { docs } = data.response; // For each tile, loop through the docs to find which ones to include in its infoWindow - _.each(viewRef.tiles, function (tile, key, list) { - var infoWindowContent = ""; + _.each(viewRef.tiles, (tile, key, list) => { + let infoWindowContent = ""; - _.each(docs, function (doc, key, list) { - var docGeohashes = doc[geohashName]; + _.each(docs, (doc, key, list) => { + const docGeohashes = doc[geohashName]; if (docGeohashes) { // Is this document in this tile? - for (var i = 0; i < docGeohashes.length; i++) { + for (let i = 0; i < docGeohashes.length; i++) { if (docGeohashes[i] == tile.geohash) { // Add this doc to the infoWindow content - infoWindowContent += - "" + - doc.title + - " (" + - doc.id + - ")
    "; + infoWindowContent += `${doc.title} (${ + doc.id + })
    `; break; } } @@ -2925,21 +2890,19 @@ define([ }); // The center of the tile - var decodedGeohash = nGeohash.decode(tile.geohash), - tileCenter = new google.maps.LatLng( - decodedGeohash.latitude, - decodedGeohash.longitude, - ); + const decodedGeohash = nGeohash.decode(tile.geohash); + const tileCenter = new google.maps.LatLng( + decodedGeohash.latitude, + decodedGeohash.longitude, + ); // The infowindow - var infoWindow = new gmaps.InfoWindow({ + const infoWindow = new gmaps.InfoWindow({ content: - "
    " + - "

    Datasets located here

    " + - "

    " + - infoWindowContent + - "

    " + - "
    ", + `
    ` + + `

    Datasets located here

    ` + + `

    ${infoWindowContent}

    ` + + `
    `, isOpen: false, disableAutoPan: false, maxWidth: 250, @@ -2953,7 +2916,7 @@ define([ tile.shape, "click", function (clickEvent) { - //--- We are at max zoom, display an infowindow ----// + // --- We are at max zoom, display an infowindow ----// if (this.mapModel.isMaxZoom(viewRef.map)) { // Find the infowindow that belongs to this tile in the view infoWindow.open(viewRef.map); @@ -2963,13 +2926,13 @@ define([ viewRef.closeInfoWindows(infoWindow); } - //------ We are not at max zoom, so zoom into this tile ----// + // ------ We are not at max zoom, so zoom into this tile ----// else { // Change the center viewRef.map.panTo(clickEvent.latLng); // Get this tile's bounds - var bounds = tile.shape.getBounds(); + const bounds = tile.shape.getBounds(); // Change the zoom viewRef.map.fitBounds(bounds); @@ -2978,7 +2941,7 @@ define([ ); // Close the infowindow upon any click on the map - gmaps.event.addListener(viewRef.map, "click", function () { + gmaps.event.addListener(viewRef.map, "click", () => { infoWindow.close(); infoWindow.isOpen = false; }); @@ -3001,13 +2964,14 @@ define([ * Iterate over each infowindow that we have stored in the view and close it. * Pass an infoWindow object to this function to keep that infoWindow open/skip it * @param {infoWindow} - An infoWindow to keep open + * @param except */ - closeInfoWindows: function (except) { - var infoWindowLists = [this.markerInfoWindows, this.tileInfoWindows]; + closeInfoWindows(except) { + const infoWindowLists = [this.markerInfoWindows, this.tileInfoWindows]; - _.each(infoWindowLists, function (infoWindows, key, list) { + _.each(infoWindowLists, (infoWindows, key, list) => { // Iterate over all the marker infowindows and close all of them except for this one - for (var i = 0; i < infoWindows.length; i++) { + for (let i = 0; i < infoWindows.length; i++) { if (infoWindows[i].isOpen && infoWindows[i] != except) { // Close this info window and stop looking, since only one of each kind should be open anyway infoWindows[i].close(); @@ -3020,15 +2984,15 @@ define([ /** * Remove all the tiles and text from the map - **/ - removeTiles: function () { + */ + removeTiles() { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Remove the tile from the map - _.each(this.tiles, function (tile, key, list) { + _.each(this.tiles, (tile, key, list) => { if (tile.shape) tile.shape.setMap(null); if (tile.text) tile.text.setMap(null); }); @@ -3042,14 +3006,14 @@ define([ /** * Iterate over all the markers in the view and remove them from the map and view */ - removeMarkers: function () { + removeMarkers() { // Exit if maps are not in use if (this.mode != "map" || !gmaps) { return false; } // Remove the marker from the map - _.each(this.markers, function (marker, key, list) { + _.each(this.markers, (marker, key, list) => { marker.marker.setMap(null); }); @@ -3065,17 +3029,18 @@ define([ * ================================================================================================== */ - /** Add all items in the **SearchResults** collection + /** + * Add all items in the **SearchResults** collection * This loads the first 25, then waits for the map to be * fully loaded and then loads the remaining items. * Without this delay, the app waits until all records are processed */ - addAll: function () { + addAll() { // After the map is done loading, then load the rest of the results into the list if (this.ready) this.renderAll(); else { - var viewRef = this; - var intervalID = setInterval(function () { + const viewRef = this; + var intervalID = setInterval(() => { if (viewRef.ready) { clearInterval(intervalID); viewRef.renderAll(); @@ -3084,10 +3049,10 @@ define([ } // After all the results are loaded, query for our facet counts in the background - //this.getAutocompletes(); + // this.getAutocompletes(); }, - renderAll: function () { + renderAll() { // do this first to indicate coming results this.updateStats(); @@ -3099,7 +3064,7 @@ define([ this.$results.removeClass("loading"); // If there are no results, display so - var numFound = this.searchResults.length; + const numFound = this.searchResults.length; if (numFound == 0) { // Add a No Results Found message this.$results.html("

    No results found.

    "); @@ -3118,12 +3083,12 @@ define([ !MetacatUI.appSearchResults.length ) { $("#no-results-found").after( - "

    Where are my data sets?

    If you are a previous ACADIS Gateway user, " + - "you will need to take additional steps to access your data sets in the new NSF Arctic Data Center." + - "Send us a message at support@arcticdata.io with your old ACADIS " + - "Gateway username and your ORCID identifier (" + - MetacatUI.appUserModel.get("username") + - "), we will help.

    ", + `

    Where are my data sets?

    If you are a previous ACADIS Gateway user, ` + + `you will need to take additional steps to access your data sets in the new NSF Arctic Data Center.` + + `Send us a message at support@arcticdata.io with your old ACADIS ` + + `Gateway username and your ORCID identifier (${MetacatUI.appUserModel.get( + "username", + )}), we will help.

    `, ); } } @@ -3133,7 +3098,7 @@ define([ // Clear the results list before we start adding new rows this.$results.html(""); - //--First map all the results-- + // --First map all the results-- if (gmaps && this.mapModel) { // Draw all the tiles on the map to represent the datasets this.drawTiles(); @@ -3142,25 +3107,25 @@ define([ $("#map-container").removeClass("loading"); } - var pid_list = new Array(); + const pid_list = new Array(); - //--- Add all the results to the list --- + // --- Add all the results to the list --- for (i = 0; i < this.searchResults.length; i++) { pid_list.push(this.searchResults.models[i].get("id")); } if (MetacatUI.appModel.get("displayDatasetMetrics")) { - var metricsModel = new MetricsModel({ - pid_list: pid_list, + const metricsModel = new MetricsModel({ + pid_list, type: "catalog", }); metricsModel.fetch(); this.metricsModel = metricsModel; } - //--- Add all the results to the list --- + // --- Add all the results to the list --- for (i = 0; i < this.searchResults.length; i++) { - var element = this.searchResults.models[i]; + const element = this.searchResults.models[i]; if (typeof element !== "undefined") this.addOne(element, this.metricsModel); } @@ -3175,8 +3140,9 @@ define([ /** * Add a single SolrResult item to the list by creating a view for it and appending its element to the DOM. + * @param result */ - addOne: function (result) { + addOne(result) { // Get the view and package service URL's this.$view_service = MetacatUI.appModel.get("viewServiceUrl"); this.$package_service = MetacatUI.appModel.get("packageServiceUrl"); @@ -3185,7 +3151,7 @@ define([ package_service: this.$package_service, }); - var view = new SearchResultView({ + const view = new SearchResultView({ model: result, metricsModel: this.metricsModel, }); @@ -3197,23 +3163,23 @@ define([ if ( gmaps && this.mapModel && - typeof result.get("geohash_9") != "undefined" && + typeof result.get("geohash_9") !== "undefined" && result.get("geohash_9") != null ) { - var title = result.get("title"); + const title = result.get("title"); - for (var i = 0; i < result.get("geohash_9").length; i++) { - var centerGeohash = result.get("geohash_9")[i], - decodedGeohash = nGeohash.decode(centerGeohash), - position = new google.maps.LatLng( - decodedGeohash.latitude, - decodedGeohash.longitude, - ), - marker = new gmaps.Marker({ - position: position, - icon: this.mapModel.get("markerImage"), - zIndex: 99999, - }); + for (let i = 0; i < result.get("geohash_9").length; i++) { + const centerGeohash = result.get("geohash_9")[i]; + const decodedGeohash = nGeohash.decode(centerGeohash); + const position = new google.maps.LatLng( + decodedGeohash.latitude, + decodedGeohash.longitude, + ); + const marker = new gmaps.Marker({ + position, + icon: this.mapModel.get("markerImage"), + zIndex: 99999, + }); } } }, @@ -3224,9 +3190,9 @@ define([ * @param {SolrResult} model * @param {XMLHttpRequest.response} response */ - showError: function (model, response) { - var errorMessage = ""; - var statusCode = response.status; + showError(model, response) { + let errorMessage = ""; + let statusCode = response.status; if (!statusCode) { statusCode = parseInt(response.statusText); @@ -3244,17 +3210,14 @@ define([ errorMessage = ""; } } finally { - if (typeof errorMessage == "string" && errorMessage.length) { - errorMessage = "

    Error details: " + errorMessage + "

    "; + if (typeof errorMessage === "string" && errorMessage.length) { + errorMessage = `

    Error details: ${errorMessage}

    `; } } } MetacatUI.appView.showAlert( - "

    " + - this.solrErrorTitle + - ".

    " + - errorMessage, + `

    ${this.solrErrorTitle}.

    ${errorMessage}`, "alert-error", this.$results, ); @@ -3267,7 +3230,7 @@ define([ * STYLING THE UI * ================================================================================================== */ - toggleMapMode: function (e) { + toggleMapMode(e) { if (typeof e === "object") { e.preventDefault(); } @@ -3292,7 +3255,7 @@ define([ }, // Communicate that the page is loading - loading: function () { + loading() { $("#map-container").addClass("loading"); this.$results.addClass("loading"); @@ -3304,13 +3267,13 @@ define([ }, // Toggles the collapseable filters sidebar and result list in the default theme - collapse: function (e) { - var id = $(e.target).attr("data-collapse"); + collapse(e) { + const id = $(e.target).attr("data-collapse"); - $("#" + id).toggleClass("collapsed"); + $(`#${id}`).toggleClass("collapsed"); }, - toggleFilterCollapse: function (e) { + toggleFilterCollapse(e) { let container = this.$(".filter-contain.collapsable"); if (typeof e !== "undefined") { container = $(e.target).parents(".filter-contain.collapsable"); @@ -3361,7 +3324,7 @@ define([ /* * Either hides or shows the "clear all filters" button */ - toggleClearButton: function () { + toggleClearButton() { if (this.searchModel.filterCount() > 0) { this.showClearButton(); } else { @@ -3370,7 +3333,7 @@ define([ }, // Move the popover element up the page a bit if it runs off the bottom of the page - preventPopoverRunoff: function (e) { + preventPopoverRunoff(e) { // In map view only (because all elements are fixed and you can't scroll) if (this.mode == "map") { var viewportHeight = $("#map-container").outerHeight(); @@ -3379,9 +3342,9 @@ define([ } if ($(".popover").length > 0) { - var offset = $(".popover").offset(); - var popoverHeight = $(".popover").outerHeight(); - var topPosition = offset.top; + const offset = $(".popover").offset(); + const popoverHeight = $(".popover").outerHeight(); + const topPosition = offset.top; // If pixels are cut off the top of the page, readjust its vertical position if (topPosition < 0) { @@ -3390,11 +3353,11 @@ define([ }); } else { // Else, let's check if it is cut off at the bottom - var totalHeight = topPosition + popoverHeight; + const totalHeight = topPosition + popoverHeight; - var pixelsHidden = totalHeight - viewportHeight; + const pixelsHidden = totalHeight - viewportHeight; - var newTopPosition = topPosition - pixelsHidden - 40; + const newTopPosition = topPosition - pixelsHidden - 40; // If pixels are cut off the bottom of the page, readjust its vertical position if (pixelsHidden > 0) { @@ -3406,7 +3369,7 @@ define([ } }, - onClose: function () { + onClose() { this.stopListening(); $(".DataCatalog").removeClass("DataCatalog"); diff --git a/src/js/views/DataPackageView.js b/src/js/views/DataPackageView.js index 5a7cd1c99..613ea3499 100644 --- a/src/js/views/DataPackageView.js +++ b/src/js/views/DataPackageView.js @@ -4,6 +4,7 @@ "backbone", "localforage", "collections/DataPackage", + "common/Utilities", "models/DataONEObject", "models/PackageModel", "models/metadata/ScienceMetadata", @@ -14,12 +15,13 @@ "text!templates/dataPackage.html", "text!templates/dataPackageStart.html", "text!templates/dataPackageHeader.html", -], function ( +], ( $, _, Backbone, LocalForage, DataPackage, + Utilities, DataONEObject, PackageModel, ScienceMetadata, @@ -30,7 +32,7 @@ DataPackageTemplate, DataPackageStartTemplate, DataPackageHeaderTemplate, -) { +) => { "use strict"; /** @@ -39,7 +41,7 @@ * a file/folder browser * @classcategory Views * @screenshot views/DataPackageView.png - * @extends Backbone.View + * @augments Backbone.View */ var DataPackageView = Backbone.View.extend( /** @lends DataPackageView.prototype */ { @@ -80,11 +82,11 @@ /* Flag indicating the open or closed state of the package rows */ isOpen: true, - initialize: function (options) { + initialize(options) { if (options === undefined || !options) var options = {}; if (!options.edit) { - //The edit option will allow the user to edit the table + // The edit option will allow the user to edit the table this.edit = options.edit || false; this.mode = "view"; this.packageId = options.packageId || null; @@ -107,18 +109,18 @@ this.listenTo(this.packageModel, "changeAll", this.render); } else { - //Get the options sent to this view - if (typeof options == "object") { - //The edit option will allow the user to edit the table + // Get the options sent to this view + if (typeof options === "object") { + // The edit option will allow the user to edit the table this.edit = options.edit || false; this.mode = "edit"; - //The data package to render + // The data package to render this.dataPackage = options.dataPackage || new DataPackage(); this.parentEditorView = options.parentEditorView || null; } - //Create a new DataPackage collection if one wasn't sent + // Create a new DataPackage collection if one wasn't sent else if (!this.dataPackage) { this.dataPackage = new DataPackage(); } @@ -130,7 +132,7 @@ /** * Render the DataPackage HTML */ - render: function () { + render() { this.$el.addClass("download-contents table-condensed"); this.$el.append( this.template({ @@ -158,13 +160,13 @@ this.addAll(); if (this.edit) { - //If this is a new data package, then display a message and button + // If this is a new data package, then display a message and button if ( (this.dataPackage.length == 1 && this.dataPackage.models[0].isNew()) || !this.dataPackage.length ) { - var messageRow = this.startMessageTemplate(); + const messageRow = this.startMessageTemplate(); this.$("tbody").append(messageRow); @@ -173,7 +175,7 @@ }); } - //Render the Share control(s) + // Render the Share control(s) this.renderShareControl(); } else { // check for nessted datasets @@ -188,13 +190,14 @@ /** * Add a single DataItemView row to the DataPackageView + * @param item + * @param dataPackage */ - addOne: function (item, dataPackage) { + addOne(item, dataPackage) { if (!item) return false; - //Don't add duplicate rows - if (this.$(".data-package-item[data-id='" + item.id + "']").length) - return; + // Don't add duplicate rows + if (this.$(`.data-package-item[data-id='${item.id}']`).length) return; // Don't add data package if ( @@ -204,18 +207,21 @@ return; } - var dataItemView, scimetaParent, parentRow, delayed_models; + let dataItemView; + let scimetaParent; + let parentRow; + let delayed_models; if (_.contains(Object.keys(this.subviews), item.id)) { return false; // Don't double render } - var itemPath = null, - view = this; + let itemPath = null; + const view = this; if (!_.isEmpty(this.atLocationObj)) { itemPath = this.atLocationObj[item.get("id")]; if (itemPath[0] != "/") { - itemPath = "/" + itemPath; + itemPath = `/${itemPath}`; } } @@ -226,37 +232,37 @@ if (typeof dataPackageId === "undefined") dataPackageId = this.dataPackage.id; - var insertInfoIcon = this.edit + const insertInfoIcon = this.edit ? false : view.dataEntities.includes(item.id); dataItemView = new DataItemView({ model: item, metricsModel: this.metricsModel, - itemPath: itemPath, - insertInfoIcon: insertInfoIcon, + itemPath, + insertInfoIcon, currentlyViewing: this.currentlyViewing, mode: this.mode, parentEditorView: this.parentEditorView, - dataPackageId: dataPackageId, + dataPackageId, }); this.subviews[item.id] = dataItemView; // keep track of all views if (this.edit) { - //Get the science metadata that documents this item + // Get the science metadata that documents this item scimetaParent = item.get("isDocumentedBy"); - //If this item is not documented by a science metadata object, + // If this item is not documented by a science metadata object, // and there is only one science metadata doc in the package, then assume it is // documented by that science metadata doc - if (typeof scimetaParent == "undefined" || !scimetaParent) { - //Get the science metadata models - var metadataIds = this.dataPackage.sciMetaPids; + if (typeof scimetaParent === "undefined" || !scimetaParent) { + // Get the science metadata models + const metadataIds = this.dataPackage.sciMetaPids; - //If there is only one science metadata model in the package, then use it + // If there is only one science metadata model in the package, then use it if (metadataIds.length == 1) scimetaParent = metadataIds[0]; } - //Otherwise, get the first science metadata doc that documents this object + // Otherwise, get the first science metadata doc that documents this object else { scimetaParent = scimetaParent[0]; } @@ -276,7 +282,7 @@ } else { // Find the parent row by it's id, stored in a custom attribute if (scimetaParent) - parentRow = this.$("[data-id='" + scimetaParent + "']"); + parentRow = this.$(`[data-id='${scimetaParent}']`); if (typeof parentRow !== "undefined" && parentRow.length) { // This is a data row, insert below it's metadata parent folder @@ -285,7 +291,7 @@ // Remove it from the delayedModels list if necessary if (_.contains(Object.keys(this.delayedModels), scimetaParent)) { delayed_models = this.delayedModels[scimetaParent]; - var index = _.indexOf(delayed_models, item); + const index = _.indexOf(delayed_models, item); delayed_models = delayed_models.splice(index, 1); // Put the shortened array back if delayed models remains @@ -299,9 +305,7 @@ this.trigger("addOne"); } else { console.warn( - "Couldn't render " + - item.id + - ". Delayed until parent is rendered.", + `Couldn't render ${item.id}. Delayed until parent is rendered.`, ); // Postpone the data row until the parent is rendered delayed_models = this.delayedModels[scimetaParent]; @@ -330,8 +334,8 @@ /** * Render the Data Package View and insert it into this view */ - renderDataPackage: function () { - var view = this; + renderDataPackage() { + const view = this; if (MetacatUI.rootDataPackage.packageModel.isNew()) { view.renderMember(this.model); @@ -343,23 +347,23 @@ this.listenTo(model, "sync", view.renderMember); else if (model.get("synced")) view.renderMember(model); - //Listen for changes on this member + // Listen for changes on this member model.on("change:fileName", model.addToUploadQueue); }); - //Render the Data Package view + // Render the Data Package view this.dataPackageView = new DataPackageView({ edit: true, dataPackage: MetacatUI.rootDataPackage, parentEditorView: this, }); - //Render the view - var $packageTableContainer = this.$("#data-package-container"); + // Render the view + const $packageTableContainer = this.$("#data-package-container"); $packageTableContainer.html(this.dataPackageView.render().el); - //Make the view resizable on the bottom - var handle = $(document.createElement("div")) + // Make the view resizable on the bottom + const handle = $(document.createElement("div")) .addClass("ui-resizable-handle ui-resizable-s") .attr("title", "Drag to resize") .append( @@ -370,15 +374,15 @@ handles: { s: handle }, minHeight: 100, maxHeight: 900, - resize: function () { + resize() { view.emlView.resizeTOC(); }, }); - var tableHeight = ($(window).height() - $("#Navbar").height()) * 0.4; - $packageTableContainer.css("height", tableHeight + "px"); + const tableHeight = ($(window).height() - $("#Navbar").height()) * 0.4; + $packageTableContainer.css("height", `${tableHeight}px`); - var table = this.dataPackageView.$el; + const table = this.dataPackageView.$el; this.listenTo(this.dataPackageView, "addOne", function () { if ( table.outerHeight() > $packageTableContainer.outerHeight() && @@ -394,7 +398,7 @@ if (this.emlView) this.emlView.resizeTOC(); - //Save the view as a subview + // Save the view as a subview this.subviews.push(this.dataPackageView); this.listenTo( @@ -407,12 +411,12 @@ /** * Add all rows to the DataPackageView */ - addAll: function () { + addAll() { this.$el.find("#data-package-table-body").html(""); // clear the table first this.dataPackage.sort(); if (!this.edit) { - var atLocationObj = this.dataPackage.getAtLocation(); + const atLocationObj = this.dataPackage.getAtLocation(); this.atLocationObj = atLocationObj; // form path to D1 object dictionary @@ -428,11 +432,11 @@ } }, this); - for (let key of Object.keys(this.atLocationObj)) { - var path = this.atLocationObj[key]; - var pathArray = path.split("/"); + for (const key of Object.keys(this.atLocationObj)) { + const path = this.atLocationObj[key]; + const pathArray = path.split("/"); pathArray.pop(); - var parentPath = pathArray.join("/"); + const parentPath = pathArray.join("/"); if (filePathObj.hasOwnProperty(parentPath)) { filePathObj[parentPath].push(key); } else { @@ -443,31 +447,33 @@ } // add top level data package row to the package table - var tableRow = null, - view = this, - title = this.packageTitle, - packageUrl = null; + let tableRow = null; + const view = this; + let title = this.packageTitle; + let packageUrl = null; if (title === "") { - let metadataObj = _.filter(this.dataPackage.models, function (m) { - return m.get("id") == view.currentlyViewing; - }); + const metadataObj = _.filter( + this.dataPackage.models, + (m) => m.get("id") == view.currentlyViewing, + ); if (metadataObj.length > 0) { title = metadataObj[0].get("title"); - let metaId = metadataObj[0].get("id"); + const metaId = metadataObj[0].get("id"); this.metaId = metaId; } else { title = this.dataPackage.get("id"); } } - let titleTooltip = title; + const titleTooltip = title; title = title.length > 150 - ? title.slice(0, 75) + - "..." + - title.slice(title.length - 75, title.length) + ? `${title.slice(0, 75)}...${title.slice( + title.length - 75, + title.length, + )}` : title; // set the package URL @@ -476,13 +482,13 @@ MetacatUI.appModel.get("packageServiceUrl") + encodeURIComponent(view.dataPackage.id); - var disablePackageDownloads = this.disablePackageDownloads; + const { disablePackageDownloads } = this; tableRow = this.dataPackageHeaderTemplate({ id: view.dataPackage.id, - title: title, - titleTooltip: titleTooltip, + title, + titleTooltip, downloadUrl: packageUrl, - disablePackageDownloads: disablePackageDownloads, + disablePackageDownloads, disablePackageUrl: true, }); @@ -490,7 +496,7 @@ if (this.atLocationObj !== undefined && filePathObj !== undefined) { // sort the filePath by length - var sortedFilePathObj = Object.keys(filePathObj) + const sortedFilePathObj = Object.keys(filePathObj) .sort() .reduce((obj, key) => { obj[key] = filePathObj[key]; @@ -509,23 +515,25 @@ /** * Add all the files and folders + * @param sortedFilePathObj */ - addFilesAndFolders: function (sortedFilePathObj) { + addFilesAndFolders(sortedFilePathObj) { if (!sortedFilePathObj) return false; - var insertedPath = new Array(); - let pathMap = new Object(); + const insertedPath = new Array(); + const pathMap = new Object(); pathMap[""] = ""; - for (let key of Object.keys(sortedFilePathObj)) { + for (const key of Object.keys(sortedFilePathObj)) { // add folder - var pathArray = key.split("/"); - //skip the first empty value + const pathArray = key.split("/"); + // skip the first empty value for (let i = 0; i < pathArray.length; i++) { if (pathArray[i].length < 1) continue; if (!(pathArray[i] in pathMap)) { // insert path - var dataItemView, itemPath; + var dataItemView; + var itemPath; // root if (i == 0) { @@ -537,7 +545,7 @@ dataItemView = new DataItemView({ mode: this.mode, itemName: pathArray[i], - itemPath: itemPath, + itemPath, itemType: "folder", parentEditorView: this.parentEditorView, dataPackageId: this.dataPackage.id, @@ -549,12 +557,12 @@ this.trigger("addOne"); - pathMap[pathArray[i]] = itemPath + "/" + pathArray[i]; + pathMap[pathArray[i]] = `${itemPath}/${pathArray[i]}`; } } // add files in the folder - var itemArray = sortedFilePathObj[key]; + const itemArray = sortedFilePathObj[key]; // Add metadata object at the top of the file table if ( @@ -562,12 +570,12 @@ this.metaId !== "undefined" && itemArray.includes(this.metaId) ) { - let item = this.metaId; + const item = this.metaId; this.addOne(this.dataPackage.get(item)); } for (let i = 0; i < itemArray.length; i++) { - let item = itemArray[i]; + const item = itemArray[i]; this.addOne(this.dataPackage.get(item)); } } @@ -575,10 +583,9 @@ /** Remove the subview represented by the given model item. - @param item The model representing the sub view to be removed - */ - removeOne: function (item) { + */ + removeOne(item) { if (_.contains(Object.keys(this.subviews), item.id)) { // Remove the view and the its reference in the subviews list this.subviews[item.id].remove(); @@ -586,8 +593,8 @@ } }, - handleAddFiles: function (e) { - //Pass this on to the DataItemView for the root data package + handleAddFiles(e) { + // Pass this on to the DataItemView for the root data package this.$(".data-package-item.folder") .first() .data("view") @@ -598,7 +605,7 @@ * Renders a control that opens the AccessPolicyView for editing permissions on this package * @since 2.15.0 */ - renderShareControl: function () { + renderShareControl() { if ( this.parentEditorView && !this.parentEditorView.isAccessPolicyEditEnabled() @@ -610,31 +617,32 @@ /** * Close subviews as needed */ - onClose: function () { + onClose() { // Close each subview _.each( Object.keys(this.subviews), function (id) { - var subview = this.subviews[id]; + const subview = this.subviews[id]; subview.onClose(); }, this, ); - //Reset the subviews from the view completely (by removing it from the prototype) + // Reset the subviews from the view completely (by removing it from the prototype) this.__proto__.subviews = {}; }, /** Show or hide the data rows associated with the event row science metadata - */ - toggleRows: function (event) { + * @param event + */ + toggleRows(event) { if (this.isOpen) { // Get the DataItemView associated with each id _.each( Object.keys(this.subviews), function (id) { - var subview = this.subviews[id]; + const subview = this.subviews[id]; if (subview.model.get("type") === "Data" && subview.remove) { // Remove the view from the DOM @@ -662,7 +670,7 @@ this.isOpen = false; } else { // Add sub rows to the view - var dataModels = this.dataPackage.where({ type: "Data" }); + const dataModels = this.dataPackage.where({ type: "Data" }); _.each( dataModels, function (model) { @@ -696,23 +704,23 @@ * @param {Event} e - The event object. * @since 2.28.0 */ - expand: function (e) { + expand(e) { // Don't do anything... e.preventDefault(); - var view = this; - var eventEl = $(e.target).parents("td"); - var rowEl = $(e.target).parents("tr"); + const view = this; + const eventEl = $(e.target).parents("td"); + const rowEl = $(e.target).parents("tr"); - var parentId = rowEl.data("id"); - var children = "tr[data-parent='" + parentId + "']"; + const parentId = rowEl.data("id"); + const children = `tr[data-parent='${parentId}']`; this.$(children).fadeIn(); this.$(eventEl) .children() .children(".expand-control") - .fadeOut(function () { + .fadeOut(() => { view .$(eventEl) .children() @@ -725,7 +733,7 @@ .children() .children() .children(".collapse-control") - .fadeOut(function () { + .fadeOut(() => { view .$(children) .children() @@ -738,25 +746,24 @@ /** * Collapse function to hide rows when a user clicks on a collapse control. * @param {Event} e - The event object. - * * @since 2.28.0 */ - collapse: function (e) { + collapse(e) { // Don't do anything... e.preventDefault(); - var view = this; - var eventEl = $(e.target).parents("td"); - var rowEl = $(e.target).parents("tr"); + const view = this; + const eventEl = $(e.target).parents("td"); + const rowEl = $(e.target).parents("tr"); - var parentId = rowEl.data("id"); - var children = "tr[data-parent^='" + parentId + "']"; + const parentId = rowEl.data("id"); + const children = `tr[data-parent^='${parentId}']`; this.$(children).fadeOut(); this.$(eventEl) .children() .children(".collapse-control") - .fadeOut(function () { + .fadeOut(() => { view.$(eventEl).children().children(".expand-control").fadeIn(); view.$(".tooltip-this").tooltip(); }); @@ -765,25 +772,24 @@ /** * Expand all function to show all child rows when a user clicks on an expand-all control. * @param {Event} e - The event object. - * * @since 2.28.0 */ - expandAll: function (e) { + expandAll(e) { // Don't do anything... e.preventDefault(); - var view = this; - var eventEl = $(e.target).parents("td"); - var rowEl = $(e.target).parents("tr"); + const view = this; + const eventEl = $(e.target).parents("td"); + const rowEl = $(e.target).parents("tr"); - var parentId = rowEl.data("id"); - var children = "tr[data-packageid='" + parentId + "']"; + const parentId = rowEl.data("id"); + const children = `tr[data-packageid='${parentId}']`; this.$(children).fadeIn(); this.$(eventEl) .children(".d1package-expand") - .fadeOut(function () { + .fadeOut(() => { view.$(eventEl).children(".d1package-collapse").fadeIn("fast"); view.$(".tooltip-this").tooltip(); }); @@ -792,7 +798,7 @@ .children() .children() .children(".collapse-control") - .fadeOut(function () { + .fadeOut(() => { view .$(children) .children() @@ -805,31 +811,30 @@ /** * Collapse all function to hide all child rows when a user clicks on a collapse-all control. * @param {Event} e - The event object. - * * @since 2.28.0 */ - collapseAll: function (e) { + collapseAll(e) { // Don't do anything... e.preventDefault(); - var view = this; - var eventEl = $(e.target).parents("td"); - var rowEl = $(e.target).parents("tr"); + const view = this; + const eventEl = $(e.target).parents("td"); + const rowEl = $(e.target).parents("tr"); - var parentId = rowEl.data("id"); - var children = "tr[data-packageid='" + parentId + "']"; + const parentId = rowEl.data("id"); + const children = `tr[data-packageid='${parentId}']`; this.$(children).each(function () { $(this).fadeOut(); - let childId = $(this).data("id"); - let grandchildren = "tr[data-parent^='" + childId + "']"; + const childId = $(this).data("id"); + const grandchildren = `tr[data-parent^='${childId}']`; $(grandchildren).fadeOut(); }); this.$(eventEl) .children(".d1package-collapse") - .fadeOut(function () { + .fadeOut(() => { view.$(eventEl).children(".d1package-expand").fadeIn(); view.$(".tooltip-this").tooltip(); }); @@ -837,27 +842,26 @@ /** * Check for private members and disable download buttons if necessary. - * * @since 2.28.0 */ - checkForPrivateMembers: function () { + checkForPrivateMembers() { try { - var packageModel = this.model, - packageCollection = this.dataPackage; + const packageModel = this.model; + const packageCollection = this.dataPackage; if (!packageModel || !packageCollection) { return; } - var numMembersFromSolr = packageModel.get("members").length, - numMembersFromRDF = packageCollection.length; + const numMembersFromSolr = packageModel.get("members").length; + const numMembersFromRDF = packageCollection.length; if (numMembersFromRDF > numMembersFromSolr) { - var downloadButtons = this.$(".btn.download"); + const downloadButtons = this.$(".btn.download"); - for (var i = 0; i < downloadButtons.length; i++) { - var btn = downloadButtons[i]; - var downloadURL = $(btn).attr("href"); + for (let i = 0; i < downloadButtons.length; i++) { + const btn = downloadButtons[i]; + const downloadURL = $(btn).attr("href"); if ( downloadURL.indexOf(packageModel.get("id")) > -1 || @@ -888,24 +892,23 @@ /** * Retrieves and processes nested packages for the current package. - * * @since 2.28.0 */ - getNestedPackages: function () { - var nestedPackages = new Array(); - var nestedPackageIds = new Array(); + getNestedPackages() { + const nestedPackages = new Array(); + const nestedPackageIds = new Array(); this.nestedPackages = nestedPackages; // get all the child packages for this resource map - var childPackages = this.dataPackage.filter(function (m) { - return m.get("formatType") === "RESOURCE"; - }); + const childPackages = this.dataPackage.filter( + (m) => m.get("formatType") === "RESOURCE", + ); // iterate over the list of child packages and add their members - for (var ite in childPackages) { - var childPkg = childPackages[ite]; + for (const ite in childPackages) { + const childPkg = childPackages[ite]; if (!nestedPackageIds.includes(childPkg.get("id"))) { - var nestedPackage = new PackageModel(); + const nestedPackage = new PackageModel(); nestedPackage.set("id", childPkg.get("id")); nestedPackage.setURL(); nestedPackage.getMembers(); @@ -924,65 +927,63 @@ /** * Adds a nested data package to the package table. - * - * @param {Object} dataPackage - The data package to be added. + * @param {object} dataPackage - The data package to be added. * @since 2.28.0 */ - addNestedPackages: function (dataPackage) { + addNestedPackages(dataPackage) { /** * Generates the table row for the data package header. * @type {null|Element} */ - var tableRow = null, - /** - * Reference to the current view. - * @type {Object} - */ - view = this, - /** - * The title of the data package. - * @type {null|string} - */ - title = null, - /** - * The URL of the data package. - * @type {null|string} - */ - packageUrl = null, - /** - * The URL of the nested data package. - * @type {null|string} - */ - nestedPackageUrl = null; + let tableRow = null; + /** + * Reference to the current view. + * @type {object} + */ + var view = this; + /** + * The title of the data package. + * @type {null|string} + */ + let title = null; + /** + * The URL of the data package. + * @type {null|string} + */ + let packageUrl = null; + /** + * The URL of the nested data package. + * @type {null|string} + */ + let nestedPackageUrl = null; /** * The members of the data package. - * * @type {Array} */ - var members = dataPackage.get("members"); + let members = dataPackage.get("members"); /** * Filters out metadata objects from the members. - * * @type {Array} */ - let metadataObj = _.filter(members, function (m) { - return m.get("type") == "Metadata" || m.get("type") == "metadata"; - }); + const metadataObj = _.filter( + members, + (m) => m.get("type") == "Metadata" || m.get("type") == "metadata", + ); title = metadataObj[0].get("title"); /** * The tooltip for the title (used for long titles). - * * @type {string} */ - let titleTooltip = title; + const titleTooltip = title; title = title.length > 150 - ? title.slice(0, 75) + - "..." + - title.slice(title.length - 75, title.length) + ? `${title.slice(0, 75)}...${title.slice( + title.length - 75, + title.length, + )}` : title; // Set the package URL @@ -993,18 +994,16 @@ // Set the nested package URL if (MetacatUI.root !== undefined && dataPackage.id !== undefined) - nestedPackageUrl = - MetacatUI.root + "/view/" + encodeURIComponent(dataPackage.id); + nestedPackageUrl = `${MetacatUI.root}/view/${encodeURIComponent(dataPackage.id)}`; /** * The HTML content for the data package header. - * * @type {string} */ tableRow = this.dataPackageHeaderTemplate({ id: dataPackage.id, - title: title, - titleTooltip: titleTooltip, + title, + titleTooltip, disablePackageDownloads: false, downloadUrl: packageUrl, disablePackageUrl: false, @@ -1024,26 +1023,24 @@ // Add the downloadButtonView el to the span this.$el - .find(".downloadAction[data-id='" + dataPackage.id + "']") + .find(`.downloadAction[data-id='${dataPackage.id}']`) .html(this.downloadButtonView.el); // Filter out the packages from the member list - members = _.filter(members, function (m) { - return m.type != "Package"; - }); + members = _.filter(members, (m) => m.type != "Package"); // Add each member to the package table view var view = this; - _.each(members, function (m) { + _.each(members, (m) => { // Update the size to bytes format - m.set({ size: m.bytesToSize(m.get("size")) }); + m.set({ size: Utilities.bytesToSize(m.get("size")) }); // Add each item of this nested package to the package table view view.addOne(m, dataPackage); }); }, - /*showDownloadProgress: function(e){ + /* showDownloadProgress: function(e){ e.preventDefault(); var button = $(e.target); @@ -1052,7 +1049,7 @@ return true; - }*/ + } */ }, ); return DataPackageView; diff --git a/src/js/views/DownloadButtonView.js b/src/js/views/DownloadButtonView.js index f35d06bcc..e97cef402 100644 --- a/src/js/views/DownloadButtonView.js +++ b/src/js/views/DownloadButtonView.js @@ -5,15 +5,15 @@ define([ "models/SolrResult", "models/DataONEObject", "models/PackageModel", -], function ($, _, Backbone, SolrResult, DataONEObject, PackageModel) { +], ($, _, Backbone, SolrResult, DataONEObject, PackageModel) => { "use strict"; - var DownloadButtonView = Backbone.View.extend({ + const DownloadButtonView = Backbone.View.extend({ tagName: "a", className: "btn download", - initialize: function (options) { + initialize(options) { if (!options) var options = {}; this.view = options.view || null; this.id = options.id || null; @@ -25,14 +25,14 @@ define([ click: "download", }, - render: function () { - var fileName = this.model.get("fileName") || ""; + render() { + let fileName = this.model.get("fileName") || ""; - if (typeof fileName == "string") { + if (typeof fileName === "string") { fileName = fileName.trim(); } - //Add the href and id attributes + // Add the href and id attributes let hrefLink = this.model.get("url"); if ( this.model instanceof DataONEObject && @@ -54,7 +54,7 @@ define([ .attr("data-id", this.model.get("id")) .attr("download", fileName); - //Check for CORS downloads. For CORS, the 'download' attribute may not work, + // Check for CORS downloads. For CORS, the 'download' attribute may not work, // so open in a new tab. if ( typeof hrefLink !== "undefined" && @@ -63,7 +63,7 @@ define([ this.$el.attr("target", "_blank"); } - //For packages + // For packages if (typeof this.view !== "undefined" && this.view == "actionsView") { this.$el.append( $(document.createElement("i")).addClass( @@ -77,7 +77,7 @@ define([ if (this.model.type == "Package") { this.$el.text("Download All").addClass("btn-primary"); - //if the Package Model has no Solr index document associated with it, then we + // if the Package Model has no Solr index document associated with it, then we // can assume the resource map object is private. So disable the download button. if (!this.model.get("indexDoc")) { this.$el @@ -93,18 +93,18 @@ define([ }); } } - //For individual DataONEObjects + // For individual DataONEObjects else { this.$el.text("Download"); } - //Add a download icon + // Add a download icon this.$el.append( $(document.createElement("i")).addClass("icon icon-cloud-download"), ); } - //If this is a Download All button for a package but it's too large, then disable the button with a message + // If this is a Download All button for a package but it's too large, then disable the button with a message if ( this.model.type == "Package" && this.model.getTotalSize() > MetacatUI.appModel.get("maxDownloadSize") @@ -128,12 +128,11 @@ define([ } }, - download: function (e) { + download(e) { // Checking if the Download All button is disabled because the package is too large - var isDownloadDisabled = + const isDownloadDisabled = !!( this.$el.attr("disabled") === "disabled" || this.$el.is(".disabled") - ? true - : false; + ); // Do nothing if the `disabled` attribute is set!. // If the download is already in progress, don't try to download again @@ -142,41 +141,38 @@ define([ return; } - //If the user isn't logged in, let the browser handle the download normally + // If the user isn't logged in, let the browser handle the download normally if ( MetacatUI.appUserModel.get("tokenChecked") && !MetacatUI.appUserModel.get("loggedIn") ) { return; } - //If the authentication hasn't been checked yet, wait for it - else if (!MetacatUI.appUserModel.get("tokenChecked")) { - var view = this; - this.listenTo( - MetacatUI.appUserModel, - "change:tokenChecked", - function () { - view.download(e); - }, - ); + // If the authentication hasn't been checked yet, wait for it + if (!MetacatUI.appUserModel.get("tokenChecked")) { + const view = this; + this.listenTo(MetacatUI.appUserModel, "change:tokenChecked", () => { + view.download(e); + }); return; } - //If the user is logged in but the object is public, download normally - else if (this.model.get("isPublic")) { - //If this is a "Download All" button for a package, and at least object is private, then + // If the user is logged in but the object is public, download normally + if (this.model.get("isPublic")) { + // If this is a "Download All" button for a package, and at least object is private, then // we need to download via XHR with credentials if (this.model.type == "Package") { - //If we found a private object, download the package via XHR so we can send the auth token. - var privateObject = _.find(this.model.get("members"), function (m) { - return m.get("isPublic") !== true; - }); - //If no private object is found, download normally. + // If we found a private object, download the package via XHR so we can send the auth token. + const privateObject = _.find( + this.model.get("members"), + (m) => m.get("isPublic") !== true, + ); + // If no private object is found, download normally. // This may still fail when there is a private object that the logged-in user doesn't have access to. if (!privateObject) { return; } } - //All other object types (data and metadata objects) can be downloaded normally + // All other object types (data and metadata objects) can be downloaded normally else { return; } @@ -184,9 +180,9 @@ define([ e.preventDefault(); - //Show that the download has started + // Show that the download has started this.$el.addClass("in-progress"); - var buttonHTML = this.$el.html(); + const buttonHTML = this.$el.html(); if (typeof this.view !== "undefined" && this.view == "actionsView") { this.$el.html( "", @@ -197,9 +193,9 @@ define([ ); } - var thisRef = this; + const thisRef = this; - this.listenToOnce(this.model, "downloadComplete", function () { + this.listenToOnce(this.model, "downloadComplete", () => { let iconEl = ""; let downloadEl = ""; @@ -208,27 +204,27 @@ define([ downloadEl = buttonHTML; } - //Show that the download is complete + // Show that the download is complete thisRef.$el .html(iconEl) .addClass("complete") .removeClass("in-progress error"); - //Put the download button back to normal - setTimeout(function () { - //After one second, change the background color with an animation + // Put the download button back to normal + setTimeout(() => { + // After one second, change the background color with an animation thisRef.$el.removeClass("complete").html(downloadEl); }, 2000); }); - this.listenToOnce(this.model, "downloadError", function () { + this.listenToOnce(this.model, "downloadError", () => { let iconEl = ""; if (thisRef.view != "actionsView") { iconEl += "Error "; } - //Show that the download failed to compelete. + // Show that the download failed to compelete. thisRef.$el .html(iconEl) .addClass("error") @@ -241,7 +237,7 @@ define([ }); }); - //Fire the download event via the SolrResult model + // Fire the download event via the SolrResult model this.model.downloadWithCredentials(); }, }); diff --git a/src/js/views/MdqRunView.js b/src/js/views/MdqRunView.js index 67f32577a..0306aa63e 100644 --- a/src/js/views/MdqRunView.js +++ b/src/js/views/MdqRunView.js @@ -1,3 +1,5 @@ +"use strict"; + define([ "jquery", "underscore", @@ -7,10 +9,10 @@ define([ "DonutChart", "views/CitationView", "text!templates/mdqRun.html", - "text!templates/mdqSuites.html", "text!templates/loading-metrics.html", "collections/QualityReport", -], function ( + "views/MarkdownView", +], ( $, _, Backbone, @@ -19,42 +21,73 @@ define([ DonutChart, CitationView, MdqRunTemplate, - SuitesTemplate, LoadingTemplate, QualityReport, -) { - "use strict"; + MarkdownView, +) => { + const MSG_ERROR_GENERATING_REPORT = + "There was an error generating the assessment report."; + const MSG_QUEUED_REPORT = + "The assessment report is in the Assessment Server queue to be generated."; + const MSG_REPORT_NOT_READY = + "The assessment report for this dataset is not ready yet. Try checking back in 24 hours to see these results."; + const MSG_ERROR_GENERAL = + "There was an error retrieving the assessment report for this dataset."; + const MSG_ERROR_DETAILS = "The Assessment Server reported this error: "; + const QUEUE_ERROR_DETAILS = " It was queued at: "; /** * @class MdqRunView * @classdesc A view that fetches and displays a Metadata Assessment Report * @classcategory Views * @name MdqRunView - * @extends Backbone.View + * @augments Backbone.View * @constructs */ - var MdqRunView = Backbone.View.extend( + const MdqRunView = Backbone.View.extend( /** @lends MdqRunView.prototype */ { + /** @inheritdoc */ el: "#Content", + /** @inheritdoc */ events: { "change #suiteId": "switchSuite", }, - url: null, + /** + * The identifier of the object to be assessed + * @type {string} + */ pid: null, + /** * The currently selected/requested suite * @type {string} */ suiteId: null, + /** * The list of all potential suites for this theme * @type {string[]} */ suiteIdList: [], + + /** + * The template to use to indicate that the view is loading + * @type {Function} + */ loadingTemplate: _.template(LoadingTemplate), + + /** + * The main template for this view + * @type {Function} + */ template: _.template(MdqRunTemplate), + + /** + * The selector for the element that will contain the breadcrumbs + * @type {string} + */ breadcrumbContainer: "#breadcrumb-container", /** @@ -65,13 +98,16 @@ define([ */ loadingContainer: "#mdqResult", - initialize: function () {}, - - switchSuite: function (event) { - var select = $(event.target); - var suiteId = $(select).val(); + /** + * Handles the event when the user selects a different suite + * @param {Event} event The event object + * @returns {boolean} False, to prevent the default action + */ + switchSuite(event) { + const select = $(event.target); + const suiteId = $(select).val(); MetacatUI.uiRouter.navigate( - "quality/s=" + suiteId + "/" + encodeURIComponent(this.pid), + `quality/s=${suiteId}/${encodeURIComponent(this.pid)}`, { trigger: false }, ); this.suiteId = suiteId; @@ -79,20 +115,20 @@ define([ return false; }, - render: function () { - var viewRef = this; + /** @inheritdoc */ + render() { + const viewRef = this; // The suite use for rendering can initially be set via the theme AppModel. // If a suite id is request via the metacatui route, then we have to display that // suite, and in addition have to display all possible suites for this theme in // a selection list, if the user wants to view a different one. + this.suiteIdList = MetacatUI.appModel.get("mdqSuiteIds"); if (!this.suiteId) { - this.suiteId = MetacatUI.appModel.get("mdqSuiteIds")[0]; + this.suiteId = this.suiteIdList?.[0]; } - this.suiteIdList = MetacatUI.appModel.get("mdqSuiteIds"); this.suiteLabels = MetacatUI.appModel.get("mdqSuiteLabels"); - //this.url = this.mdqRunsServiceUrl + "/" + this.suiteId + "/" + this.pid; // Insert the basic template this.$el.html(this.template({})); @@ -102,217 +138,364 @@ define([ this.showLoading(); if (!this.pid) { - var searchLink = $(document.createElement("a")) - .attr("href", MetacatUI.root + "/data") + const searchLink = $(document.createElement("a")) + .attr("href", `${MetacatUI.root}/data`) .text("Search our database"); - var message = $(document.createElement("span")) + const message = $(document.createElement("span")) .text(" to see an assessment report for a dataset") .prepend(searchLink); this.showMessage(message, true, false); return; } - // Fetch a quality report from the quality server and display it. - var qualityUrl = - MetacatUI.appModel.get("mdqRunsServiceUrl") + - viewRef.suiteId + - "/" + - viewRef.pid; - var qualityReport = new QualityReport([], { + const root = MetacatUI.appModel.get("mdqRunsServiceUrl"); + + const qualityUrl = `${root}${viewRef.suiteId}/${viewRef.pid}`; + const qualityReport = new QualityReport([], { url: qualityUrl, pid: viewRef.pid, }); + this.qualityReport = qualityReport; + + this.listenToOnce( + qualityReport, + "fetchError", + this.handleQualityReportError, + ); + this.listenToOnce( + qualityReport, + "fetchComplete", + this.renderQualityReport, + ); + qualityReport.fetch({ url: qualityUrl }); + }, - this.listenToOnce(qualityReport, "fetchError", function () { - // Inspect the results to see if a quality report was returned. - // If not, then submit a request to the quality engine to create the - // quality report for this pid/suiteId, and inform the user of this. - var msgText; - console.log("Error status: " + qualityReport.fetchResponse.status); - if (qualityReport.fetchResponse.status == 404) { - msgText = - "The assessment report for this dataset is not ready yet. Try checking back in 24 hours to see these results."; - } else { - msgText = - "There was an error retrieving the assessment report for this dataset."; - if ( - typeof qualityReport.fetchResponse.statusText !== "undefined" && - typeof qualityReport.fetchResponse.status !== "undefined" - ) { - if (qualityReport.fetchResponse.status != 0) - msgText += - "Error details: " + qualityReport.fetchResponse.statusText; + /** + * Render the quality report once it has been fetched + */ + async renderQualityReport() { + const viewRef = this; + const { qualityReport } = this; + if (qualityReport?.runStatus?.toUpperCase() !== "SUCCESS") { + this.handleQualityReportError(); + return; + } + viewRef.hideLoading(); + + // Filter out the checks with level 'METADATA', as these checks are intended + // to pass info to metadig-engine indexing (for search, faceting), and not intended for display. + qualityReport.reset( + _.reject(qualityReport.models, (model) => { + const check = model.get("check"); + if (check.level === "METADATA") { + return true; } + return false; + }), + ); + + const groupedResults = qualityReport.groupResults(qualityReport.models); + const groupedByType = qualityReport.groupByType(qualityReport.models); + + const checkCount = qualityReport.length; + const blueCount = groupedResults.BLUE?.length || 0; + const greenCount = groupedResults.GREEN?.length || 0; + const orangeCount = groupedResults.ORANGE?.length || 0; + const redCount = groupedResults.RED?.length || 0; + const extraRedText = + redCount > 0 ? " Please correct these issues." : ""; + const extraOrangeText = + orangeCount > 0 ? " Please review these warnings." : ""; + const totalPassable = checkCount - blueCount; + + const checkWord = (num) => (num === 1 ? "check" : "checks"); + const greenText = `Passed ${greenCount} ${checkWord(greenCount)} out of ${totalPassable} (excluding informational checks).`; + const orangeText = `Warning for ${orangeCount} ${checkWord(orangeCount)}. ${extraOrangeText}`; + const redText = `Failed ${redCount} ${checkWord(redCount)}. ${extraRedText}`; + const blueText = `${blueCount} informational ${checkWord(blueCount)}.`; + + const data = { + objectIdentifier: qualityReport.id, + suiteId: viewRef.suiteId, + suiteIdList: viewRef.suiteIdList, + suiteLabels: viewRef.suiteLabels, + timestamp: _.now(), + id: viewRef.pid, + groupedResults, + groupedByType, + checkCount, + greenText, + orangeText, + redText, + blueText, + }; + + viewRef.$el.html(viewRef.template(data)); + await viewRef.addCheckItems(groupedResults); + viewRef.insertBreadcrumbs(); + viewRef.drawScoreChart(qualityReport.models, groupedResults); + viewRef.showCitation(); + viewRef.show(); + viewRef.$(".popover-this").popover(); + }, + + /** + * Add the check result item els to the view + * @param {object} groupedResults - The results grouped by status + * @since 0.0.0 + */ + async addCheckItems(groupedResults) { + const viewRef = this; + + const types = { + GREEN: { + className: "pass", + iconClass: "icon-check-sign success", + headerClass: "success", + }, + ORANGE: { + className: "warn", + iconClass: "icon-exclamation", + headerClass: "warning", + }, + RED: { + className: "fail", + iconClass: "icon-remove", + headerClass: "danger", + }, + BLUE: { + className: "info-check", + iconClass: "icon-info", + headerClass: "info", + }, + }; + + Object.keys(types).forEach(async (type) => { + const { className, iconClass, headerClass } = types[type]; + const results = groupedResults[type]; + if (results) { + // Use `map` to handle promises + const itemEls = await Promise.all( + results.map(async (result) => + viewRef.createCheckItem(result, className, iconClass), + ), + ); + + // Join the resolved HTML strings and append them + viewRef + .$(`.list-group-item.${headerClass}`) + .after(itemEls.join("")); } - this.showMessage(msgText); - }), - this.listenToOnce(qualityReport, "fetchComplete", function () { - var msgText; - if (qualityReport.runStatus != "success") { - if (qualityReport.runStatus == "failure") { - msgText = - "There was an error generating the assessment report. The Assessment Server reported this error: " + - qualityReport.errorDescription; - } else if (qualityReport.runStatus == "queued") { - msgText = - "The assessment report is in the Assessment Server queue to be generated. It was queued at: " + - qualityReport.timestamp; - } else { - msgText = - "There was an error retrieving the assessment report."; - } - this.showMessage(msgText); - return; - } else { - viewRef.hideLoading(); + }); + }, + + /** + * Create a check item element + * @param {object} result - The check result + * @param {string} className - The class name for the check item + * @param {string} iconClass - The class + * @returns {string} The HTML for the check item + * @since 0.0.0 + */ + async createCheckItem(result, className, iconClass) { + const outputs = await this.getOutputHTML(result.get("output")); + + return ` +
  • + + + + ${outputs} + + + + + + + ${result.get("status")} + ${result.get("check").level} + ${result.get("check").type} + +
  • + `; + }, + + /** + * Get the HTML for the output + * @param {Array} outputs - The outputs from the quality service + * @returns {string} The HTML for the output + */ + async getOutputHTML(outputs) { + const outputHTMLs = await Promise.all( + outputs.map(async (output) => { + if (output?.type?.includes("image")) { + return ``; + } + if (output.type === "markdown") { + return this.getHTMLFromMarkdown(output.value); } + return `
    ${output.value}
    `; + }), + ); - // Filter out the checks with level 'METADATA', as these checks are intended - // to pass info to metadig-engine indexing (for search, faceting), and not intended for display. - qualityReport.reset( - _.reject(qualityReport.models, function (model) { - var check = model.get("check"); - if (check.level == "METADATA") { - return true; - } else { - return false; - } - }), - ); + return outputHTMLs.join(""); + }, - var groupedResults = qualityReport.groupResults( - qualityReport.models, - ); - var groupedByType = qualityReport.groupByType(qualityReport.models); - - var data = { - objectIdentifier: qualityReport.id, - suiteId: viewRef.suiteId, - suiteIdList: viewRef.suiteIdList, - suiteLabels: viewRef.suiteLabels, - groupedResults: groupedResults, - groupedByType: groupedByType, - timestamp: _.now(), - id: viewRef.pid, - checkCount: qualityReport.length, - }; - - viewRef.$el.html(viewRef.template(data)); - viewRef.insertBreadcrumbs(); - viewRef.drawScoreChart(qualityReport.models, groupedResults); - viewRef.showCitation(); - viewRef.show(); - viewRef.$(".popover-this").popover(); + /** + * Get the HTML from markdown + * @param {string} markdown - The markdown to convert to HTML + * @returns {Promise} A promise that resolves with the HTML + */ + getHTMLFromMarkdown(markdown) { + const markdownView = new MarkdownView({ + markdown, + showTOC: false, + }).render(); + + return new Promise((resolve) => { + this.listenToOnce(markdownView, "mdRendered", () => { + resolve(markdownView.el.innerHTML); }); + }); + }, + + /** + * Handles errors that occur when fetching the quality report + */ + handleQualityReportError() { + const { qualityReport } = this; + let status = + qualityReport.runStatus || qualityReport.fetchResponse?.status; + + if (typeof status === "string") { + status = status.toUpperCase(); + } + + const description = + qualityReport.errorDescription || + qualityReport.fetchResponse?.statusText || + ""; + const time = qualityReport.timestamp; + + const errorReport = description + ? `${MSG_ERROR_DETAILS}${description}` + : ""; + const queueTime = time ? `${QUEUE_ERROR_DETAILS} ${time}` : ""; + + let msgText = ""; + + if (status === "FAILURE") { + msgText = `${MSG_ERROR_GENERATING_REPORT}`; + if (errorReport) { + msgText += ` ${errorReport}`; + } + } else if (status === "QUEUED" || status === "PROCESSING") { + msgText = `${MSG_QUEUED_REPORT} `; + if (queueTime) { + msgText += ` ${queueTime}`; + } + } else if (status === 404) { + msgText = MSG_REPORT_NOT_READY; + } else { + msgText = MSG_ERROR_GENERAL; + if (errorReport) { + msgText += ` ${errorReport}`; + } + } + this.showMessage(msgText); }, /** * Updates the message in the loading image * @param {string} message The new message to display - * @param {boolean} [showHelp=true] If set to true, and an email contact is configured + * @param {boolean} [showHelp] If set to true, and an email contact is configured * in MetacatUI, then the contact email will be shown at the bottom of the message. - * @param {boolean} [showLink=true] If set to true, a link back to the dataset will be + * @param {boolean} [showLink] If set to true, a link back to the dataset will be * appended to the end of the message. * @since 2.15.0 */ - showMessage: function (message, showHelp = true, showLink = true) { - try { - var view = this; - var messageEl = this.loadingEl.find(".message"); + showMessage(message, showHelp = true, showLink = true) { + const view = this; + const messageEl = this.loadingEl.find(".message"); - if (!messageEl) { - return; - } + if (!messageEl) { + return; + } - // Update the message - messageEl.html(message); + // Update the message + messageEl.html(message); - // Create a link back to the data set - if (showLink) { - var viewURL = "/view/" + encodeURIComponent(this.pid); - var backLink = $(document.createElement("a")).text( - " Return to the dataset", - ); - backLink.on("click", function () { - view.hideLoading(); - MetacatUI.uiRouter.navigate(viewURL, { - trigger: true, - replace: true, - }); + // Create a link back to the data set + if (showLink) { + const viewURL = `/view/${encodeURIComponent(this.pid)}`; + const backLink = $(document.createElement("a")).text( + " Return to the dataset", + ); + backLink.on("click", () => { + view.hideLoading(); + MetacatUI.uiRouter.navigate(viewURL, { + trigger: true, + replace: true, }); - messageEl.append(backLink); - } + }); + messageEl.append(backLink); + } - // Show how the user can get more help - if (showHelp) { - var emailAddress = MetacatUI.appModel.get("emailContact"); - // Don't show help if there's no contact email configured - if (emailAddress) { - var helpEl = $( - "

    " + - "" + - "Need help? Email us at

    ", - ); - var emailLink = $(document.createElement("a")) - .attr("href", "mailto:" + emailAddress) - .text(emailAddress); - helpEl.append(emailLink); - messageEl.append(helpEl); - } + // Show how the user can get more help + if (showHelp) { + const emailAddress = MetacatUI.appModel.get("emailContact"); + // Don't show help if there's no contact email configured + if (emailAddress) { + const helpEl = $( + "

    " + + "" + + "Need help? Email us at

    ", + ); + const emailLink = $(document.createElement("a")) + .attr("href", `mailto:${emailAddress}`) + .text(emailAddress); + helpEl.append(emailLink); + messageEl.append(helpEl); } - } catch (error) { - console.log( - "There was an error showing a message in a MdqRunView" + - ". Error details: " + - error, - ); } }, /** * Render a loading image with message */ - showLoading: function () { - try { - var loadingEl = this.loadingTemplate({ - message: "Retrieving assessment report...", - character: "none", - type: "barchart", - }); - this.loadingEl = $(loadingEl); - this.$el.find(this.loadingContainer).html(this.loadingEl); - } catch (error) { - console.log( - "There was an error showing the loading image in a MdqRunView" + - ". Error details: " + - error, - ); - } + showLoading() { + const loadingEl = this.loadingTemplate({ + message: "Retrieving assessment report...", + character: "none", + type: "barchart", + }); + this.loadingEl = $(loadingEl); + this.$el.find(this.loadingContainer).html(this.loadingEl); }, /** * Remove the loading image and message. */ - hideLoading: function () { - try { - this.loadingEl.remove(); - } catch (error) { - console.log( - "There was an error hiding a loading image in a MdqRunView" + - ". Error details: " + - error, - ); - } + hideLoading() { + this.loadingEl.remove(); }, - showCitation: function () { - var solrResultModel = new SolrResult({ + /** Render a citation view for the object and display it in the view */ + showCitation() { + const solrResultModel = new SolrResult({ id: this.pid, }); - this.listenTo(solrResultModel, "sync", function () { - var citationView = new CitationView({ + this.listenTo(solrResultModel, "sync", () => { + const citationView = new CitationView({ model: solrResultModel, createLink: false, createTitleLink: true, @@ -325,15 +508,20 @@ define([ solrResultModel.getInfo(); }, - show: function () { - var view = this; + /** Show the view */ + show() { this.$el.hide(); this.$el.fadeIn({ duration: "slow" }); }, - drawScoreChart: function (results, groupedResults) { - var dataCount = results.length; - var data = [ + /** + * Draw a donut chart showing the distribution of checks by status + * @param {Array} results - The array of check results + * @param {object} groupedResults - The results grouped by status + */ + drawScoreChart(results, groupedResults) { + const dataCount = results.length; + const data = [ { label: "Pass", count: groupedResults.GREEN.length, @@ -356,95 +544,53 @@ define([ }, ]; - var svgClass = "data"; + const svgClass = "data"; - //If d3 isn't supported in this browser or didn't load correctly, insert a text title instead + // If d3 isn't supported in this browser or didn't load correctly, insert a text title instead if (!d3) { this.$(".format-charts-data").html( - "

    " + - MetacatUI.appView.commaSeparateNumber(dataCount) + - " data files

    ", + `

    ${MetacatUI.appView.commaSeparateNumber( + dataCount, + )} data files

    `, ); return; } - //Draw a donut chart - var donut = new DonutChart({ + // Draw a donut chart + const donut = new DonutChart({ id: "data-chart", - data: data, + data, total: dataCount, titleText: "checks", titleCount: dataCount, - svgClass: svgClass, + svgClass, countClass: "data", height: 250, width: 250, keepOrder: true, - formatLabel: function (name) { + formatLabel(name) { return name; }, }); this.$(".format-charts-data").html(donut.render().el); }, - insertBreadcrumbs: function () { - var breadcrumbs = $(document.createElement("ol")) - .addClass("breadcrumb") - .append( - $(document.createElement("li")) - .addClass("home") - .append( - $(document.createElement("a")) - .attr("href", MetacatUI.root ? MetacatUI.root : "/") - .addClass("home") - .text("Home"), - ), - ) - .append( - $(document.createElement("li")) - .addClass("search") - .append( - $(document.createElement("a")) - .attr( - "href", - MetacatUI.root + - "/data" + - (MetacatUI.appModel.get("page") > 0 - ? "/page/" + - (parseInt(MetacatUI.appModel.get("page")) + 1) - : ""), - ) - .addClass("search") - .text("Search"), - ), - ) - .append( - $(document.createElement("li")).append( - $(document.createElement("a")) - .attr( - "href", - MetacatUI.root + "/view/" + encodeURIComponent(this.pid), - ) - .addClass("inactive") - .text("Metadata"), - ), - ) - .append( - $(document.createElement("li")).append( - $(document.createElement("a")) - .attr( - "href", - MetacatUI.root + "/quality/" + encodeURIComponent(this.pid), - ) - .addClass("inactive") - .text("Assessment Report"), - ), - ); - - this.$(this.breadcrumbContainer).html(breadcrumbs); + /** + * Insert breadcrumbs into the view + */ + insertBreadcrumbs() { + const encodedPid = encodeURIComponent(this.pid); + const root = MetacatUI.root || "/"; + const breadcrumbs = ` + + `; + this.el.querySelector(this.breadcrumbContainer).innerHTML = breadcrumbs; }, }, ); diff --git a/src/js/views/MetadataIndexView.js b/src/js/views/MetadataIndexView.js index 518ff79ce..ce30354bd 100644 --- a/src/js/views/MetadataIndexView.js +++ b/src/js/views/MetadataIndexView.js @@ -3,25 +3,27 @@ define([ "underscore", "backbone", "gmaps", + "common/Utilities", "models/SolrResult", "views/DownloadButtonView", "text!templates/loading.html", "text!templates/alert.html", "text!templates/attribute.html", "text!templates/dataDisplay.html", -], function ( +], ( $, _, Backbone, gmaps, + Utilities, SolrResult, DownloadButtonView, LoadingTemplate, alertTemplate, AttributeTemplate, DataDisplayTemplate, -) { - var MetadataIndexView = Backbone.View.extend({ +) => { + const MetadataIndexView = Backbone.View.extend({ type: "MetadataIndex", id: "Metadata", @@ -44,9 +46,9 @@ define([ events: {}, - initialize: function (options) { + initialize(options) { this.pid = options.pid || null; - //this.el.id = this.id + "-" + this.pid; //Give this element a specific ID in case multiple MetadataIndex views are on one page + // this.el.id = this.id + "-" + this.pid; //Give this element a specific ID in case multiple MetadataIndex views are on one page this.parentView = options.parentView || null; // use these to tailor the annotation ui widget @@ -61,30 +63,29 @@ define([ }; }, - render: function () { + render() { if (!this.pid) return false; - var view = this; + const view = this; - //Get all the fields from the Solr index - var query = - 'q=(id:"' + - encodeURIComponent(this.pid) + - '"+OR+seriesId:"' + - encodeURIComponent(this.pid) + - '")&rows=1&start=0&fl=*&wt=json'; + // Get all the fields from the Solr index + const query = `q=(id:"${encodeURIComponent( + this.pid, + )}"+OR+seriesId:"${encodeURIComponent( + this.pid, + )}")&rows=1&start=0&fl=*&wt=json`; var requestSettings = { url: MetacatUI.appModel.get("queryServiceUrl") + query, - success: function (data, textStatus, xhr) { + success(data, textStatus, xhr) { try { if (!data?.response?.numFound) { if (view.parentView && view.parentView.model) { - //Show a "not indexed" message if there is system metadata but nothing in + // Show a "not indexed" message if there is system metadata but nothing in // the index if (view.parentView.model.get("systemMetadata")) { view.showNotIndexed(); } - //Show a "not found" message if there is no system metadata and no results in the index + // Show a "not found" message if there is no system metadata and no results in the index else { view.parentView.model.set("notFound", true); view.parentView.showNotFound(); @@ -95,8 +96,8 @@ define([ } else { view.docs = data.response.docs; - _.each(data.response.docs, function (doc, i, list) { - //If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. + _.each(data.response.docs, (doc, i, list) => { + // If this is a data object and there is a science metadata doc that describes it, then navigate to that Metadata View. if ( doc.formatType == "DATA" && doc.isDocumentedBy && @@ -104,26 +105,26 @@ define([ ) { view.onClose(); MetacatUI.uiRouter.navigate( - "view/" + doc.isDocumentedBy[0], + `view/${doc.isDocumentedBy[0]}`, true, ); return; } - var metadataEl = $(document.createElement("section")).attr( - "id", - "metadata-index-details", - ), - id = doc.id, - creator = doc.origin, - title = doc.title, - pubDate = doc.pubDate, - dateUploaded = doc.dateUploaded, - keys = Object.keys(doc), - docModel = new SolrResult(doc); - - //Extract General Info details that we want to list first - var generalInfoKeys = [ + const metadataEl = $(document.createElement("section")).attr( + "id", + "metadata-index-details", + ); + const { id } = doc; + const creator = doc.origin; + const { title } = doc; + const { pubDate } = doc; + const { dateUploaded } = doc; + let keys = Object.keys(doc); + const docModel = new SolrResult(doc); + + // Extract General Info details that we want to list first + const generalInfoKeys = [ "title", "id", "abstract", @@ -139,8 +140,8 @@ define([ ), ); - //Extract Spatial details - var spatialKeys = [ + // Extract Spatial details + const spatialKeys = [ "site", "southBoundCoord", "northBoundCoord", @@ -156,8 +157,8 @@ define([ ), ); - //Extract Temporal Coverage details - var temporalKeys = ["beginDate", "endDate"]; + // Extract Temporal Coverage details + const temporalKeys = ["beginDate", "endDate"]; keys = _.difference(keys, temporalKeys); $(metadataEl).append( view.formatAttributeSection( @@ -167,8 +168,8 @@ define([ ), ); - //Extract Taxonomic Coverage details - var taxonKeys = [ + // Extract Taxonomic Coverage details + const taxonKeys = [ "order", "phylum", "family", @@ -185,8 +186,8 @@ define([ ), ); - //Extract People details - var peopleKeys = [ + // Extract People details + const peopleKeys = [ "origin", "investigator", "contactOrganization", @@ -201,8 +202,8 @@ define([ ), ); - //Extract Access Control details - var accessKeys = [ + // Extract Access Control details + const accessKeys = [ "isPublic", "submitter", "rightsHolder", @@ -220,7 +221,7 @@ define([ ), ); - //Add the rest of the metadata + // Add the rest of the metadata $(metadataEl).append( view.formatAttributeSection(docModel, keys, "Other"), ); @@ -231,16 +232,14 @@ define([ }); } } catch (e) { - console.log("Error parsing Solr response: " + e); - console.log("Solr response: " + data); + console.log(`Error parsing Solr response: ${e}`); + console.log(`Solr response: ${data}`); view.parentView.showNotFound(); } }, - error: function () { - var msg = "

    Sorry, no dataset was found.

    "; - view.$el.html( - view.alertTemplate({ msg: msg, classes: "alert-danger" }), - ); + error() { + const msg = "

    Sorry, no dataset was found.

    "; + view.$el.html(view.alertTemplate({ msg, classes: "alert-danger" })); }, }; @@ -248,65 +247,64 @@ define([ _.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()), ); - //Send a request for the EML doc itself to extract certain info + // Send a request for the EML doc itself to extract certain info if (this.parentView && this.parentView.model) { - var formatId = this.parentView.model.get("formatId"); + const formatId = this.parentView.model.get("formatId"); if (formatId.indexOf("eml://") >= 0) { - var url = + const url = `${ MetacatUI.appModel.get("baseUrl") + MetacatUI.appModel.get("context") + - MetacatUI.appModel.get("d1Service") + - "/object/" + - encodeURIComponent(this.parentView.model.get("id")); + MetacatUI.appModel.get("d1Service") + }/object/${encodeURIComponent(this.parentView.model.get("id"))}`; var requestSettings = { - url: url, - success: function (data, textStatus, xhr) { + url, + success(data, textStatus, xhr) { if (!data || !$(data).length) return; - //Find the distribution information - var emlDoc = $(data) + // Find the distribution information + const emlDoc = $(data) .find("distribution") - .each(function (i, dist) { - var onlineDist = $(dist).children("online"); + .each((i, dist) => { + const onlineDist = $(dist).children("online"); if (onlineDist.length) { var linkText = $(onlineDist).text(); if (linkText.indexOf("ecogrid") >= 0) { - //Clean up the link text - var start = linkText.lastIndexOf("/"); - var ecogridPid = linkText.substr(start + 1).trim(), - dataObjects = []; + // Clean up the link text + const start = linkText.lastIndexOf("/"); + const ecogridPid = linkText.substr(start + 1).trim(); + let dataObjects = []; - //Iterate over each id in the package and try to fuzzily match the ecogrid link to the id + // Iterate over each id in the package and try to fuzzily match the ecogrid link to the id if (view.parentView.packageModels) { - //Get all the data objects in this metadata's packages - _.each(view.parentView.packageModels, function (pckg) { + // Get all the data objects in this metadata's packages + _.each(view.parentView.packageModels, (pckg) => { dataObjects.push(pckg.get("members")); }); dataObjects = _.flatten(dataObjects); } for (var i = 0; i < dataObjects.length; i++) { - //If we find a match, replace the ecogrid links with a DataONE API link to the object + // If we find a match, replace the ecogrid links with a DataONE API link to the object if (dataObjects[i].get("id").indexOf(ecogridPid) > -1) { var linkText = dataObjects[i].get("url"); - //We can stop looking now + // We can stop looking now i = dataObjects.length; } } } - var link = $(document.createElement("a")) - .attr("href", linkText) - .text(linkText), - fullHTML = view.formatAttribute( - "Online Distribution Info", - link, - ); + const link = $(document.createElement("a")) + .attr("href", linkText) + .text(linkText); + const fullHTML = view.formatAttribute( + "Online Distribution Info", + link, + ); - //Find the "General" section of this page + // Find the "General" section of this page if (view.$(".General").length) view.$(".General").after(fullHTML); else view.$el.append(fullHTML); @@ -327,7 +325,7 @@ define([ return this; }, - formatAttributeSection: function (doc, keys, title, className) { + formatAttributeSection(doc, keys, title, className) { if (keys.length == 0) return ""; if (typeof title === "string") { @@ -341,56 +339,57 @@ define([ var titleText = titleHTML.text(); } - var html = "", - sectionClass = - typeof className === "undefined" - ? titleText.replace(/ /g, "") - : className, - view = this, - populated = false; + let html = ""; + const sectionClass = + typeof className === "undefined" + ? titleText.replace(/ /g, "") + : className; + const view = this; + let populated = false; - _.each(keys, function (key, keyNum, list) { + _.each(keys, (key, keyNum, list) => { if (typeof key === "object" && doc.get(key.field)) { html += view.formatAttribute(key.display, doc.get(key.field)); populated = true; - } else if (doc.get(key)) { + } else if (doc.get(key) || doc.get(key) === 0) { html += view.formatAttribute(key, doc.get(key)); populated = true; } }); if (populated) { - var section = $(document.createElement("section")) + const section = $(document.createElement("section")) .addClass(sectionClass) .append(titleHTML) .append(html); return section; - } else return null; + } + return null; }, - formatAttribute: function (attribute, value) { - var html = "", - view = this, - embeddedAttributes = "", - type = "sem_annotation"; + formatAttribute(attribute, value) { + let html = ""; + const view = this; + let embeddedAttributes = ""; + let type = "sem_annotation"; // see if there is special handling for this field if (this.semanticFields[attribute]) { type = this.semanticFields[attribute]; } - //If this is a multi-valued field from Solr, the attribute value is actually multiple embedded attribute templates - var numAttributes = + // If this is a multi-valued field from Solr, the attribute value is actually multiple embedded attribute templates + const numAttributes = Array.isArray(value) && value.length > 1 ? value.length : 0; - for (var i = 0; i < numAttributes; i++) { + for (let i = 0; i < numAttributes; i++) { embeddedAttributes += view.attributeTemplate({ attribute: "", formattedAttribute: view.transformCamelCase(attribute), value: value[i].toString(), - id: attribute + "_" + (i + 1), - type: type, - resource: "#xpointer(//" + attribute + "[" + (i + 1) + "])", + id: `${attribute}_${i + 1}`, + type, + resource: `#xpointer(//${attribute}[${i + 1}])`, }); } @@ -399,45 +398,48 @@ define([ } html += view.attributeTemplate({ - attribute: attribute, + attribute, formattedAttribute: view.transformCamelCase(attribute), value: embeddedAttributes || value.toString(), id: attribute, - type: type, - resource: "#xpointer(//" + attribute + ")", + type, + resource: `#xpointer(//${attribute})`, }); return html; }, - transformCamelCase: function (string) { - var result = string + transformCamelCase(string) { + const result = string .replace(/([A-Z]+)/g, " $1") .replace(/([A-Z][a-z])/g, " $1"); - var finalResult = result.charAt(0).toUpperCase() + result.slice(1); + const finalResult = result.charAt(0).toUpperCase() + result.slice(1); return finalResult; }, - insertDataDetails: function () { - var view = this; + insertDataDetails() { + const view = this; - //Get the Package Model - it is attached with the parent Metadata View - var pkg = this.parentView.packageModel; + // Get the Package Model - it is attached with the parent Metadata View + const pkg = this.parentView.packageModel; if (!pkg) return; if (pkg.get("members").length <= 1) return; - //Start some html - var html = $(document.createElement("section")); + // Start some html + const html = $(document.createElement("section")); - _.each(pkg.get("members"), function (solrResult, i) { + _.each(pkg.get("members"), (solrResult, i) => { if (solrResult.get("formatType") != "DATA") return; - solrResult.set("formattedSize", solrResult.bytesToSize()); + solrResult.set( + "formattedSize", + Utilities.bytesToSize(solrResult.get("size")), + ); - //Add a section for the data details, just like the other attribute sections - var keys = [ + // Add a section for the data details, just like the other attribute sections + const keys = [ "id", { field: "formattedSize", display: "size" }, "views", @@ -446,42 +448,42 @@ define([ "formatId", ]; - //Determine the icon type based on format id - var type = solrResult.getType(), - icon = ""; + // Determine the icon type based on format id + const type = solrResult.getType(); + var icon = ""; if (type == "program") icon = "icon-code"; else if (type == "metadata") icon = "icon-file-text"; else if (type == "image") icon = "icon-picture"; else if (type == "pdf") icon = "icon-file pdf"; else icon = "icon-table"; - var icon = $(document.createElement("i")).addClass(icon), - title = $(document.createElement("span")) - .text(solrResult.get("id")) - .addClass("title"), - downloadBtn = new DownloadButtonView({ model: solrResult }), - anchor = $(document.createElement("a")).attr( - "name", - encodeURIComponent(solrResult.get("id")), - ), - header = $(document.createElement("h4")) - .append(anchor) - .append(icon) - .append(title) - .append(downloadBtn.render().el); - - //Create the section - var entityDetailsSection = view + var icon = $(document.createElement("i")).addClass(icon); + const title = $(document.createElement("span")) + .text(solrResult.get("id")) + .addClass("title"); + const downloadBtn = new DownloadButtonView({ model: solrResult }); + const anchor = $(document.createElement("a")).attr( + "name", + encodeURIComponent(solrResult.get("id")), + ); + const header = $(document.createElement("h4")) + .append(anchor) + .append(icon) + .append(title) + .append(downloadBtn.render().el); + + // Create the section + const entityDetailsSection = view .formatAttributeSection(solrResult, keys, header, "entitydetails") .attr("data-id", solrResult.get("id")); - //Create an image thumbnail, if this is an image + // Create an image thumbnail, if this is an image if (type == "image") { - //var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); - //$(entityDetailsSection).prepend(thumbnail); + // var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); + // $(entityDetailsSection).prepend(thumbnail); } - //Mark this section with an anchor tag with the doc id + // Mark this section with an anchor tag with the doc id $(entityDetailsSection).prepend( $(document.createElement("a")).attr( "id", @@ -492,19 +494,19 @@ define([ $(html).append(entityDetailsSection); }); - //Glue together the header and attribute info section - var header = $(document.createElement("h4")).text( + // Glue together the header and attribute info section + const header = $(document.createElement("h4")).text( "Data Table, Image, and Other Data Details", ); - var section = $(html).prepend(header); + const section = $(html).prepend(header); - //Insert into the DOM right after the "general" information + // Insert into the DOM right after the "general" information this.$(".General").after(section); }, - //Shows a message to the user that indicates this object has not been indexed - showNotIndexed: function () { - var message = this.alertTemplate({ + // Shows a message to the user that indicates this object has not been indexed + showNotIndexed() { + const message = this.alertTemplate({ classes: "alert-warning", msg: "

    There is limited information about this content.

    " + @@ -515,44 +517,44 @@ define([ }); this.$el.append(message); - //If this metadata doc is not indexed, we need to search the system metadata - //to see if it is publicly accessible. + // If this metadata doc is not indexed, we need to search the system metadata + // to see if it is publicly accessible. if (this.parentView && this.parentView.model) { - //Get the system metadata string - var sysMeta = this.parentView.model.get("systemMetadata"); + // Get the system metadata string + let sysMeta = this.parentView.model.get("systemMetadata"); if (sysMeta) { - //Parse it into XML nodes + // Parse it into XML nodes sysMeta = $.parseXML(sysMeta); - //Find the allow permission for the public - var publicPermission = $(sysMeta).find( + // Find the allow permission for the public + const publicPermission = $(sysMeta).find( "allow subject:contains('public')", ); if (publicPermission.length) { - //Remove the "private" icon + // Remove the "private" icon $("#metadata-controls-container .private").remove(); } } - //If there is no system metadata, default to hiding the private icon + // If there is no system metadata, default to hiding the private icon else { $("#metadata-controls-container .private").remove(); } } }, - flagComplete: function () { + flagComplete() { this.complete = true; this.trigger("complete"); }, - onClose: function () { + onClose() { this.$el.html(this.loadingTemplate()); this.pid = null; - //Detach this view from its parent view + // Detach this view from its parent view this.parentView.subviews = _.without(this.parentView.subviews, this); this.parentView = null; - //Remove listeners + // Remove listeners this.stopListening(); }, }); diff --git a/src/js/views/PackageTableView.js b/src/js/views/PackageTableView.js index 96ce136b5..bbb934c23 100644 --- a/src/js/views/PackageTableView.js +++ b/src/js/views/PackageTableView.js @@ -2,10 +2,11 @@ define([ "jquery", "underscore", "backbone", + "common/Utilities", "models/PackageModel", "views/DownloadButtonView", "text!templates/downloadContents.html", -], function ($, _, Backbone, Package, DownloadButtonView, Template) { +], function ($, _, Backbone, Utilities, Package, DownloadButtonView, Template) { "use strict"; var PackageTable = Backbone.View.extend({ @@ -401,7 +402,8 @@ define([ //File size cell var sizeCell = $(document.createElement("td")).addClass("size"); - var size = memberModel.bytesToSize(); + var size = Utilities.bytesToSize(memberModel.get("size")); + memberModel.set("sizeStr", size); $(sizeCell).text(size); $(tr).append(sizeCell); diff --git a/src/js/views/StatsView.js b/src/js/views/StatsView.js index e655ca2a4..fb394eaab 100644 --- a/src/js/views/StatsView.js +++ b/src/js/views/StatsView.js @@ -8,6 +8,7 @@ define([ "DonutChart", "CircleBadge", "collections/Citations", + "common/Utilities", "models/MetricsModel", "models/Stats", "MetricsChart", @@ -27,6 +28,7 @@ define([ DonutChart, CircleBadge, Citations, + Utilities, MetricsModel, StatsModel, MetricsChart, @@ -923,12 +925,13 @@ define([ displayTotalSize: function () { var className = "quick-stats-count"; var count = ""; + var view = this; if (!this.model.get("totalSize")) { count = "0 bytes"; className += " no-activity"; } else { - count = this.bytesToSize(this.model.get("totalSize")); + count = Utilities.bytesToSize(view.model.get("totalSize")); } var countEl = $(document.createElement("p")) @@ -1103,36 +1106,6 @@ define([ } }, - /** - * Convert number of bytes into human readable format - * - * @param integer bytes Number of bytes to convert - * @param integer precision Number of digits after the decimal separator - * @return string - */ - bytesToSize: function (bytes, precision) { - var kibibyte = 1024; - var mebibyte = kibibyte * 1024; - var gibibyte = mebibyte * 1024; - var tebibyte = gibibyte * 1024; - - if (typeof bytes === "undefined") var bytes = this.get("size"); - - if (bytes >= 0 && bytes < kibibyte) { - return bytes + " B"; - } else if (bytes >= kibibyte && bytes < mebibyte) { - return (bytes / kibibyte).toFixed(precision) + " KiB"; - } else if (bytes >= mebibyte && bytes < gibibyte) { - return (bytes / mebibyte).toFixed(precision) + " MiB"; - } else if (bytes >= gibibyte && bytes < tebibyte) { - return (bytes / gibibyte).toFixed(precision) + " GiB"; - } else if (bytes >= tebibyte) { - return (bytes / tebibyte).toFixed(precision) + " TiB"; - } else { - return bytes + " B"; - } - }, - renderUsageMetricsError: function () { var message = "

    This might take some time. Check back in 24 hours to see these results.

    "; diff --git a/test/js/specs/unit/common/Utilities.spec.js b/test/js/specs/unit/common/Utilities.spec.js index 61db30807..b3d5aff8f 100644 --- a/test/js/specs/unit/common/Utilities.spec.js +++ b/test/js/specs/unit/common/Utilities.spec.js @@ -115,4 +115,44 @@ define(["../../../../../../src/js/common/Utilities"], function (EntityUtils) { }); }); }); + + describe("Converting bytes to human-readable size", function () { + it("should handle undefined bytes", function () { + const result = EntityUtils.bytesToSize(undefined, 2); + expect(result).to.equal("0 B"); + }); + + it("should handle bytes less than 1 KiB", function () { + const result = EntityUtils.bytesToSize(512, 2); + expect(result).to.equal("512 B"); + }); + + it("should convert bytes to KiB with precision", function () { + const result = EntityUtils.bytesToSize(2048, 2); + expect(result).to.equal("2.00 KiB"); + }); + + it("should convert bytes to MiB with precision", function () { + const result = EntityUtils.bytesToSize(2 * 1024 * 1024, 3); + expect(result).to.equal("2.000 MiB"); + }); + + it("should convert bytes to GiB with precision", function () { + const result = EntityUtils.bytesToSize(2 * 1024 * 1024 * 1024, 4); + expect(result).to.equal("2.0000 GiB"); + }); + + it("should convert bytes to TiB with precision", function () { + const result = EntityUtils.bytesToSize(2 * 1024 * 1024 * 1024 * 1024, 5); + expect(result).to.equal("2.00000 TiB"); + }); + + it("should handle very large bytes", function () { + const result = EntityUtils.bytesToSize( + 2 * 1024 * 1024 * 1024 * 1024 * 1024, + 2, + ); + expect(result).to.equal("2048.00 TiB"); + }); + }); }); diff --git a/test/js/specs/unit/models/SolrResult.spec.js b/test/js/specs/unit/models/SolrResult.spec.js index d67113870..b67f00a03 100644 --- a/test/js/specs/unit/models/SolrResult.spec.js +++ b/test/js/specs/unit/models/SolrResult.spec.js @@ -22,45 +22,5 @@ define(["../../../../../../../../src/js/models/SolrResult"], function ( solrResult.should.be.instanceof(SolrResult); }); }); - - describe("Converting bytes to human-readable size", function () { - it("should handle undefined bytes", function () { - const result = solrResult.bytesToSize(undefined, 2); - expect(result).to.equal("0 B"); - }); - - it("should handle bytes less than 1 KiB", function () { - const result = solrResult.bytesToSize(512, 2); - expect(result).to.equal("512 B"); - }); - - it("should convert bytes to KiB with precision", function () { - const result = solrResult.bytesToSize(2048, 2); - expect(result).to.equal("2.00 KiB"); - }); - - it("should convert bytes to MiB with precision", function () { - const result = solrResult.bytesToSize(2 * 1024 * 1024, 3); - expect(result).to.equal("2.000 MiB"); - }); - - it("should convert bytes to GiB with precision", function () { - const result = solrResult.bytesToSize(2 * 1024 * 1024 * 1024, 4); - expect(result).to.equal("2.0000 GiB"); - }); - - it("should convert bytes to TiB with precision", function () { - const result = solrResult.bytesToSize(2 * 1024 * 1024 * 1024 * 1024, 5); - expect(result).to.equal("2.00000 TiB"); - }); - - it("should handle very large bytes", function () { - const result = solrResult.bytesToSize( - 2 * 1024 * 1024 * 1024 * 1024 * 1024, - 2, - ); - expect(result).to.equal("2048.00 TiB"); - }); - }); }); });