-
-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
File Hierarchy clean up #2496
File Hierarchy clean up #2496
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
eslint
Unexpected unnamed function.
metacatui/src/js/models/DataONEObject.js
Line 139 in 554eadc
this.on("change:size", function () { |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/models/DataONEObject.js
Lines 139 to 142 in 554eadc
this.on("change:size", function () { | |
var size = Utilities.bytesToSize(model.get("size")); | |
model.set("sizeStr", size); | |
}); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/models/DataONEObject.js
Line 140 in 554eadc
var size = Utilities.bytesToSize(model.get("size")); |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/models/DataONEObject.js
Line 144 in 554eadc
var size = Utilities.bytesToSize(model.get("size")); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/models/DataONEObject.js
Line 144 in 554eadc
var size = Utilities.bytesToSize(model.get("size")); |
🚫 [eslint] <eqeqeq> reported by reviewdog 🐶
Expected '===' and instead saw '=='.
metacatui/src/js/models/DataONEObject.js
Line 149 in 554eadc
if (this.type == "DataONEObject") |
🚫 [eslint] <block-scoped-var> reported by reviewdog 🐶
'totalSize' declared on line 1807 column 15 is used outside of binding context.
metacatui/src/js/models/PackageModel.js
Line 1816 in 554eadc
this.set("totalSize", totalSize); |
🚫 [eslint] <block-scoped-var> reported by reviewdog 🐶
'totalSize' declared on line 1809 column 15 is used outside of binding context.
metacatui/src/js/models/PackageModel.js
Line 1816 in 554eadc
this.set("totalSize", totalSize); |
🚫 [eslint] <block-scoped-var> reported by reviewdog 🐶
'totalSize' declared on line 1807 column 15 is used outside of binding context.
metacatui/src/js/models/PackageModel.js
Line 1817 in 554eadc
return totalSize; |
🚫 [eslint] <block-scoped-var> reported by reviewdog 🐶
'totalSize' declared on line 1809 column 15 is used outside of binding context.
metacatui/src/js/models/PackageModel.js
Line 1817 in 554eadc
return totalSize; |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/models/SolrResult.js
Lines 846 to 861 in 554eadc
parseResourceMapField: function (json) { | |
if (typeof json.resourceMap == "string") { | |
return json.resourceMap.trim(); | |
} else if (Array.isArray(json.resourceMap)) { | |
let newResourceMapIds = []; | |
_.each(json.resourceMap, function (rMapId) { | |
if (typeof rMapId == "string") { | |
newResourceMapIds.push(rMapId.trim()); | |
} | |
}); | |
return newResourceMapIds; | |
} | |
//If nothing works so far, return an empty array | |
return []; | |
}, |
🚫 [eslint] <spaced-comment> reported by reviewdog 🐶
Expected exception block, space or tab after '//' in comment.
metacatui/src/js/models/SolrResult.js
Line 859 in 554eadc
//If nothing works so far, return an empty array |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/DataPackageView.js
Lines 18 to 1061 in 554eadc
], function ( | |
$, | |
_, | |
Backbone, | |
LocalForage, | |
DataPackage, | |
Utilities, | |
DataONEObject, | |
PackageModel, | |
ScienceMetadata, | |
EML211, | |
Package, | |
DataItemView, | |
DownloadButtonView, | |
DataPackageTemplate, | |
DataPackageStartTemplate, | |
DataPackageHeaderTemplate, | |
) { | |
"use strict"; | |
/** | |
* @class DataPackageView | |
* @classdesc The main view of a Data Package in MetacatUI. The view is | |
* a file/folder browser | |
* @classcategory Views | |
* @screenshot views/DataPackageView.png | |
* @extends Backbone.View | |
*/ | |
var DataPackageView = Backbone.View.extend( | |
/** @lends DataPackageView.prototype */ { | |
type: "DataPackage", | |
tagName: "table", | |
className: "table table-striped table-hover", | |
id: "data-package-table", | |
events: { | |
"click .toggle-rows": "toggleRows", // Show/hide rows associated with event's metadata row | |
"click .message-row .addFiles": "handleAddFiles", | |
"click .expand-control": "expand", | |
"click .collapse-control": "collapse", | |
"click .d1package-expand": "expandAll", | |
"click .d1package-collapse": "collapseAll", | |
}, | |
subviews: {}, | |
/** | |
* A reference to the parent EditorView that contains this view | |
* @type EditorView | |
* @since 2.15.0 | |
*/ | |
parentEditorView: null, | |
template: _.template(DataPackageTemplate), | |
startMessageTemplate: _.template(DataPackageStartTemplate), | |
dataPackageHeaderTemplate: _.template(DataPackageHeaderTemplate), | |
// Models waiting for their parent folder to be rendered, hashed by parent id: | |
// {'parentid': [model1, model2, ...]} | |
delayedModels: {}, | |
/* Flag indicating the open or closed state of the package rows */ | |
isOpen: true, | |
initialize: function (options) { | |
if (options === undefined || !options) var options = {}; | |
if (!options.edit) { | |
//The edit option will allow the user to edit the table | |
this.edit = options.edit || false; | |
this.mode = "view"; | |
this.packageId = options.packageId || null; | |
this.memberId = options.memberId || null; | |
this.attributes = options.attributes || null; | |
this.dataPackage = options.dataPackage || new DataPackage(); | |
this.dataEntities = options.dataEntities || new Array(); | |
this.disablePackageDownloads = | |
options.disablePackageDownloads || false; | |
this.currentlyViewing = options.currentlyViewing || null; | |
this.parentEditorView = options.parentView || null; | |
this.title = options.title || ""; | |
this.packageTitle = options.packageTitle || ""; | |
this.nested = | |
typeof options.nested === "undefined" ? false : options.nested; | |
this.metricsModel = options.metricsModel; | |
// set the package model | |
this.packageModel = this.dataPackage.packageModel; | |
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 | |
this.edit = options.edit || false; | |
this.mode = "edit"; | |
//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 | |
else if (!this.dataPackage) { | |
this.dataPackage = new DataPackage(); | |
} | |
return this; | |
} | |
}, | |
/** | |
* Render the DataPackage HTML | |
*/ | |
render: function () { | |
this.$el.addClass("download-contents table-condensed"); | |
this.$el.append( | |
this.template({ | |
edit: this.edit, | |
dataPackageFiltering: | |
MetacatUI.appModel.get("dataPackageFiltering") || false, | |
dataPackageSorting: | |
MetacatUI.appModel.get("dataPackageSorting") || false, | |
loading: MetacatUI.appView.loadingTemplate({ | |
msg: "Loading files table... ", | |
}), | |
id: this.dataPackage.get("id"), | |
title: this.title || "Files in this dataset", | |
classes: "download-contents table-striped table-condensed table", | |
}), | |
); | |
if (this.edit) { | |
// Listen for add events because models are being merged | |
this.listenTo(this.dataPackage, "add", this.addOne); | |
this.listenTo(this.dataPackage, "fileAdded", this.addOne); | |
} | |
// Render the current set of models in the DataPackage | |
this.addAll(); | |
if (this.edit) { | |
//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(); | |
this.$("tbody").append(messageRow); | |
this.listenTo(this.dataPackage, "add", function () { | |
this.$(".message-row").remove(); | |
}); | |
} | |
//Render the Share control(s) | |
this.renderShareControl(); | |
} else { | |
// check for nessted datasets | |
if (this.nested) { | |
this.getNestedPackages(); | |
} | |
} | |
return this; | |
}, | |
/** | |
* Add a single DataItemView row to the DataPackageView | |
*/ | |
addOne: function (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 data package | |
if ( | |
item.get("formatType") == "RESOURCE" || | |
item.get("type") == "DataPackage" | |
) { | |
return; | |
} | |
var dataItemView, scimetaParent, parentRow, delayed_models; | |
if (_.contains(Object.keys(this.subviews), item.id)) { | |
return false; // Don't double render | |
} | |
var itemPath = null, | |
view = this; | |
if (!_.isEmpty(this.atLocationObj)) { | |
itemPath = this.atLocationObj[item.get("id")]; | |
if (itemPath[0] != "/") { | |
itemPath = "/" + itemPath; | |
} | |
} | |
// get the data package id | |
if (typeof dataPackage !== "undefined") { | |
var dataPackageId = dataPackage.id; | |
} | |
if (typeof dataPackageId === "undefined") | |
dataPackageId = this.dataPackage.id; | |
var insertInfoIcon = this.edit | |
? false | |
: view.dataEntities.includes(item.id); | |
dataItemView = new DataItemView({ | |
model: item, | |
metricsModel: this.metricsModel, | |
itemPath: itemPath, | |
insertInfoIcon: insertInfoIcon, | |
currentlyViewing: this.currentlyViewing, | |
mode: this.mode, | |
parentEditorView: this.parentEditorView, | |
dataPackageId: dataPackageId, | |
}); | |
this.subviews[item.id] = dataItemView; // keep track of all views | |
if (this.edit) { | |
//Get the science metadata that documents this item | |
scimetaParent = item.get("isDocumentedBy"); | |
//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 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 | |
else { | |
scimetaParent = scimetaParent[0]; | |
} | |
if ( | |
scimetaParent == item.get("id") || | |
(!scimetaParent && item.get("type") == "Metadata") | |
) { | |
// This is a metadata folder row, append it to the table | |
this.$el.append(dataItemView.render().el); | |
// Render any delayed models if this is the parent | |
if (_.contains(Object.keys(this.delayedModels), dataItemView.id)) { | |
delayed_models = this.delayedModels[dataItemView.id]; | |
_.each(delayed_models, this.addOne, this); | |
} | |
} else { | |
// Find the parent row by it's id, stored in a custom attribute | |
if (scimetaParent) | |
parentRow = this.$("[data-id='" + scimetaParent + "']"); | |
if (typeof parentRow !== "undefined" && parentRow.length) { | |
// This is a data row, insert below it's metadata parent folder | |
parentRow.after(dataItemView.render().el); | |
// 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); | |
delayed_models = delayed_models.splice(index, 1); | |
// Put the shortened array back if delayed models remains | |
if (delayed_models.length > 0) { | |
this.delayedModels[scimetaParent] = delayed_models; | |
} else { | |
this.delayedModels[scimetaParent] = undefined; | |
} | |
} | |
this.trigger("addOne"); | |
} else { | |
console.warn( | |
"Couldn't render " + | |
item.id + | |
". Delayed until parent is rendered.", | |
); | |
// Postpone the data row until the parent is rendered | |
delayed_models = this.delayedModels[scimetaParent]; | |
// Delay the model rendering if it isn't already delayed | |
if (typeof delayed_models !== "undefined") { | |
if (!_.contains(delayed_models, item)) { | |
delayed_models.push(item); | |
this.delayedModels[scimetaParent] = delayed_models; | |
} | |
} else { | |
delayed_models = []; | |
delayed_models.push(item); | |
this.delayedModels[scimetaParent] = delayed_models; | |
} | |
} | |
} | |
} else { | |
// This is a metadata folder row, append it to the table | |
this.$el.append(dataItemView.render().el); | |
this.trigger("addOne"); | |
} | |
}, | |
/** | |
* Render the Data Package View and insert it into this view | |
*/ | |
renderDataPackage: function () { | |
var view = this; | |
if (MetacatUI.rootDataPackage.packageModel.isNew()) { | |
view.renderMember(this.model); | |
} | |
// As the root collection is updated with models, render the UI | |
this.listenTo(MetacatUI.rootDataPackage, "add", function (model) { | |
if (!model.get("synced") && model.get("id")) | |
this.listenTo(model, "sync", view.renderMember); | |
else if (model.get("synced")) view.renderMember(model); | |
//Listen for changes on this member | |
model.on("change:fileName", model.addToUploadQueue); | |
}); | |
//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"); | |
$packageTableContainer.html(this.dataPackageView.render().el); | |
//Make the view resizable on the bottom | |
var handle = $(document.createElement("div")) | |
.addClass("ui-resizable-handle ui-resizable-s") | |
.attr("title", "Drag to resize") | |
.append( | |
$(document.createElement("i")).addClass("icon icon-caret-down"), | |
); | |
$packageTableContainer.after(handle); | |
$packageTableContainer.resizable({ | |
handles: { s: handle }, | |
minHeight: 100, | |
maxHeight: 900, | |
resize: function () { | |
view.emlView.resizeTOC(); | |
}, | |
}); | |
var tableHeight = ($(window).height() - $("#Navbar").height()) * 0.4; | |
$packageTableContainer.css("height", tableHeight + "px"); | |
var table = this.dataPackageView.$el; | |
this.listenTo(this.dataPackageView, "addOne", function () { | |
if ( | |
table.outerHeight() > $packageTableContainer.outerHeight() && | |
table.outerHeight() < 220 | |
) { | |
$packageTableContainer.css( | |
"height", | |
table.outerHeight() + handle.outerHeight(), | |
); | |
if (this.emlView) this.emlView.resizeTOC(); | |
} | |
}); | |
if (this.emlView) this.emlView.resizeTOC(); | |
//Save the view as a subview | |
this.subviews.push(this.dataPackageView); | |
this.listenTo( | |
MetacatUI.rootDataPackage.packageModel, | |
"change:childPackages", | |
this.renderChildren, | |
); | |
}, | |
/** | |
* Add all rows to the DataPackageView | |
*/ | |
addAll: function () { | |
this.$el.find("#data-package-table-body").html(""); // clear the table first | |
this.dataPackage.sort(); | |
if (!this.edit) { | |
var atLocationObj = this.dataPackage.getAtLocation(); | |
this.atLocationObj = atLocationObj; | |
// form path to D1 object dictionary | |
if ( | |
this.atLocationObj !== undefined && | |
!_.isEmpty(this.atLocationObj) | |
) { | |
var filePathObj = new Object(); | |
this.dataPackage.each(function (item) { | |
if (!Object.keys(this.atLocationObj).includes(item.id)) { | |
this.atLocationObj[item.id] = "/"; | |
} | |
}, this); | |
for (let key of Object.keys(this.atLocationObj)) { | |
var path = this.atLocationObj[key]; | |
var pathArray = path.split("/"); | |
pathArray.pop(); | |
var parentPath = pathArray.join("/"); | |
if (filePathObj.hasOwnProperty(parentPath)) { | |
filePathObj[parentPath].push(key); | |
} else { | |
filePathObj[parentPath] = new Array(); | |
filePathObj[parentPath].push(key); | |
} | |
} | |
} | |
// add top level data package row to the package table | |
var tableRow = null, | |
view = this, | |
title = this.packageTitle, | |
packageUrl = null; | |
if (title === "") { | |
let metadataObj = _.filter(this.dataPackage.models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
if (metadataObj.length > 0) { | |
title = metadataObj[0].get("title"); | |
let metaId = metadataObj[0].get("id"); | |
this.metaId = metaId; | |
} else { | |
title = this.dataPackage.get("id"); | |
} | |
} | |
let titleTooltip = title; | |
title = | |
title.length > 150 | |
? title.slice(0, 75) + | |
"..." + | |
title.slice(title.length - 75, title.length) | |
: title; | |
// set the package URL | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
packageUrl = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(view.dataPackage.id); | |
var disablePackageDownloads = this.disablePackageDownloads; | |
tableRow = this.dataPackageHeaderTemplate({ | |
id: view.dataPackage.id, | |
title: title, | |
titleTooltip: titleTooltip, | |
downloadUrl: packageUrl, | |
disablePackageDownloads: disablePackageDownloads, | |
disablePackageUrl: true, | |
}); | |
this.$el.append(tableRow); | |
if (this.atLocationObj !== undefined && filePathObj !== undefined) { | |
// sort the filePath by length | |
var sortedFilePathObj = Object.keys(filePathObj) | |
.sort() | |
.reduce((obj, key) => { | |
obj[key] = filePathObj[key]; | |
return obj; | |
}, {}); | |
this.sortedFilePathObj = sortedFilePathObj; | |
this.addFilesAndFolders(sortedFilePathObj); | |
} else { | |
this.dataPackage.each(this.addOne, this, this.dataPackage); | |
} | |
} else { | |
this.dataPackage.each(this.addOne, this); | |
} | |
}, | |
/** | |
* Add all the files and folders | |
*/ | |
addFilesAndFolders: function (sortedFilePathObj) { | |
if (!sortedFilePathObj) return false; | |
var insertedPath = new Array(); | |
let pathMap = new Object(); | |
pathMap[""] = ""; | |
for (let key of Object.keys(sortedFilePathObj)) { | |
// add folder | |
var 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; | |
// root | |
if (i == 0) { | |
itemPath = ""; | |
} else { | |
itemPath = pathMap[pathArray[i - 1]]; | |
} | |
dataItemView = new DataItemView({ | |
mode: this.mode, | |
itemName: pathArray[i], | |
itemPath: itemPath, | |
itemType: "folder", | |
parentEditorView: this.parentEditorView, | |
dataPackageId: this.dataPackage.id, | |
}); | |
this.subviews[pathArray[i]] = dataItemView; // keep track of all views | |
this.$el.append(dataItemView.render().el); | |
this.trigger("addOne"); | |
pathMap[pathArray[i]] = itemPath + "/" + pathArray[i]; | |
} | |
} | |
// add files in the folder | |
var itemArray = sortedFilePathObj[key]; | |
// Add metadata object at the top of the file table | |
if ( | |
key == "" && | |
this.metaId !== "undefined" && | |
itemArray.includes(this.metaId) | |
) { | |
let item = this.metaId; | |
this.addOne(this.dataPackage.get(item)); | |
} | |
for (let i = 0; i < itemArray.length; i++) { | |
let item = itemArray[i]; | |
this.addOne(this.dataPackage.get(item)); | |
} | |
} | |
}, | |
/** | |
Remove the subview represented by the given model item. | |
@param item The model representing the sub view to be removed | |
*/ | |
removeOne: function (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(); | |
delete this.subviews[item.id]; | |
} | |
}, | |
handleAddFiles: function (e) { | |
//Pass this on to the DataItemView for the root data package | |
this.$(".data-package-item.folder") | |
.first() | |
.data("view") | |
.handleAddFiles(e); | |
}, | |
/** | |
* Renders a control that opens the AccessPolicyView for editing permissions on this package | |
* @since 2.15.0 | |
*/ | |
renderShareControl: function () { | |
if ( | |
this.parentEditorView && | |
!this.parentEditorView.isAccessPolicyEditEnabled() | |
) { | |
this.$("#data-package-table-share").remove(); | |
} | |
}, | |
/** | |
* Close subviews as needed | |
*/ | |
onClose: function () { | |
// Close each subview | |
_.each( | |
Object.keys(this.subviews), | |
function (id) { | |
var subview = this.subviews[id]; | |
subview.onClose(); | |
}, | |
this, | |
); | |
//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) { | |
if (this.isOpen) { | |
// Get the DataItemView associated with each id | |
_.each( | |
Object.keys(this.subviews), | |
function (id) { | |
var subview = this.subviews[id]; | |
if (subview.model.get("type") === "Data" && subview.remove) { | |
// Remove the view from the DOM | |
subview.remove(); | |
// And from the subviews list | |
delete this.subviews[id]; | |
} | |
}, | |
this, | |
); | |
// And then close the folder | |
this.$el | |
.find(".open") | |
.removeClass("open") | |
.addClass("closed") | |
.removeClass("icon-chevron-down") | |
.addClass("icon-chevron-right"); | |
this.$el | |
.find(".icon-folder-open") | |
.removeClass("icon-folder-open") | |
.addClass("icon-folder-close"); | |
this.isOpen = false; | |
} else { | |
// Add sub rows to the view | |
var dataModels = this.dataPackage.where({ type: "Data" }); | |
_.each( | |
dataModels, | |
function (model) { | |
this.addOne(model); | |
}, | |
this, | |
); | |
// And then open the folder | |
this.$el | |
.find(".closed") | |
.removeClass("closed") | |
.addClass("open") | |
.removeClass("icon-folder-close") | |
.addClass("icon-chevron-down"); | |
this.$el | |
.find(".icon-folder-close") | |
.removeClass("icon-folder-close") | |
.addClass("icon-folder-open"); | |
this.isOpen = true; | |
} | |
event.stopPropagation(); | |
event.preventDefault(); | |
}, | |
/** | |
* Expand function to show hidden rows when a user clicks on an expand control. | |
* @param {Event} e - The event object. | |
* @since 2.28.0 | |
*/ | |
expand: function (e) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-parent='" + parentId + "']"; | |
this.$(children).fadeIn(); | |
this.$(eventEl) | |
.children() | |
.children(".expand-control") | |
.fadeOut(function () { | |
view | |
.$(eventEl) | |
.children() | |
.children(".collapse-control") | |
.fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
this.$(children) | |
.children() | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view | |
.$(children) | |
.children() | |
.children() | |
.children(".expand-control") | |
.fadeIn("fast"); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-parent^='" + parentId + "']"; | |
this.$(children).fadeOut(); | |
this.$(eventEl) | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view.$(eventEl).children().children(".expand-control").fadeIn(); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-packageid='" + parentId + "']"; | |
this.$(children).fadeIn(); | |
this.$(eventEl) | |
.children(".d1package-expand") | |
.fadeOut(function () { | |
view.$(eventEl).children(".d1package-collapse").fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
this.$(children) | |
.children() | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view | |
.$(children) | |
.children() | |
.children() | |
.children(".expand-control") | |
.fadeIn("fast"); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-packageid='" + parentId + "']"; | |
this.$(children).each(function () { | |
$(this).fadeOut(); | |
let childId = $(this).data("id"); | |
let grandchildren = "tr[data-parent^='" + childId + "']"; | |
$(grandchildren).fadeOut(); | |
}); | |
this.$(eventEl) | |
.children(".d1package-collapse") | |
.fadeOut(function () { | |
view.$(eventEl).children(".d1package-expand").fadeIn(); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
/** | |
* Check for private members and disable download buttons if necessary. | |
* | |
* @since 2.28.0 | |
*/ | |
checkForPrivateMembers: function () { | |
try { | |
var packageModel = this.model, | |
packageCollection = this.dataPackage; | |
if (!packageModel || !packageCollection) { | |
return; | |
} | |
var numMembersFromSolr = packageModel.get("members").length, | |
numMembersFromRDF = packageCollection.length; | |
if (numMembersFromRDF > numMembersFromSolr) { | |
var downloadButtons = this.$(".btn.download"); | |
for (var i = 0; i < downloadButtons.length; i++) { | |
var btn = downloadButtons[i]; | |
var downloadURL = $(btn).attr("href"); | |
if ( | |
downloadURL.indexOf(packageModel.get("id")) > -1 || | |
downloadURL.indexOf( | |
encodeURIComponent(packageModel.get("id")), | |
) > -1 | |
) { | |
$(btn) | |
.attr("disabled", "disabled") | |
.addClass("disabled") | |
.attr("href", "") | |
.tooltip({ | |
trigger: "hover", | |
placement: "top", | |
delay: 500, | |
title: | |
"This dataset may contain private data, so each data file should be downloaded individually.", | |
}); | |
i = downloadButtons.length; | |
} | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
}, | |
/** | |
* Retrieves and processes nested packages for the current package. | |
* | |
* @since 2.28.0 | |
*/ | |
getNestedPackages: function () { | |
var nestedPackages = new Array(); | |
var 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"; | |
}); | |
// iterate over the list of child packages and add their members | |
for (var ite in childPackages) { | |
var childPkg = childPackages[ite]; | |
if (!nestedPackageIds.includes(childPkg.get("id"))) { | |
var nestedPackage = new PackageModel(); | |
nestedPackage.set("id", childPkg.get("id")); | |
nestedPackage.setURL(); | |
nestedPackage.getMembers(); | |
nestedPackages.push(nestedPackage); | |
nestedPackageIds.push(childPkg.get("id")); | |
this.listenToOnce( | |
nestedPackage, | |
"change:members", | |
this.addNestedPackages, | |
nestedPackage, | |
); | |
} | |
} | |
}, | |
/** | |
* Adds a nested data package to the package table. | |
* | |
* @param {Object} dataPackage - The data package to be added. | |
* @since 2.28.0 | |
*/ | |
addNestedPackages: function (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; | |
/** | |
* The members of the data package. | |
* | |
* @type {Array} | |
*/ | |
var 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"; | |
}); | |
title = metadataObj[0].get("title"); | |
/** | |
* The tooltip for the title (used for long titles). | |
* | |
* @type {string} | |
*/ | |
let titleTooltip = title; | |
title = | |
title.length > 150 | |
? title.slice(0, 75) + | |
"..." + | |
title.slice(title.length - 75, title.length) | |
: title; | |
// Set the package URL | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
packageUrl = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(dataPackage.id); | |
// Set the nested package URL | |
if (MetacatUI.root !== undefined && dataPackage.id !== undefined) | |
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, | |
disablePackageDownloads: false, | |
downloadUrl: packageUrl, | |
disablePackageUrl: false, | |
packageUrl: nestedPackageUrl, | |
}); | |
this.$el.append(tableRow); | |
// Create an instance of DownloadButtonView to handle package downloads | |
this.downloadButtonView = new DownloadButtonView({ | |
model: dataPackage, | |
view: "actionsView", | |
nested: true, | |
}); | |
// Render | |
this.downloadButtonView.render(); | |
// Add the downloadButtonView el to the span | |
this.$el | |
.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"; | |
}); | |
// Add each member to the package table view | |
var view = this; | |
_.each(members, function (m) { | |
// Update the size to bytes format | |
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){ | |
e.preventDefault(); | |
var button = $(e.target); | |
button.addClass("in-progress"); | |
button.html("Downloading... <i class='icon icon-on-right icon-spinner icon-spin'></i>"); | |
return true; | |
}*/ | |
}, | |
); | |
return DataPackageView; | |
}); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/DataPackageView.js
Lines 46 to 1059 in 554eadc
var DataPackageView = Backbone.View.extend( | |
/** @lends DataPackageView.prototype */ { | |
type: "DataPackage", | |
tagName: "table", | |
className: "table table-striped table-hover", | |
id: "data-package-table", | |
events: { | |
"click .toggle-rows": "toggleRows", // Show/hide rows associated with event's metadata row | |
"click .message-row .addFiles": "handleAddFiles", | |
"click .expand-control": "expand", | |
"click .collapse-control": "collapse", | |
"click .d1package-expand": "expandAll", | |
"click .d1package-collapse": "collapseAll", | |
}, | |
subviews: {}, | |
/** | |
* A reference to the parent EditorView that contains this view | |
* @type EditorView | |
* @since 2.15.0 | |
*/ | |
parentEditorView: null, | |
template: _.template(DataPackageTemplate), | |
startMessageTemplate: _.template(DataPackageStartTemplate), | |
dataPackageHeaderTemplate: _.template(DataPackageHeaderTemplate), | |
// Models waiting for their parent folder to be rendered, hashed by parent id: | |
// {'parentid': [model1, model2, ...]} | |
delayedModels: {}, | |
/* Flag indicating the open or closed state of the package rows */ | |
isOpen: true, | |
initialize: function (options) { | |
if (options === undefined || !options) var options = {}; | |
if (!options.edit) { | |
//The edit option will allow the user to edit the table | |
this.edit = options.edit || false; | |
this.mode = "view"; | |
this.packageId = options.packageId || null; | |
this.memberId = options.memberId || null; | |
this.attributes = options.attributes || null; | |
this.dataPackage = options.dataPackage || new DataPackage(); | |
this.dataEntities = options.dataEntities || new Array(); | |
this.disablePackageDownloads = | |
options.disablePackageDownloads || false; | |
this.currentlyViewing = options.currentlyViewing || null; | |
this.parentEditorView = options.parentView || null; | |
this.title = options.title || ""; | |
this.packageTitle = options.packageTitle || ""; | |
this.nested = | |
typeof options.nested === "undefined" ? false : options.nested; | |
this.metricsModel = options.metricsModel; | |
// set the package model | |
this.packageModel = this.dataPackage.packageModel; | |
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 | |
this.edit = options.edit || false; | |
this.mode = "edit"; | |
//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 | |
else if (!this.dataPackage) { | |
this.dataPackage = new DataPackage(); | |
} | |
return this; | |
} | |
}, | |
/** | |
* Render the DataPackage HTML | |
*/ | |
render: function () { | |
this.$el.addClass("download-contents table-condensed"); | |
this.$el.append( | |
this.template({ | |
edit: this.edit, | |
dataPackageFiltering: | |
MetacatUI.appModel.get("dataPackageFiltering") || false, | |
dataPackageSorting: | |
MetacatUI.appModel.get("dataPackageSorting") || false, | |
loading: MetacatUI.appView.loadingTemplate({ | |
msg: "Loading files table... ", | |
}), | |
id: this.dataPackage.get("id"), | |
title: this.title || "Files in this dataset", | |
classes: "download-contents table-striped table-condensed table", | |
}), | |
); | |
if (this.edit) { | |
// Listen for add events because models are being merged | |
this.listenTo(this.dataPackage, "add", this.addOne); | |
this.listenTo(this.dataPackage, "fileAdded", this.addOne); | |
} | |
// Render the current set of models in the DataPackage | |
this.addAll(); | |
if (this.edit) { | |
//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(); | |
this.$("tbody").append(messageRow); | |
this.listenTo(this.dataPackage, "add", function () { | |
this.$(".message-row").remove(); | |
}); | |
} | |
//Render the Share control(s) | |
this.renderShareControl(); | |
} else { | |
// check for nessted datasets | |
if (this.nested) { | |
this.getNestedPackages(); | |
} | |
} | |
return this; | |
}, | |
/** | |
* Add a single DataItemView row to the DataPackageView | |
*/ | |
addOne: function (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 data package | |
if ( | |
item.get("formatType") == "RESOURCE" || | |
item.get("type") == "DataPackage" | |
) { | |
return; | |
} | |
var dataItemView, scimetaParent, parentRow, delayed_models; | |
if (_.contains(Object.keys(this.subviews), item.id)) { | |
return false; // Don't double render | |
} | |
var itemPath = null, | |
view = this; | |
if (!_.isEmpty(this.atLocationObj)) { | |
itemPath = this.atLocationObj[item.get("id")]; | |
if (itemPath[0] != "/") { | |
itemPath = "/" + itemPath; | |
} | |
} | |
// get the data package id | |
if (typeof dataPackage !== "undefined") { | |
var dataPackageId = dataPackage.id; | |
} | |
if (typeof dataPackageId === "undefined") | |
dataPackageId = this.dataPackage.id; | |
var insertInfoIcon = this.edit | |
? false | |
: view.dataEntities.includes(item.id); | |
dataItemView = new DataItemView({ | |
model: item, | |
metricsModel: this.metricsModel, | |
itemPath: itemPath, | |
insertInfoIcon: insertInfoIcon, | |
currentlyViewing: this.currentlyViewing, | |
mode: this.mode, | |
parentEditorView: this.parentEditorView, | |
dataPackageId: dataPackageId, | |
}); | |
this.subviews[item.id] = dataItemView; // keep track of all views | |
if (this.edit) { | |
//Get the science metadata that documents this item | |
scimetaParent = item.get("isDocumentedBy"); | |
//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 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 | |
else { | |
scimetaParent = scimetaParent[0]; | |
} | |
if ( | |
scimetaParent == item.get("id") || | |
(!scimetaParent && item.get("type") == "Metadata") | |
) { | |
// This is a metadata folder row, append it to the table | |
this.$el.append(dataItemView.render().el); | |
// Render any delayed models if this is the parent | |
if (_.contains(Object.keys(this.delayedModels), dataItemView.id)) { | |
delayed_models = this.delayedModels[dataItemView.id]; | |
_.each(delayed_models, this.addOne, this); | |
} | |
} else { | |
// Find the parent row by it's id, stored in a custom attribute | |
if (scimetaParent) | |
parentRow = this.$("[data-id='" + scimetaParent + "']"); | |
if (typeof parentRow !== "undefined" && parentRow.length) { | |
// This is a data row, insert below it's metadata parent folder | |
parentRow.after(dataItemView.render().el); | |
// 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); | |
delayed_models = delayed_models.splice(index, 1); | |
// Put the shortened array back if delayed models remains | |
if (delayed_models.length > 0) { | |
this.delayedModels[scimetaParent] = delayed_models; | |
} else { | |
this.delayedModels[scimetaParent] = undefined; | |
} | |
} | |
this.trigger("addOne"); | |
} else { | |
console.warn( | |
"Couldn't render " + | |
item.id + | |
". Delayed until parent is rendered.", | |
); | |
// Postpone the data row until the parent is rendered | |
delayed_models = this.delayedModels[scimetaParent]; | |
// Delay the model rendering if it isn't already delayed | |
if (typeof delayed_models !== "undefined") { | |
if (!_.contains(delayed_models, item)) { | |
delayed_models.push(item); | |
this.delayedModels[scimetaParent] = delayed_models; | |
} | |
} else { | |
delayed_models = []; | |
delayed_models.push(item); | |
this.delayedModels[scimetaParent] = delayed_models; | |
} | |
} | |
} | |
} else { | |
// This is a metadata folder row, append it to the table | |
this.$el.append(dataItemView.render().el); | |
this.trigger("addOne"); | |
} | |
}, | |
/** | |
* Render the Data Package View and insert it into this view | |
*/ | |
renderDataPackage: function () { | |
var view = this; | |
if (MetacatUI.rootDataPackage.packageModel.isNew()) { | |
view.renderMember(this.model); | |
} | |
// As the root collection is updated with models, render the UI | |
this.listenTo(MetacatUI.rootDataPackage, "add", function (model) { | |
if (!model.get("synced") && model.get("id")) | |
this.listenTo(model, "sync", view.renderMember); | |
else if (model.get("synced")) view.renderMember(model); | |
//Listen for changes on this member | |
model.on("change:fileName", model.addToUploadQueue); | |
}); | |
//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"); | |
$packageTableContainer.html(this.dataPackageView.render().el); | |
//Make the view resizable on the bottom | |
var handle = $(document.createElement("div")) | |
.addClass("ui-resizable-handle ui-resizable-s") | |
.attr("title", "Drag to resize") | |
.append( | |
$(document.createElement("i")).addClass("icon icon-caret-down"), | |
); | |
$packageTableContainer.after(handle); | |
$packageTableContainer.resizable({ | |
handles: { s: handle }, | |
minHeight: 100, | |
maxHeight: 900, | |
resize: function () { | |
view.emlView.resizeTOC(); | |
}, | |
}); | |
var tableHeight = ($(window).height() - $("#Navbar").height()) * 0.4; | |
$packageTableContainer.css("height", tableHeight + "px"); | |
var table = this.dataPackageView.$el; | |
this.listenTo(this.dataPackageView, "addOne", function () { | |
if ( | |
table.outerHeight() > $packageTableContainer.outerHeight() && | |
table.outerHeight() < 220 | |
) { | |
$packageTableContainer.css( | |
"height", | |
table.outerHeight() + handle.outerHeight(), | |
); | |
if (this.emlView) this.emlView.resizeTOC(); | |
} | |
}); | |
if (this.emlView) this.emlView.resizeTOC(); | |
//Save the view as a subview | |
this.subviews.push(this.dataPackageView); | |
this.listenTo( | |
MetacatUI.rootDataPackage.packageModel, | |
"change:childPackages", | |
this.renderChildren, | |
); | |
}, | |
/** | |
* Add all rows to the DataPackageView | |
*/ | |
addAll: function () { | |
this.$el.find("#data-package-table-body").html(""); // clear the table first | |
this.dataPackage.sort(); | |
if (!this.edit) { | |
var atLocationObj = this.dataPackage.getAtLocation(); | |
this.atLocationObj = atLocationObj; | |
// form path to D1 object dictionary | |
if ( | |
this.atLocationObj !== undefined && | |
!_.isEmpty(this.atLocationObj) | |
) { | |
var filePathObj = new Object(); | |
this.dataPackage.each(function (item) { | |
if (!Object.keys(this.atLocationObj).includes(item.id)) { | |
this.atLocationObj[item.id] = "/"; | |
} | |
}, this); | |
for (let key of Object.keys(this.atLocationObj)) { | |
var path = this.atLocationObj[key]; | |
var pathArray = path.split("/"); | |
pathArray.pop(); | |
var parentPath = pathArray.join("/"); | |
if (filePathObj.hasOwnProperty(parentPath)) { | |
filePathObj[parentPath].push(key); | |
} else { | |
filePathObj[parentPath] = new Array(); | |
filePathObj[parentPath].push(key); | |
} | |
} | |
} | |
// add top level data package row to the package table | |
var tableRow = null, | |
view = this, | |
title = this.packageTitle, | |
packageUrl = null; | |
if (title === "") { | |
let metadataObj = _.filter(this.dataPackage.models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
if (metadataObj.length > 0) { | |
title = metadataObj[0].get("title"); | |
let metaId = metadataObj[0].get("id"); | |
this.metaId = metaId; | |
} else { | |
title = this.dataPackage.get("id"); | |
} | |
} | |
let titleTooltip = title; | |
title = | |
title.length > 150 | |
? title.slice(0, 75) + | |
"..." + | |
title.slice(title.length - 75, title.length) | |
: title; | |
// set the package URL | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
packageUrl = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(view.dataPackage.id); | |
var disablePackageDownloads = this.disablePackageDownloads; | |
tableRow = this.dataPackageHeaderTemplate({ | |
id: view.dataPackage.id, | |
title: title, | |
titleTooltip: titleTooltip, | |
downloadUrl: packageUrl, | |
disablePackageDownloads: disablePackageDownloads, | |
disablePackageUrl: true, | |
}); | |
this.$el.append(tableRow); | |
if (this.atLocationObj !== undefined && filePathObj !== undefined) { | |
// sort the filePath by length | |
var sortedFilePathObj = Object.keys(filePathObj) | |
.sort() | |
.reduce((obj, key) => { | |
obj[key] = filePathObj[key]; | |
return obj; | |
}, {}); | |
this.sortedFilePathObj = sortedFilePathObj; | |
this.addFilesAndFolders(sortedFilePathObj); | |
} else { | |
this.dataPackage.each(this.addOne, this, this.dataPackage); | |
} | |
} else { | |
this.dataPackage.each(this.addOne, this); | |
} | |
}, | |
/** | |
* Add all the files and folders | |
*/ | |
addFilesAndFolders: function (sortedFilePathObj) { | |
if (!sortedFilePathObj) return false; | |
var insertedPath = new Array(); | |
let pathMap = new Object(); | |
pathMap[""] = ""; | |
for (let key of Object.keys(sortedFilePathObj)) { | |
// add folder | |
var 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; | |
// root | |
if (i == 0) { | |
itemPath = ""; | |
} else { | |
itemPath = pathMap[pathArray[i - 1]]; | |
} | |
dataItemView = new DataItemView({ | |
mode: this.mode, | |
itemName: pathArray[i], | |
itemPath: itemPath, | |
itemType: "folder", | |
parentEditorView: this.parentEditorView, | |
dataPackageId: this.dataPackage.id, | |
}); | |
this.subviews[pathArray[i]] = dataItemView; // keep track of all views | |
this.$el.append(dataItemView.render().el); | |
this.trigger("addOne"); | |
pathMap[pathArray[i]] = itemPath + "/" + pathArray[i]; | |
} | |
} | |
// add files in the folder | |
var itemArray = sortedFilePathObj[key]; | |
// Add metadata object at the top of the file table | |
if ( | |
key == "" && | |
this.metaId !== "undefined" && | |
itemArray.includes(this.metaId) | |
) { | |
let item = this.metaId; | |
this.addOne(this.dataPackage.get(item)); | |
} | |
for (let i = 0; i < itemArray.length; i++) { | |
let item = itemArray[i]; | |
this.addOne(this.dataPackage.get(item)); | |
} | |
} | |
}, | |
/** | |
Remove the subview represented by the given model item. | |
@param item The model representing the sub view to be removed | |
*/ | |
removeOne: function (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(); | |
delete this.subviews[item.id]; | |
} | |
}, | |
handleAddFiles: function (e) { | |
//Pass this on to the DataItemView for the root data package | |
this.$(".data-package-item.folder") | |
.first() | |
.data("view") | |
.handleAddFiles(e); | |
}, | |
/** | |
* Renders a control that opens the AccessPolicyView for editing permissions on this package | |
* @since 2.15.0 | |
*/ | |
renderShareControl: function () { | |
if ( | |
this.parentEditorView && | |
!this.parentEditorView.isAccessPolicyEditEnabled() | |
) { | |
this.$("#data-package-table-share").remove(); | |
} | |
}, | |
/** | |
* Close subviews as needed | |
*/ | |
onClose: function () { | |
// Close each subview | |
_.each( | |
Object.keys(this.subviews), | |
function (id) { | |
var subview = this.subviews[id]; | |
subview.onClose(); | |
}, | |
this, | |
); | |
//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) { | |
if (this.isOpen) { | |
// Get the DataItemView associated with each id | |
_.each( | |
Object.keys(this.subviews), | |
function (id) { | |
var subview = this.subviews[id]; | |
if (subview.model.get("type") === "Data" && subview.remove) { | |
// Remove the view from the DOM | |
subview.remove(); | |
// And from the subviews list | |
delete this.subviews[id]; | |
} | |
}, | |
this, | |
); | |
// And then close the folder | |
this.$el | |
.find(".open") | |
.removeClass("open") | |
.addClass("closed") | |
.removeClass("icon-chevron-down") | |
.addClass("icon-chevron-right"); | |
this.$el | |
.find(".icon-folder-open") | |
.removeClass("icon-folder-open") | |
.addClass("icon-folder-close"); | |
this.isOpen = false; | |
} else { | |
// Add sub rows to the view | |
var dataModels = this.dataPackage.where({ type: "Data" }); | |
_.each( | |
dataModels, | |
function (model) { | |
this.addOne(model); | |
}, | |
this, | |
); | |
// And then open the folder | |
this.$el | |
.find(".closed") | |
.removeClass("closed") | |
.addClass("open") | |
.removeClass("icon-folder-close") | |
.addClass("icon-chevron-down"); | |
this.$el | |
.find(".icon-folder-close") | |
.removeClass("icon-folder-close") | |
.addClass("icon-folder-open"); | |
this.isOpen = true; | |
} | |
event.stopPropagation(); | |
event.preventDefault(); | |
}, | |
/** | |
* Expand function to show hidden rows when a user clicks on an expand control. | |
* @param {Event} e - The event object. | |
* @since 2.28.0 | |
*/ | |
expand: function (e) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-parent='" + parentId + "']"; | |
this.$(children).fadeIn(); | |
this.$(eventEl) | |
.children() | |
.children(".expand-control") | |
.fadeOut(function () { | |
view | |
.$(eventEl) | |
.children() | |
.children(".collapse-control") | |
.fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
this.$(children) | |
.children() | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view | |
.$(children) | |
.children() | |
.children() | |
.children(".expand-control") | |
.fadeIn("fast"); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-parent^='" + parentId + "']"; | |
this.$(children).fadeOut(); | |
this.$(eventEl) | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view.$(eventEl).children().children(".expand-control").fadeIn(); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-packageid='" + parentId + "']"; | |
this.$(children).fadeIn(); | |
this.$(eventEl) | |
.children(".d1package-expand") | |
.fadeOut(function () { | |
view.$(eventEl).children(".d1package-collapse").fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
this.$(children) | |
.children() | |
.children() | |
.children(".collapse-control") | |
.fadeOut(function () { | |
view | |
.$(children) | |
.children() | |
.children() | |
.children(".expand-control") | |
.fadeIn("fast"); | |
}); | |
}, | |
/** | |
* 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) { | |
// Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
var eventEl = $(e.target).parents("td"); | |
var rowEl = $(e.target).parents("tr"); | |
var parentId = rowEl.data("id"); | |
var children = "tr[data-packageid='" + parentId + "']"; | |
this.$(children).each(function () { | |
$(this).fadeOut(); | |
let childId = $(this).data("id"); | |
let grandchildren = "tr[data-parent^='" + childId + "']"; | |
$(grandchildren).fadeOut(); | |
}); | |
this.$(eventEl) | |
.children(".d1package-collapse") | |
.fadeOut(function () { | |
view.$(eventEl).children(".d1package-expand").fadeIn(); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
/** | |
* Check for private members and disable download buttons if necessary. | |
* | |
* @since 2.28.0 | |
*/ | |
checkForPrivateMembers: function () { | |
try { | |
var packageModel = this.model, | |
packageCollection = this.dataPackage; | |
if (!packageModel || !packageCollection) { | |
return; | |
} | |
var numMembersFromSolr = packageModel.get("members").length, | |
numMembersFromRDF = packageCollection.length; | |
if (numMembersFromRDF > numMembersFromSolr) { | |
var downloadButtons = this.$(".btn.download"); | |
for (var i = 0; i < downloadButtons.length; i++) { | |
var btn = downloadButtons[i]; | |
var downloadURL = $(btn).attr("href"); | |
if ( | |
downloadURL.indexOf(packageModel.get("id")) > -1 || | |
downloadURL.indexOf( | |
encodeURIComponent(packageModel.get("id")), | |
) > -1 | |
) { | |
$(btn) | |
.attr("disabled", "disabled") | |
.addClass("disabled") | |
.attr("href", "") | |
.tooltip({ | |
trigger: "hover", | |
placement: "top", | |
delay: 500, | |
title: | |
"This dataset may contain private data, so each data file should be downloaded individually.", | |
}); | |
i = downloadButtons.length; | |
} | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
}, | |
/** | |
* Retrieves and processes nested packages for the current package. | |
* | |
* @since 2.28.0 | |
*/ | |
getNestedPackages: function () { | |
var nestedPackages = new Array(); | |
var 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"; | |
}); | |
// iterate over the list of child packages and add their members | |
for (var ite in childPackages) { | |
var childPkg = childPackages[ite]; | |
if (!nestedPackageIds.includes(childPkg.get("id"))) { | |
var nestedPackage = new PackageModel(); | |
nestedPackage.set("id", childPkg.get("id")); | |
nestedPackage.setURL(); | |
nestedPackage.getMembers(); | |
nestedPackages.push(nestedPackage); | |
nestedPackageIds.push(childPkg.get("id")); | |
this.listenToOnce( | |
nestedPackage, | |
"change:members", | |
this.addNestedPackages, | |
nestedPackage, | |
); | |
} | |
} | |
}, | |
/** | |
* Adds a nested data package to the package table. | |
* | |
* @param {Object} dataPackage - The data package to be added. | |
* @since 2.28.0 | |
*/ | |
addNestedPackages: function (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; | |
/** | |
* The members of the data package. | |
* | |
* @type {Array} | |
*/ | |
var 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"; | |
}); | |
title = metadataObj[0].get("title"); | |
/** | |
* The tooltip for the title (used for long titles). | |
* | |
* @type {string} | |
*/ | |
let titleTooltip = title; | |
title = | |
title.length > 150 | |
? title.slice(0, 75) + | |
"..." + | |
title.slice(title.length - 75, title.length) | |
: title; | |
// Set the package URL | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
packageUrl = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(dataPackage.id); | |
// Set the nested package URL | |
if (MetacatUI.root !== undefined && dataPackage.id !== undefined) | |
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, | |
disablePackageDownloads: false, | |
downloadUrl: packageUrl, | |
disablePackageUrl: false, | |
packageUrl: nestedPackageUrl, | |
}); | |
this.$el.append(tableRow); | |
// Create an instance of DownloadButtonView to handle package downloads | |
this.downloadButtonView = new DownloadButtonView({ | |
model: dataPackage, | |
view: "actionsView", | |
nested: true, | |
}); | |
// Render | |
this.downloadButtonView.render(); | |
// Add the downloadButtonView el to the span | |
this.$el | |
.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"; | |
}); | |
// Add each member to the package table view | |
var view = this; | |
_.each(members, function (m) { | |
// Update the size to bytes format | |
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){ | |
e.preventDefault(); | |
var button = $(e.target); | |
button.addClass("in-progress"); | |
button.html("Downloading... <i class='icon icon-on-right icon-spinner icon-spin'></i>"); | |
return true; | |
}*/ | |
}, | |
); |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/DataPackageView.js
Lines 933 to 1046 in 554eadc
addNestedPackages: function (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; | |
/** | |
* The members of the data package. | |
* | |
* @type {Array} | |
*/ | |
var 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"; | |
}); | |
title = metadataObj[0].get("title"); | |
/** | |
* The tooltip for the title (used for long titles). | |
* | |
* @type {string} | |
*/ | |
let titleTooltip = title; | |
title = | |
title.length > 150 | |
? title.slice(0, 75) + | |
"..." + | |
title.slice(title.length - 75, title.length) | |
: title; | |
// Set the package URL | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
packageUrl = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(dataPackage.id); | |
// Set the nested package URL | |
if (MetacatUI.root !== undefined && dataPackage.id !== undefined) | |
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, | |
disablePackageDownloads: false, | |
downloadUrl: packageUrl, | |
disablePackageUrl: false, | |
packageUrl: nestedPackageUrl, | |
}); | |
this.$el.append(tableRow); | |
// Create an instance of DownloadButtonView to handle package downloads | |
this.downloadButtonView = new DownloadButtonView({ | |
model: dataPackage, | |
view: "actionsView", | |
nested: true, | |
}); | |
// Render | |
this.downloadButtonView.render(); | |
// Add the downloadButtonView el to the span | |
this.$el | |
.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"; | |
}); | |
// Add each member to the package table view | |
var view = this; | |
_.each(members, function (m) { | |
// Update the size to bytes format | |
m.set({ size: Utilities.bytesToSize(m.get("size")) }); | |
// Add each item of this nested package to the package table view | |
view.addOne(m, dataPackage); | |
}); | |
}, |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/views/DataPackageView.js
Line 1038 in 554eadc
var view = this; |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/DataPackageView.js
Line 1038 in 554eadc
var view = this; |
🚫 [eslint] <no-redeclare> reported by reviewdog 🐶
'view' is already defined.
metacatui/src/js/views/DataPackageView.js
Line 1038 in 554eadc
var view = this; |
Unexpected unnamed function.
metacatui/src/js/views/DataPackageView.js
Line 1039 in 554eadc
_.each(members, function (m) { |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/DataPackageView.js
Lines 1039 to 1045 in 554eadc
_.each(members, function (m) { | |
// Update the size to bytes format | |
m.set({ size: Utilities.bytesToSize(m.get("size")) }); | |
// Add each item of this nested package to the package table view | |
view.addOne(m, dataPackage); | |
}); |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/MetadataIndexView.js
Lines 13 to 565 in 554eadc
], function ( | |
$, | |
_, | |
Backbone, | |
gmaps, | |
Utilities, | |
SolrResult, | |
DownloadButtonView, | |
LoadingTemplate, | |
alertTemplate, | |
AttributeTemplate, | |
DataDisplayTemplate, | |
) { | |
var MetadataIndexView = Backbone.View.extend({ | |
type: "MetadataIndex", | |
id: "Metadata", | |
className: "metadata-index container form-horizontal", | |
tagName: "article", | |
template: null, | |
loadingTemplate: _.template(LoadingTemplate), | |
attributeTemplate: _.template(AttributeTemplate), | |
alertTemplate: _.template(alertTemplate), | |
dataDisplayTemplate: _.template(DataDisplayTemplate), | |
semanticFields: null, | |
events: {}, | |
initialize: function (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.parentView = options.parentView || null; | |
// use these to tailor the annotation ui widget | |
this.semanticFields = { | |
attribute: "sem_annotation", | |
attributeName: "sem_annotation", | |
attributeLabel: "sem_annotation", | |
attributeDescription: "sem_annotation", | |
attributeUnit: "sem_annotation", | |
origin: "orcid_sm", | |
investigator: "orcid_sm", | |
}; | |
}, | |
render: function () { | |
if (!this.pid) return false; | |
var 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'; | |
var requestSettings = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
success: function (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 | |
// 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 | |
else { | |
view.parentView.model.set("notFound", true); | |
view.parentView.showNotFound(); | |
} | |
} | |
view.flagComplete(); | |
} 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. | |
if ( | |
doc.formatType == "DATA" && | |
doc.isDocumentedBy && | |
doc.isDocumentedBy.length | |
) { | |
view.onClose(); | |
MetacatUI.uiRouter.navigate( | |
"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 = [ | |
"title", | |
"id", | |
"abstract", | |
"pubDate", | |
"keywords", | |
]; | |
keys = _.difference(keys, generalInfoKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
generalInfoKeys, | |
"General", | |
), | |
); | |
//Extract Spatial details | |
var spatialKeys = [ | |
"site", | |
"southBoundCoord", | |
"northBoundCoord", | |
"westBoundCoord", | |
"eastBoundCoord", | |
]; | |
keys = _.difference(keys, spatialKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
spatialKeys, | |
"Geographic Region", | |
), | |
); | |
//Extract Temporal Coverage details | |
var temporalKeys = ["beginDate", "endDate"]; | |
keys = _.difference(keys, temporalKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
temporalKeys, | |
"Temporal Coverage", | |
), | |
); | |
//Extract Taxonomic Coverage details | |
var taxonKeys = [ | |
"order", | |
"phylum", | |
"family", | |
"genus", | |
"species", | |
"scientificName", | |
]; | |
keys = _.difference(keys, taxonKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
taxonKeys, | |
"Taxonomic Coverage", | |
), | |
); | |
//Extract People details | |
var peopleKeys = [ | |
"origin", | |
"investigator", | |
"contactOrganization", | |
"project", | |
]; | |
keys = _.difference(keys, peopleKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
peopleKeys, | |
"People and Associated Parties", | |
), | |
); | |
//Extract Access Control details | |
var accessKeys = [ | |
"isPublic", | |
"submitter", | |
"rightsHolder", | |
"writePermission", | |
"readPermission", | |
"changePermission", | |
"authoritativeMN", | |
]; | |
keys = _.difference(keys, accessKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
accessKeys, | |
"Access Control", | |
), | |
); | |
//Add the rest of the metadata | |
$(metadataEl).append( | |
view.formatAttributeSection(docModel, keys, "Other"), | |
); | |
view.$el.html(metadataEl); | |
view.flagComplete(); | |
}); | |
} | |
} catch (e) { | |
console.log("Error parsing Solr response: " + e); | |
console.log("Solr response: " + data); | |
view.parentView.showNotFound(); | |
} | |
}, | |
error: function () { | |
var msg = "<h4>Sorry, no dataset was found.</h4>"; | |
view.$el.html( | |
view.alertTemplate({ msg: msg, classes: "alert-danger" }), | |
); | |
}, | |
}; | |
$.ajax( | |
_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()), | |
); | |
//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"); | |
if (formatId.indexOf("eml://") >= 0) { | |
var url = | |
MetacatUI.appModel.get("baseUrl") + | |
MetacatUI.appModel.get("context") + | |
MetacatUI.appModel.get("d1Service") + | |
"/object/" + | |
encodeURIComponent(this.parentView.model.get("id")); | |
var requestSettings = { | |
url: url, | |
success: function (data, textStatus, xhr) { | |
if (!data || !$(data).length) return; | |
//Find the distribution information | |
var emlDoc = $(data) | |
.find("distribution") | |
.each(function (i, dist) { | |
var 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 = []; | |
//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) { | |
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 (dataObjects[i].get("id").indexOf(ecogridPid) > -1) { | |
var linkText = dataObjects[i].get("url"); | |
//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, | |
); | |
//Find the "General" section of this page | |
if (view.$(".General").length) | |
view.$(".General").after(fullHTML); | |
else view.$el.append(fullHTML); | |
} | |
}); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
} | |
return this; | |
}, | |
formatAttributeSection: function (doc, keys, title, className) { | |
if (keys.length == 0) return ""; | |
if (typeof title === "string") { | |
var titleHTML = $(document.createElement("h4")).text(title); | |
var titleText = title; | |
} else if (typeof title === "undefined") { | |
var titleHTML = $(document.createElement("h4")); | |
var titleText = ""; | |
} else { | |
var titleHTML = title; | |
var titleText = titleHTML.text(); | |
} | |
var html = "", | |
sectionClass = | |
typeof className === "undefined" | |
? titleText.replace(/ /g, "") | |
: className, | |
view = this, | |
populated = false; | |
_.each(keys, function (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)) { | |
html += view.formatAttribute(key, doc.get(key)); | |
populated = true; | |
} | |
}); | |
if (populated) { | |
var section = $(document.createElement("section")) | |
.addClass(sectionClass) | |
.append(titleHTML) | |
.append(html); | |
return section; | |
} else return null; | |
}, | |
formatAttribute: function (attribute, value) { | |
var html = "", | |
view = this, | |
embeddedAttributes = "", | |
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 = | |
Array.isArray(value) && value.length > 1 ? value.length : 0; | |
for (var 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) + "])", | |
}); | |
} | |
if (!embeddedAttributes && value instanceof $) { | |
value = value[0].outerHTML; | |
} | |
html += view.attributeTemplate({ | |
attribute: attribute, | |
formattedAttribute: view.transformCamelCase(attribute), | |
value: embeddedAttributes || value.toString(), | |
id: attribute, | |
type: type, | |
resource: "#xpointer(//" + attribute + ")", | |
}); | |
return html; | |
}, | |
transformCamelCase: function (string) { | |
var result = string | |
.replace(/([A-Z]+)/g, " $1") | |
.replace(/([A-Z][a-z])/g, " $1"); | |
var finalResult = result.charAt(0).toUpperCase() + result.slice(1); | |
return finalResult; | |
}, | |
insertDataDetails: function () { | |
var view = this; | |
//Get the Package Model - it is attached with the parent Metadata View | |
var pkg = this.parentView.packageModel; | |
if (!pkg) return; | |
if (pkg.get("members").length <= 1) return; | |
//Start some html | |
var html = $(document.createElement("section")); | |
_.each(pkg.get("members"), function (solrResult, i) { | |
if (solrResult.get("formatType") != "DATA") return; | |
solrResult.set( | |
"formattedSize", | |
Utilities.bytesToSize(solrResult.get("size")), | |
); | |
//Add a section for the data details, just like the other attribute sections | |
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; | |
//Determine the icon type based on format id | |
var type = solrResult.getType(), | |
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 | |
.formatAttributeSection(solrResult, keys, header, "entitydetails") | |
.attr("data-id", solrResult.get("id")); | |
//Create an image thumbnail, if this is an image | |
if (type == "image") { | |
//var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); | |
//$(entityDetailsSection).prepend(thumbnail); | |
} | |
//Mark this section with an anchor tag with the doc id | |
$(entityDetailsSection).prepend( | |
$(document.createElement("a")).attr( | |
"id", | |
solrResult.get("id").replace(/[^A-Za-z0-9]/g, "-"), | |
), | |
); | |
$(html).append(entityDetailsSection); | |
}); | |
//Glue together the header and attribute info section | |
var header = $(document.createElement("h4")).text( | |
"Data Table, Image, and Other Data Details", | |
); | |
var section = $(html).prepend(header); | |
//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({ | |
classes: "alert-warning", | |
msg: | |
"<h4>There is limited information about this content.</h4>" + | |
"<p>This data or science metadata is available to download, but " + | |
"there seems to be an issue with displaying details on this webpage. " + | |
"If this content was recently submitted, it may still be in the processing queue.</p>", | |
includeEmail: true, | |
}); | |
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.parentView && this.parentView.model) { | |
//Get the system metadata string | |
var sysMeta = this.parentView.model.get("systemMetadata"); | |
if (sysMeta) { | |
//Parse it into XML nodes | |
sysMeta = $.parseXML(sysMeta); | |
//Find the allow permission for the public | |
var publicPermission = $(sysMeta).find( | |
"allow subject:contains('public')", | |
); | |
if (publicPermission.length) { | |
//Remove the "private" icon | |
$("#metadata-controls-container .private").remove(); | |
} | |
} | |
//If there is no system metadata, default to hiding the private icon | |
else { | |
$("#metadata-controls-container .private").remove(); | |
} | |
} | |
}, | |
flagComplete: function () { | |
this.complete = true; | |
this.trigger("complete"); | |
}, | |
onClose: function () { | |
this.$el.html(this.loadingTemplate()); | |
this.pid = null; | |
//Detach this view from its parent view | |
this.parentView.subviews = _.without(this.parentView.subviews, this); | |
this.parentView = null; | |
//Remove listeners | |
this.stopListening(); | |
}, | |
}); | |
return MetadataIndexView; | |
}); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/MetadataIndexView.js
Lines 26 to 563 in 554eadc
var MetadataIndexView = Backbone.View.extend({ | |
type: "MetadataIndex", | |
id: "Metadata", | |
className: "metadata-index container form-horizontal", | |
tagName: "article", | |
template: null, | |
loadingTemplate: _.template(LoadingTemplate), | |
attributeTemplate: _.template(AttributeTemplate), | |
alertTemplate: _.template(alertTemplate), | |
dataDisplayTemplate: _.template(DataDisplayTemplate), | |
semanticFields: null, | |
events: {}, | |
initialize: function (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.parentView = options.parentView || null; | |
// use these to tailor the annotation ui widget | |
this.semanticFields = { | |
attribute: "sem_annotation", | |
attributeName: "sem_annotation", | |
attributeLabel: "sem_annotation", | |
attributeDescription: "sem_annotation", | |
attributeUnit: "sem_annotation", | |
origin: "orcid_sm", | |
investigator: "orcid_sm", | |
}; | |
}, | |
render: function () { | |
if (!this.pid) return false; | |
var 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'; | |
var requestSettings = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
success: function (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 | |
// 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 | |
else { | |
view.parentView.model.set("notFound", true); | |
view.parentView.showNotFound(); | |
} | |
} | |
view.flagComplete(); | |
} 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. | |
if ( | |
doc.formatType == "DATA" && | |
doc.isDocumentedBy && | |
doc.isDocumentedBy.length | |
) { | |
view.onClose(); | |
MetacatUI.uiRouter.navigate( | |
"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 = [ | |
"title", | |
"id", | |
"abstract", | |
"pubDate", | |
"keywords", | |
]; | |
keys = _.difference(keys, generalInfoKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
generalInfoKeys, | |
"General", | |
), | |
); | |
//Extract Spatial details | |
var spatialKeys = [ | |
"site", | |
"southBoundCoord", | |
"northBoundCoord", | |
"westBoundCoord", | |
"eastBoundCoord", | |
]; | |
keys = _.difference(keys, spatialKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
spatialKeys, | |
"Geographic Region", | |
), | |
); | |
//Extract Temporal Coverage details | |
var temporalKeys = ["beginDate", "endDate"]; | |
keys = _.difference(keys, temporalKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
temporalKeys, | |
"Temporal Coverage", | |
), | |
); | |
//Extract Taxonomic Coverage details | |
var taxonKeys = [ | |
"order", | |
"phylum", | |
"family", | |
"genus", | |
"species", | |
"scientificName", | |
]; | |
keys = _.difference(keys, taxonKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
taxonKeys, | |
"Taxonomic Coverage", | |
), | |
); | |
//Extract People details | |
var peopleKeys = [ | |
"origin", | |
"investigator", | |
"contactOrganization", | |
"project", | |
]; | |
keys = _.difference(keys, peopleKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
peopleKeys, | |
"People and Associated Parties", | |
), | |
); | |
//Extract Access Control details | |
var accessKeys = [ | |
"isPublic", | |
"submitter", | |
"rightsHolder", | |
"writePermission", | |
"readPermission", | |
"changePermission", | |
"authoritativeMN", | |
]; | |
keys = _.difference(keys, accessKeys); | |
$(metadataEl).append( | |
view.formatAttributeSection( | |
docModel, | |
accessKeys, | |
"Access Control", | |
), | |
); | |
//Add the rest of the metadata | |
$(metadataEl).append( | |
view.formatAttributeSection(docModel, keys, "Other"), | |
); | |
view.$el.html(metadataEl); | |
view.flagComplete(); | |
}); | |
} | |
} catch (e) { | |
console.log("Error parsing Solr response: " + e); | |
console.log("Solr response: " + data); | |
view.parentView.showNotFound(); | |
} | |
}, | |
error: function () { | |
var msg = "<h4>Sorry, no dataset was found.</h4>"; | |
view.$el.html( | |
view.alertTemplate({ msg: msg, classes: "alert-danger" }), | |
); | |
}, | |
}; | |
$.ajax( | |
_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()), | |
); | |
//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"); | |
if (formatId.indexOf("eml://") >= 0) { | |
var url = | |
MetacatUI.appModel.get("baseUrl") + | |
MetacatUI.appModel.get("context") + | |
MetacatUI.appModel.get("d1Service") + | |
"/object/" + | |
encodeURIComponent(this.parentView.model.get("id")); | |
var requestSettings = { | |
url: url, | |
success: function (data, textStatus, xhr) { | |
if (!data || !$(data).length) return; | |
//Find the distribution information | |
var emlDoc = $(data) | |
.find("distribution") | |
.each(function (i, dist) { | |
var 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 = []; | |
//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) { | |
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 (dataObjects[i].get("id").indexOf(ecogridPid) > -1) { | |
var linkText = dataObjects[i].get("url"); | |
//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, | |
); | |
//Find the "General" section of this page | |
if (view.$(".General").length) | |
view.$(".General").after(fullHTML); | |
else view.$el.append(fullHTML); | |
} | |
}); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
} | |
return this; | |
}, | |
formatAttributeSection: function (doc, keys, title, className) { | |
if (keys.length == 0) return ""; | |
if (typeof title === "string") { | |
var titleHTML = $(document.createElement("h4")).text(title); | |
var titleText = title; | |
} else if (typeof title === "undefined") { | |
var titleHTML = $(document.createElement("h4")); | |
var titleText = ""; | |
} else { | |
var titleHTML = title; | |
var titleText = titleHTML.text(); | |
} | |
var html = "", | |
sectionClass = | |
typeof className === "undefined" | |
? titleText.replace(/ /g, "") | |
: className, | |
view = this, | |
populated = false; | |
_.each(keys, function (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)) { | |
html += view.formatAttribute(key, doc.get(key)); | |
populated = true; | |
} | |
}); | |
if (populated) { | |
var section = $(document.createElement("section")) | |
.addClass(sectionClass) | |
.append(titleHTML) | |
.append(html); | |
return section; | |
} else return null; | |
}, | |
formatAttribute: function (attribute, value) { | |
var html = "", | |
view = this, | |
embeddedAttributes = "", | |
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 = | |
Array.isArray(value) && value.length > 1 ? value.length : 0; | |
for (var 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) + "])", | |
}); | |
} | |
if (!embeddedAttributes && value instanceof $) { | |
value = value[0].outerHTML; | |
} | |
html += view.attributeTemplate({ | |
attribute: attribute, | |
formattedAttribute: view.transformCamelCase(attribute), | |
value: embeddedAttributes || value.toString(), | |
id: attribute, | |
type: type, | |
resource: "#xpointer(//" + attribute + ")", | |
}); | |
return html; | |
}, | |
transformCamelCase: function (string) { | |
var result = string | |
.replace(/([A-Z]+)/g, " $1") | |
.replace(/([A-Z][a-z])/g, " $1"); | |
var finalResult = result.charAt(0).toUpperCase() + result.slice(1); | |
return finalResult; | |
}, | |
insertDataDetails: function () { | |
var view = this; | |
//Get the Package Model - it is attached with the parent Metadata View | |
var pkg = this.parentView.packageModel; | |
if (!pkg) return; | |
if (pkg.get("members").length <= 1) return; | |
//Start some html | |
var html = $(document.createElement("section")); | |
_.each(pkg.get("members"), function (solrResult, i) { | |
if (solrResult.get("formatType") != "DATA") return; | |
solrResult.set( | |
"formattedSize", | |
Utilities.bytesToSize(solrResult.get("size")), | |
); | |
//Add a section for the data details, just like the other attribute sections | |
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; | |
//Determine the icon type based on format id | |
var type = solrResult.getType(), | |
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 | |
.formatAttributeSection(solrResult, keys, header, "entitydetails") | |
.attr("data-id", solrResult.get("id")); | |
//Create an image thumbnail, if this is an image | |
if (type == "image") { | |
//var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); | |
//$(entityDetailsSection).prepend(thumbnail); | |
} | |
//Mark this section with an anchor tag with the doc id | |
$(entityDetailsSection).prepend( | |
$(document.createElement("a")).attr( | |
"id", | |
solrResult.get("id").replace(/[^A-Za-z0-9]/g, "-"), | |
), | |
); | |
$(html).append(entityDetailsSection); | |
}); | |
//Glue together the header and attribute info section | |
var header = $(document.createElement("h4")).text( | |
"Data Table, Image, and Other Data Details", | |
); | |
var section = $(html).prepend(header); | |
//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({ | |
classes: "alert-warning", | |
msg: | |
"<h4>There is limited information about this content.</h4>" + | |
"<p>This data or science metadata is available to download, but " + | |
"there seems to be an issue with displaying details on this webpage. " + | |
"If this content was recently submitted, it may still be in the processing queue.</p>", | |
includeEmail: true, | |
}); | |
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.parentView && this.parentView.model) { | |
//Get the system metadata string | |
var sysMeta = this.parentView.model.get("systemMetadata"); | |
if (sysMeta) { | |
//Parse it into XML nodes | |
sysMeta = $.parseXML(sysMeta); | |
//Find the allow permission for the public | |
var publicPermission = $(sysMeta).find( | |
"allow subject:contains('public')", | |
); | |
if (publicPermission.length) { | |
//Remove the "private" icon | |
$("#metadata-controls-container .private").remove(); | |
} | |
} | |
//If there is no system metadata, default to hiding the private icon | |
else { | |
$("#metadata-controls-container .private").remove(); | |
} | |
} | |
}, | |
flagComplete: function () { | |
this.complete = true; | |
this.trigger("complete"); | |
}, | |
onClose: function () { | |
this.$el.html(this.loadingTemplate()); | |
this.pid = null; | |
//Detach this view from its parent view | |
this.parentView.subviews = _.without(this.parentView.subviews, this); | |
this.parentView = null; | |
//Remove listeners | |
this.stopListening(); | |
}, | |
}); |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/MetadataIndexView.js
Lines 424 to 508 in 554eadc
insertDataDetails: function () { | |
var view = this; | |
//Get the Package Model - it is attached with the parent Metadata View | |
var pkg = this.parentView.packageModel; | |
if (!pkg) return; | |
if (pkg.get("members").length <= 1) return; | |
//Start some html | |
var html = $(document.createElement("section")); | |
_.each(pkg.get("members"), function (solrResult, i) { | |
if (solrResult.get("formatType") != "DATA") return; | |
solrResult.set( | |
"formattedSize", | |
Utilities.bytesToSize(solrResult.get("size")), | |
); | |
//Add a section for the data details, just like the other attribute sections | |
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; | |
//Determine the icon type based on format id | |
var type = solrResult.getType(), | |
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 | |
.formatAttributeSection(solrResult, keys, header, "entitydetails") | |
.attr("data-id", solrResult.get("id")); | |
//Create an image thumbnail, if this is an image | |
if (type == "image") { | |
//var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); | |
//$(entityDetailsSection).prepend(thumbnail); | |
} | |
//Mark this section with an anchor tag with the doc id | |
$(entityDetailsSection).prepend( | |
$(document.createElement("a")).attr( | |
"id", | |
solrResult.get("id").replace(/[^A-Za-z0-9]/g, "-"), | |
), | |
); | |
$(html).append(entityDetailsSection); | |
}); | |
//Glue together the header and attribute info section | |
var header = $(document.createElement("h4")).text( | |
"Data Table, Image, and Other Data Details", | |
); | |
var section = $(html).prepend(header); | |
//Insert into the DOM right after the "general" information | |
this.$(".General").after(section); | |
}, |
Unexpected unnamed function.
metacatui/src/js/views/MetadataIndexView.js
Line 436 in 554eadc
_.each(pkg.get("members"), function (solrResult, i) { |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/MetadataIndexView.js
Lines 436 to 498 in 554eadc
_.each(pkg.get("members"), function (solrResult, i) { | |
if (solrResult.get("formatType") != "DATA") return; | |
solrResult.set( | |
"formattedSize", | |
Utilities.bytesToSize(solrResult.get("size")), | |
); | |
//Add a section for the data details, just like the other attribute sections | |
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; | |
//Determine the icon type based on format id | |
var type = solrResult.getType(), | |
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 | |
.formatAttributeSection(solrResult, keys, header, "entitydetails") | |
.attr("data-id", solrResult.get("id")); | |
//Create an image thumbnail, if this is an image | |
if (type == "image") { | |
//var thumbnail = view.parentView.createThumbnail(solrResult.get("id")); | |
//$(entityDetailsSection).prepend(thumbnail); | |
} | |
//Mark this section with an anchor tag with the doc id | |
$(entityDetailsSection).prepend( | |
$(document.createElement("a")).attr( | |
"id", | |
solrResult.get("id").replace(/[^A-Za-z0-9]/g, "-"), | |
), | |
); | |
$(html).append(entityDetailsSection); | |
}); |
🚫 [eslint] <no-unused-vars> reported by reviewdog 🐶
'i' is defined but never used. Allowed unused args must match /^_/u.
metacatui/src/js/views/MetadataIndexView.js
Line 436 in 554eadc
_.each(pkg.get("members"), function (solrResult, i) { |
🚫 [eslint] <eqeqeq> reported by reviewdog 🐶
Expected '!==' and instead saw '!='.
metacatui/src/js/views/MetadataIndexView.js
Line 437 in 554eadc
if (solrResult.get("formatType") != "DATA") return; |
🚫 [eslint] <spaced-comment> reported by reviewdog 🐶
Expected exception block, space or tab after '//' in comment.
metacatui/src/js/views/MetadataIndexView.js
Line 444 in 554eadc
//Add a section for the data details, just like the other attribute sections |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/views/MetadataIndexView.js
Lines 445 to 452 in 554eadc
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/MetadataIndexView.js
Lines 445 to 452 in 554eadc
var keys = [ | |
"id", | |
{ field: "formattedSize", display: "size" }, | |
"views", | |
"pubDate", | |
"dataSource", | |
"formatId", | |
]; |
Unexpected unnamed function.
], function ($, _, Backbone, Utilities, Package, DownloadButtonView, Template) { |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/PackageTableView.js
Lines 9 to 597 in 554eadc
], function ($, _, Backbone, Utilities, Package, DownloadButtonView, Template) { | |
"use strict"; | |
var PackageTable = Backbone.View.extend({ | |
template: _.template(Template), | |
type: "PackageTable", | |
tagName: "div", | |
className: "download-contents", | |
events: { | |
"click .expand-control": "expand", | |
"click .collapse-control": "collapse", | |
}, | |
initialize: function (options) { | |
if (options === undefined || !options) var options = {}; | |
this.packageId = options.packageId || null; | |
this.memberId = options.memberId || null; | |
this.attributes = options.attributes || null; | |
this.className += options.className || ""; | |
this.currentlyViewing = options.currentlyViewing || null; | |
this.numVisible = options.numVisible || 4; | |
this.parentView = options.parentView || null; | |
this.title = options.title || ""; | |
this.nested = | |
typeof options.nested === "undefined" ? false : options.nested; | |
//Set up the Package model | |
if (typeof options.model === "undefined" || !options.model) { | |
this.model = new Package(); | |
this.model.set("memberId", this.memberId); | |
this.model.set("packageId", this.packageId); | |
} | |
if (!(typeof options.metricsModel == "undefined")) { | |
this.metricsModel = options.metricsModel; | |
} | |
//Get the members | |
if (this.packageId) this.model.getMembers(); | |
else if (this.memberId) this.model.getMembersByMemberID(this.memberId); | |
this.onMetadataView = | |
this.parentView && this.parentView.type == "Metadata"; | |
this.hasEntityDetails = | |
this.onMetadataView && | |
this.model.get("members") && | |
this.model.get("members").length < 150 | |
? this.parentView.hasEntityDetails() | |
: false; | |
this.listenTo(this.model, "changeAll", this.render); | |
}, | |
/* | |
* Creates a table of package/download contents that this metadata doc is a part of | |
*/ | |
render: function () { | |
var view = this, | |
members = this.model.get("members"); | |
//If the model isn't complete, we may be still waiting on a response from the index so don't render anything yet | |
if (!this.model.complete) return false; | |
//Start the HTML for the rows | |
var tbody = $(document.createElement("tbody")); | |
//Filter out the packages from the member list | |
members = _.filter(members, function (m) { | |
return m.type != "Package"; | |
}); | |
//Filter the members in order of preferred appearance | |
members = this.sort(members); | |
this.sortedMembers = members; | |
var metadata = this.model.getMetadata(); | |
//Count the number of rows in this table | |
var numRows = members.length; | |
//Cut down the members list to only those that will be visible | |
members = members.slice(0, this.numVisible); | |
this.rowsComplete = false; | |
//Create the HTML for each row | |
_.each(members, function (solrResult) { | |
//Append the row element | |
$(tbody).append(view.getMemberRow(solrResult)); | |
}); | |
var bodyRows = $(tbody).find("tr"); | |
this.numHidden = numRows - this.numVisible; | |
//Draw the footer which will have an expandable/collapsable control | |
if (this.numHidden > 0) { | |
var tfoot = $(document.createElement("tfoot")), | |
tfootRow = $(document.createElement("tr")), | |
tfootCell = $(document.createElement("th")).attr("colspan", "100%"), | |
item = this.numHidden == 1 ? "item" : "items", | |
expandLink = $(document.createElement("a")) | |
.addClass("expand-control control") | |
.text( | |
"Show " + this.numHidden + " more " + item + " in this data set", | |
), | |
expandIcon = $(document.createElement("i")).addClass( | |
"icon icon-caret-right icon-on-left", | |
), | |
collapseLink = $(document.createElement("a")) | |
.addClass("collapse-control control") | |
.text("Show less") | |
.css("display", "none"), | |
collapseIcon = $(document.createElement("i")).addClass( | |
"icon icon-caret-up icon-on-left", | |
); | |
$(tfoot).append(tfootRow); | |
$(tfootRow).append(tfootCell); | |
$(tfootCell).append(expandLink, collapseLink); | |
$(expandLink).prepend(expandIcon); | |
$(collapseLink).prepend(collapseIcon); | |
} | |
if (bodyRows.length == 0) { | |
tbody.html( | |
"<tr><td colspan='100%'>This is an empty dataset.</td></tr>", | |
); | |
} | |
if (!this.title && metadata) { | |
this.title = | |
'<a href="<%= MetacatUI.root %>/view/' + | |
encodeURIComponent(metadata.get("id")) + | |
'">Files in this dataset'; | |
if (this.model.get("id")) | |
this.title += | |
'<span class="subtle"> Package: ' + | |
this.model.get("id") + | |
"</span>"; | |
this.title += "</a>"; | |
} else if (!this.title && !metadata) { | |
this.title = "Files in this dataset"; | |
} | |
this.$el.html( | |
this.template({ | |
title: this.title || "Files in this dataset", | |
metadata: this.nested ? metadata : null, | |
colspan: bodyRows.first().find("td").length, | |
packageId: this.model.get("id"), | |
nested: this.nested, | |
}), | |
); | |
//Insert the Download All button | |
if (this.model.getURL() && this.model.get("id")) { | |
var downloadBtn = new DownloadButtonView({ model: this.model }); | |
downloadBtn.render(); | |
this.$(".download-container").append(downloadBtn.el); | |
} | |
//Add the table body and footer | |
this.$("thead").after(tbody); | |
if (typeof tfoot !== "undefined") this.$(tbody).after(tfoot); | |
return this; | |
}, | |
sort: function (models) { | |
//Default to the package model members as the models to sort | |
if (!models) { | |
var models = this.model.get("members"); | |
//If this model doesn't have members, return an empty array or a falsey value | |
if (!models) return models; | |
} | |
// One == already sorted! | |
if (models.length == 1) return models; | |
//If there are too many models to sort (takes too much time) then just get the metadata to display first | |
else if (models.length > 150) { | |
var view = this; | |
//Find the metadata doc we are currently viewing | |
var currentMetadata = _.find(models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
//Add it to the front | |
if (currentMetadata) { | |
models = _.without(models, currentMetadata); | |
models.unshift(currentMetadata); | |
} | |
//Return the newly sorted array | |
return models; | |
} | |
var view = this, | |
metadataView = this.onMetadataView ? this.parentView : null; | |
//** If this is not a nested package AND the parent view is the metadata view, then sort by order of appearance in the metadata **/ | |
if ( | |
!this.nested && | |
metadataView && | |
!_.findWhere(metadataView.subviews, { type: "MetadataIndex" }) | |
) { | |
if (metadataView.hasEntityDetails()) { | |
//If we are currently viewing a metadata document, find it | |
if (this.currentlyViewing) | |
var currentMetadata = _.find(models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
//For each model, find its position on the Metadata View page | |
var numNotFound = 0; | |
_.each(models, function (model) { | |
if (currentMetadata == model) return; | |
var container = view.parentView.findEntityDetailsContainer(model); | |
if (container) model.offsetTop = $(container)[0].offsetTop; | |
else { | |
model.offsetTop = window.outerHeight; | |
numNotFound++; | |
} | |
}); | |
//Continue only if we found the entity details section for at least one model, if not, sort by the default method later | |
if (numNotFound < models.length - 1) { | |
//Minus 1 since we don't count the metadata | |
//Sort the models by this position | |
models = _.sortBy(models, "offsetTop"); | |
//Move the metadata model that we are currently viewing in the Metadata view to the top | |
if (currentMetadata) { | |
models = _.without(models, currentMetadata); | |
models.unshift(currentMetadata); | |
} | |
//Flatten the array in case we have nesting | |
models = _.flatten(models); | |
//Return the sorted array | |
return models; | |
} | |
} | |
} | |
//** For tables with no accompanying metadata (nested or not on the Metadata View), default to sorting by group then alpha by name**/ | |
//Split the members of this package into groups based on their format type (metaata, data, image, code, etc) | |
var groupedModels = _.groupBy(models, function (m) { | |
if (!m.get("type") || typeof m.get("type") == "undefined") | |
return "data"; | |
return m.get("type"); | |
}), | |
sortedModels = []; | |
var rowOrder = [ | |
"metadata", | |
"image", | |
"PDF", | |
"program", | |
"data", | |
"annotation", | |
]; | |
for (var i = 0; i < rowOrder.length; i++) { | |
//Sort the members/rows alphabetically within each group | |
/*models = _.sortBy(models, function(m){ | |
if(m.get("type") == "metadata") return "!"; //Always display metadata first since it will have the title in the table | |
return m.get("type"); | |
}); */ | |
var group = groupedModels[rowOrder[i]]; | |
group = _.sortBy(group, function (m) { | |
return m.get("fileName") || m.get("id"); | |
}); | |
sortedModels.push(group); | |
} | |
models = _.flatten(sortedModels); | |
return models; | |
}, | |
getMemberRow: function (memberModel, options) { | |
var formatType = memberModel.get("formatType"), | |
type = memberModel.type == "Package" ? "data" : memberModel.getType(), | |
id = memberModel.get("id"), | |
entityName = memberModel.get("fileName"), | |
url = memberModel.get("url"), | |
hidden = typeof options === "undefined" ? false : options.hidden, | |
collapsable = hidden | |
? true | |
: typeof options === "undefined" | |
? false | |
: options.collapsable; | |
if (!url) { | |
memberModel.setURL(); | |
url = memberModel.get("url"); | |
} | |
//Use the metadata title instead of the ID | |
if (!entityName && formatType == "METADATA") | |
entityName = memberModel.get("title"); | |
if (formatType == "METADATA" && entityName) | |
entityName = "Metadata: " + entityName; | |
else if (formatType == "METADATA" && !entityName) entityName = "Metadata"; | |
//Display the id in the table if not name is present | |
if (typeof entityName === "undefined" || !entityName) entityName = id; | |
//Create a row for this member of the data package | |
var tr = $(document.createElement("tr")); | |
//Icon cell (based on formatType) | |
var iconCell = $(document.createElement("td")).addClass("format-type"), | |
formatTypeIcon = document.createElement("i"), | |
icon = "icon-table"; | |
//Determine the icon type based on format type | |
if (type == "program") icon = "icon-code"; | |
else if (type == "data") icon = "icon-table"; | |
else if (type == "metadata") icon = "icon-file-text"; | |
else if (type == "image") icon = "icon-picture"; | |
else if (type == "pdf") icon = "icon-file pdf"; | |
$(formatTypeIcon) | |
.addClass(icon) | |
.tooltip({ | |
placement: "top", | |
trigger: "hover focus", | |
title: type.charAt(0).toUpperCase() + type.slice(1), | |
}); | |
$(iconCell).html(formatTypeIcon); | |
$(tr).append(iconCell); | |
//Name cell | |
var nameCell = $(document.createElement("td")).addClass( | |
"name wrap-contents", | |
); | |
var nameEl = $(document.createElement("span")).text(entityName); | |
$(nameCell).html(nameEl); | |
$(tr).append(nameCell); | |
if (entityName == id) | |
$(nameCell).addClass("entity-name-placeholder").attr("data-id", id); | |
//"More info" cell | |
var moreInfoCell = $(document.createElement("td")).addClass("more-info"); | |
//If we are on the metadata view and there is no entity details section, then append a blank cell | |
var entityDetails = this.hasEntityDetails | |
? this.parentView.findEntityDetailsContainer(memberModel) | |
: false, | |
currentlyViewing = id == this.currentlyViewing; | |
if ( | |
(this.onMetadataView && !this.hasEntityDetails) || | |
(this.onMetadataView && !entityDetails) || | |
currentlyViewing || | |
this.nested | |
) { | |
$(tr).append(moreInfoCell); | |
} else { | |
let metadataId = | |
this.onMetadataView && this.currentlyViewing | |
? this.currentlyViewing | |
: memberModel.get("isDocumentedBy")[0]; | |
var moreInfo = $(document.createElement("a")) | |
.attr( | |
"href", | |
MetacatUI.root + | |
"/view/" + | |
encodeURIComponent(metadataId) + | |
"#" + | |
encodeURIComponent(id), | |
) | |
.addClass("preview") | |
.attr("data-id", id) | |
.text("More info"); | |
$(moreInfoCell).append(moreInfo); | |
} | |
$(tr).append(moreInfoCell); | |
//Format id cell | |
var fileTypeCell = $(document.createElement("td")).addClass( | |
"formatId wrap-contents", | |
); | |
$(fileTypeCell).html(memberModel.getFormat()); | |
$(tr).append(fileTypeCell); | |
//File size cell | |
var sizeCell = $(document.createElement("td")).addClass("size"); | |
var size = Utilities.bytesToSize(memberModel.get("size")); | |
memberModel.set("sizeStr", size); | |
$(sizeCell).text(size); | |
$(tr).append(sizeCell); | |
if (MetacatUI.appModel.get("displayDatasetMetrics")) { | |
// Retreiving the Package Metrics Counts from the Metrics Model | |
// Adding a Metric Cell for the corresponding DataONE object in the table | |
var readsCell = $(document.createElement("td")) | |
.addClass("metrics-count downloads") | |
.attr("data-id", id); | |
$(tr).append(readsCell); | |
if (!memberModel.hideMetrics()) { | |
// If the model has already been fethced. | |
if (this.metricsModel.get("views") !== null) { | |
readsCell.append(this.getMemberRowMetrics(id, formatType)); | |
} else { | |
// Update the metrics later on | |
// If the fetch() is still in progress. | |
this.listenTo(this.metricsModel, "sync", function () { | |
var readsCell = this.$( | |
'.metrics-count.downloads[data-id="' + id + '"]', | |
); | |
readsCell.text(this.getMemberRowMetrics(id, formatType)); | |
}); | |
} | |
} | |
} | |
//Download button cell | |
var downloadBtnCell = $(document.createElement("td")).addClass( | |
"download-btn btn-container", | |
); | |
var downloadButton = new DownloadButtonView({ model: memberModel }); | |
downloadButton.render(); | |
$(downloadBtnCell).append(downloadButton.el); | |
$(tr).append(downloadBtnCell); | |
if (collapsable) tr.addClass("collapse"); | |
if (hidden) tr.css("display", "none"); | |
return tr; | |
}, | |
// Member row metrics for the package table | |
// Retrieving information from the Metrics Model's result details | |
getMemberRowMetrics: function (id, formatType) { | |
if (typeof this.metricsModel !== "undefined") { | |
var metricsResultDetails = this.metricsModel.get("resultDetails"); | |
if ( | |
typeof metricsResultDetails !== "undefined" && | |
metricsResultDetails | |
) { | |
var metricsPackageDetails = | |
metricsResultDetails["metrics_package_counts"]; | |
var objectLevelMetrics = metricsPackageDetails[id]; | |
if (typeof objectLevelMetrics !== "undefined") { | |
if (formatType == "METADATA") { | |
var reads = objectLevelMetrics["viewCount"]; | |
} else { | |
var reads = objectLevelMetrics["downloadCount"]; | |
} | |
} else { | |
var reads = 0; | |
} | |
} else { | |
var reads = 0; | |
} | |
} | |
if (typeof reads !== "undefined" && reads) { | |
// giving labels | |
if (formatType == "METADATA" && reads == 1) reads += " view"; | |
else if (formatType == "METADATA") reads += " views"; | |
else if (reads == 1) reads += " download"; | |
else reads += " downloads"; | |
} else { | |
// returning an empty string if the metrics are 0 | |
reads = ""; | |
} | |
return reads; | |
}, | |
expand: function (e) { | |
//Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
//If this is a nested dataset, we need to actually draw the remaining rows | |
if (!this.rowsComplete) { | |
var tbody = this.$("tbody"); | |
//Create the HTML for each row | |
var members = this.sortedMembers.slice(this.numVisible); | |
_.each(members, function (solrResult) { | |
//Append the row element | |
$(tbody).append(view.getMemberRow(solrResult, { collapsable: true })); | |
}); | |
//Make the view as complete so we don't do this again | |
this.rowsComplete = true; | |
} | |
this.$("tr.collapse").fadeIn(); | |
this.$(".expand-control").fadeOut(function () { | |
view.$(".collapse-control").fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
collapse: function (e) { | |
//Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
this.$("tr.collapse").fadeOut(); | |
this.$(".collapse-control").fadeOut(function () { | |
view.$(".expand-control").fadeIn(); | |
}); | |
}, | |
checkForPrivateMembers: function () { | |
try { | |
var packageModel = this.model, | |
packageCollection = this.dataPackageCollection; | |
if (!packageModel || !packageCollection) { | |
return; | |
} | |
//Get the number of package members found in Solr and parsed from the RDF XML | |
var numMembersFromSolr = packageModel.get("members").length, | |
numMembersFromRDF = packageCollection.length; | |
//If there are more package members in the RDF XML tthan found in SOlr, we | |
// can assume that those objects are private. | |
if (numMembersFromRDF > numMembersFromSolr) { | |
var downloadButtons = this.$(".btn.download"); | |
for (var i = 0; i < downloadButtons.length; i++) { | |
var btn = downloadButtons[i]; | |
//Find the Download All button for the package | |
var downloadURL = $(btn).attr("href"); | |
if ( | |
downloadURL.indexOf(packageModel.get("id")) > -1 || | |
downloadURL.indexOf(encodeURIComponent(packageModel.get("id"))) > | |
-1 | |
) { | |
//Disable this download button | |
$(btn) | |
.attr("disabled", "disabled") | |
.addClass("disabled") | |
.attr("href", "") | |
.tooltip({ | |
trigger: "hover", | |
placement: "top", | |
delay: 500, | |
title: | |
"This dataset may contain private data, so each data file should be downloaded individually.", | |
}); | |
i = downloadButtons.length; | |
} | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
}, | |
/*showDownloadProgress: function(e){ | |
e.preventDefault(); | |
var button = $(e.target); | |
button.addClass("in-progress"); | |
button.html("Downloading... <i class='icon icon-on-right icon-spinner icon-spin'></i>"); | |
return true; | |
}*/ | |
}); | |
return PackageTable; | |
}); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/PackageTableView.js
Lines 12 to 594 in 554eadc
var PackageTable = Backbone.View.extend({ | |
template: _.template(Template), | |
type: "PackageTable", | |
tagName: "div", | |
className: "download-contents", | |
events: { | |
"click .expand-control": "expand", | |
"click .collapse-control": "collapse", | |
}, | |
initialize: function (options) { | |
if (options === undefined || !options) var options = {}; | |
this.packageId = options.packageId || null; | |
this.memberId = options.memberId || null; | |
this.attributes = options.attributes || null; | |
this.className += options.className || ""; | |
this.currentlyViewing = options.currentlyViewing || null; | |
this.numVisible = options.numVisible || 4; | |
this.parentView = options.parentView || null; | |
this.title = options.title || ""; | |
this.nested = | |
typeof options.nested === "undefined" ? false : options.nested; | |
//Set up the Package model | |
if (typeof options.model === "undefined" || !options.model) { | |
this.model = new Package(); | |
this.model.set("memberId", this.memberId); | |
this.model.set("packageId", this.packageId); | |
} | |
if (!(typeof options.metricsModel == "undefined")) { | |
this.metricsModel = options.metricsModel; | |
} | |
//Get the members | |
if (this.packageId) this.model.getMembers(); | |
else if (this.memberId) this.model.getMembersByMemberID(this.memberId); | |
this.onMetadataView = | |
this.parentView && this.parentView.type == "Metadata"; | |
this.hasEntityDetails = | |
this.onMetadataView && | |
this.model.get("members") && | |
this.model.get("members").length < 150 | |
? this.parentView.hasEntityDetails() | |
: false; | |
this.listenTo(this.model, "changeAll", this.render); | |
}, | |
/* | |
* Creates a table of package/download contents that this metadata doc is a part of | |
*/ | |
render: function () { | |
var view = this, | |
members = this.model.get("members"); | |
//If the model isn't complete, we may be still waiting on a response from the index so don't render anything yet | |
if (!this.model.complete) return false; | |
//Start the HTML for the rows | |
var tbody = $(document.createElement("tbody")); | |
//Filter out the packages from the member list | |
members = _.filter(members, function (m) { | |
return m.type != "Package"; | |
}); | |
//Filter the members in order of preferred appearance | |
members = this.sort(members); | |
this.sortedMembers = members; | |
var metadata = this.model.getMetadata(); | |
//Count the number of rows in this table | |
var numRows = members.length; | |
//Cut down the members list to only those that will be visible | |
members = members.slice(0, this.numVisible); | |
this.rowsComplete = false; | |
//Create the HTML for each row | |
_.each(members, function (solrResult) { | |
//Append the row element | |
$(tbody).append(view.getMemberRow(solrResult)); | |
}); | |
var bodyRows = $(tbody).find("tr"); | |
this.numHidden = numRows - this.numVisible; | |
//Draw the footer which will have an expandable/collapsable control | |
if (this.numHidden > 0) { | |
var tfoot = $(document.createElement("tfoot")), | |
tfootRow = $(document.createElement("tr")), | |
tfootCell = $(document.createElement("th")).attr("colspan", "100%"), | |
item = this.numHidden == 1 ? "item" : "items", | |
expandLink = $(document.createElement("a")) | |
.addClass("expand-control control") | |
.text( | |
"Show " + this.numHidden + " more " + item + " in this data set", | |
), | |
expandIcon = $(document.createElement("i")).addClass( | |
"icon icon-caret-right icon-on-left", | |
), | |
collapseLink = $(document.createElement("a")) | |
.addClass("collapse-control control") | |
.text("Show less") | |
.css("display", "none"), | |
collapseIcon = $(document.createElement("i")).addClass( | |
"icon icon-caret-up icon-on-left", | |
); | |
$(tfoot).append(tfootRow); | |
$(tfootRow).append(tfootCell); | |
$(tfootCell).append(expandLink, collapseLink); | |
$(expandLink).prepend(expandIcon); | |
$(collapseLink).prepend(collapseIcon); | |
} | |
if (bodyRows.length == 0) { | |
tbody.html( | |
"<tr><td colspan='100%'>This is an empty dataset.</td></tr>", | |
); | |
} | |
if (!this.title && metadata) { | |
this.title = | |
'<a href="<%= MetacatUI.root %>/view/' + | |
encodeURIComponent(metadata.get("id")) + | |
'">Files in this dataset'; | |
if (this.model.get("id")) | |
this.title += | |
'<span class="subtle"> Package: ' + | |
this.model.get("id") + | |
"</span>"; | |
this.title += "</a>"; | |
} else if (!this.title && !metadata) { | |
this.title = "Files in this dataset"; | |
} | |
this.$el.html( | |
this.template({ | |
title: this.title || "Files in this dataset", | |
metadata: this.nested ? metadata : null, | |
colspan: bodyRows.first().find("td").length, | |
packageId: this.model.get("id"), | |
nested: this.nested, | |
}), | |
); | |
//Insert the Download All button | |
if (this.model.getURL() && this.model.get("id")) { | |
var downloadBtn = new DownloadButtonView({ model: this.model }); | |
downloadBtn.render(); | |
this.$(".download-container").append(downloadBtn.el); | |
} | |
//Add the table body and footer | |
this.$("thead").after(tbody); | |
if (typeof tfoot !== "undefined") this.$(tbody).after(tfoot); | |
return this; | |
}, | |
sort: function (models) { | |
//Default to the package model members as the models to sort | |
if (!models) { | |
var models = this.model.get("members"); | |
//If this model doesn't have members, return an empty array or a falsey value | |
if (!models) return models; | |
} | |
// One == already sorted! | |
if (models.length == 1) return models; | |
//If there are too many models to sort (takes too much time) then just get the metadata to display first | |
else if (models.length > 150) { | |
var view = this; | |
//Find the metadata doc we are currently viewing | |
var currentMetadata = _.find(models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
//Add it to the front | |
if (currentMetadata) { | |
models = _.without(models, currentMetadata); | |
models.unshift(currentMetadata); | |
} | |
//Return the newly sorted array | |
return models; | |
} | |
var view = this, | |
metadataView = this.onMetadataView ? this.parentView : null; | |
//** If this is not a nested package AND the parent view is the metadata view, then sort by order of appearance in the metadata **/ | |
if ( | |
!this.nested && | |
metadataView && | |
!_.findWhere(metadataView.subviews, { type: "MetadataIndex" }) | |
) { | |
if (metadataView.hasEntityDetails()) { | |
//If we are currently viewing a metadata document, find it | |
if (this.currentlyViewing) | |
var currentMetadata = _.find(models, function (m) { | |
return m.get("id") == view.currentlyViewing; | |
}); | |
//For each model, find its position on the Metadata View page | |
var numNotFound = 0; | |
_.each(models, function (model) { | |
if (currentMetadata == model) return; | |
var container = view.parentView.findEntityDetailsContainer(model); | |
if (container) model.offsetTop = $(container)[0].offsetTop; | |
else { | |
model.offsetTop = window.outerHeight; | |
numNotFound++; | |
} | |
}); | |
//Continue only if we found the entity details section for at least one model, if not, sort by the default method later | |
if (numNotFound < models.length - 1) { | |
//Minus 1 since we don't count the metadata | |
//Sort the models by this position | |
models = _.sortBy(models, "offsetTop"); | |
//Move the metadata model that we are currently viewing in the Metadata view to the top | |
if (currentMetadata) { | |
models = _.without(models, currentMetadata); | |
models.unshift(currentMetadata); | |
} | |
//Flatten the array in case we have nesting | |
models = _.flatten(models); | |
//Return the sorted array | |
return models; | |
} | |
} | |
} | |
//** For tables with no accompanying metadata (nested or not on the Metadata View), default to sorting by group then alpha by name**/ | |
//Split the members of this package into groups based on their format type (metaata, data, image, code, etc) | |
var groupedModels = _.groupBy(models, function (m) { | |
if (!m.get("type") || typeof m.get("type") == "undefined") | |
return "data"; | |
return m.get("type"); | |
}), | |
sortedModels = []; | |
var rowOrder = [ | |
"metadata", | |
"image", | |
"PDF", | |
"program", | |
"data", | |
"annotation", | |
]; | |
for (var i = 0; i < rowOrder.length; i++) { | |
//Sort the members/rows alphabetically within each group | |
/*models = _.sortBy(models, function(m){ | |
if(m.get("type") == "metadata") return "!"; //Always display metadata first since it will have the title in the table | |
return m.get("type"); | |
}); */ | |
var group = groupedModels[rowOrder[i]]; | |
group = _.sortBy(group, function (m) { | |
return m.get("fileName") || m.get("id"); | |
}); | |
sortedModels.push(group); | |
} | |
models = _.flatten(sortedModels); | |
return models; | |
}, | |
getMemberRow: function (memberModel, options) { | |
var formatType = memberModel.get("formatType"), | |
type = memberModel.type == "Package" ? "data" : memberModel.getType(), | |
id = memberModel.get("id"), | |
entityName = memberModel.get("fileName"), | |
url = memberModel.get("url"), | |
hidden = typeof options === "undefined" ? false : options.hidden, | |
collapsable = hidden | |
? true | |
: typeof options === "undefined" | |
? false | |
: options.collapsable; | |
if (!url) { | |
memberModel.setURL(); | |
url = memberModel.get("url"); | |
} | |
//Use the metadata title instead of the ID | |
if (!entityName && formatType == "METADATA") | |
entityName = memberModel.get("title"); | |
if (formatType == "METADATA" && entityName) | |
entityName = "Metadata: " + entityName; | |
else if (formatType == "METADATA" && !entityName) entityName = "Metadata"; | |
//Display the id in the table if not name is present | |
if (typeof entityName === "undefined" || !entityName) entityName = id; | |
//Create a row for this member of the data package | |
var tr = $(document.createElement("tr")); | |
//Icon cell (based on formatType) | |
var iconCell = $(document.createElement("td")).addClass("format-type"), | |
formatTypeIcon = document.createElement("i"), | |
icon = "icon-table"; | |
//Determine the icon type based on format type | |
if (type == "program") icon = "icon-code"; | |
else if (type == "data") icon = "icon-table"; | |
else if (type == "metadata") icon = "icon-file-text"; | |
else if (type == "image") icon = "icon-picture"; | |
else if (type == "pdf") icon = "icon-file pdf"; | |
$(formatTypeIcon) | |
.addClass(icon) | |
.tooltip({ | |
placement: "top", | |
trigger: "hover focus", | |
title: type.charAt(0).toUpperCase() + type.slice(1), | |
}); | |
$(iconCell).html(formatTypeIcon); | |
$(tr).append(iconCell); | |
//Name cell | |
var nameCell = $(document.createElement("td")).addClass( | |
"name wrap-contents", | |
); | |
var nameEl = $(document.createElement("span")).text(entityName); | |
$(nameCell).html(nameEl); | |
$(tr).append(nameCell); | |
if (entityName == id) | |
$(nameCell).addClass("entity-name-placeholder").attr("data-id", id); | |
//"More info" cell | |
var moreInfoCell = $(document.createElement("td")).addClass("more-info"); | |
//If we are on the metadata view and there is no entity details section, then append a blank cell | |
var entityDetails = this.hasEntityDetails | |
? this.parentView.findEntityDetailsContainer(memberModel) | |
: false, | |
currentlyViewing = id == this.currentlyViewing; | |
if ( | |
(this.onMetadataView && !this.hasEntityDetails) || | |
(this.onMetadataView && !entityDetails) || | |
currentlyViewing || | |
this.nested | |
) { | |
$(tr).append(moreInfoCell); | |
} else { | |
let metadataId = | |
this.onMetadataView && this.currentlyViewing | |
? this.currentlyViewing | |
: memberModel.get("isDocumentedBy")[0]; | |
var moreInfo = $(document.createElement("a")) | |
.attr( | |
"href", | |
MetacatUI.root + | |
"/view/" + | |
encodeURIComponent(metadataId) + | |
"#" + | |
encodeURIComponent(id), | |
) | |
.addClass("preview") | |
.attr("data-id", id) | |
.text("More info"); | |
$(moreInfoCell).append(moreInfo); | |
} | |
$(tr).append(moreInfoCell); | |
//Format id cell | |
var fileTypeCell = $(document.createElement("td")).addClass( | |
"formatId wrap-contents", | |
); | |
$(fileTypeCell).html(memberModel.getFormat()); | |
$(tr).append(fileTypeCell); | |
//File size cell | |
var sizeCell = $(document.createElement("td")).addClass("size"); | |
var size = Utilities.bytesToSize(memberModel.get("size")); | |
memberModel.set("sizeStr", size); | |
$(sizeCell).text(size); | |
$(tr).append(sizeCell); | |
if (MetacatUI.appModel.get("displayDatasetMetrics")) { | |
// Retreiving the Package Metrics Counts from the Metrics Model | |
// Adding a Metric Cell for the corresponding DataONE object in the table | |
var readsCell = $(document.createElement("td")) | |
.addClass("metrics-count downloads") | |
.attr("data-id", id); | |
$(tr).append(readsCell); | |
if (!memberModel.hideMetrics()) { | |
// If the model has already been fethced. | |
if (this.metricsModel.get("views") !== null) { | |
readsCell.append(this.getMemberRowMetrics(id, formatType)); | |
} else { | |
// Update the metrics later on | |
// If the fetch() is still in progress. | |
this.listenTo(this.metricsModel, "sync", function () { | |
var readsCell = this.$( | |
'.metrics-count.downloads[data-id="' + id + '"]', | |
); | |
readsCell.text(this.getMemberRowMetrics(id, formatType)); | |
}); | |
} | |
} | |
} | |
//Download button cell | |
var downloadBtnCell = $(document.createElement("td")).addClass( | |
"download-btn btn-container", | |
); | |
var downloadButton = new DownloadButtonView({ model: memberModel }); | |
downloadButton.render(); | |
$(downloadBtnCell).append(downloadButton.el); | |
$(tr).append(downloadBtnCell); | |
if (collapsable) tr.addClass("collapse"); | |
if (hidden) tr.css("display", "none"); | |
return tr; | |
}, | |
// Member row metrics for the package table | |
// Retrieving information from the Metrics Model's result details | |
getMemberRowMetrics: function (id, formatType) { | |
if (typeof this.metricsModel !== "undefined") { | |
var metricsResultDetails = this.metricsModel.get("resultDetails"); | |
if ( | |
typeof metricsResultDetails !== "undefined" && | |
metricsResultDetails | |
) { | |
var metricsPackageDetails = | |
metricsResultDetails["metrics_package_counts"]; | |
var objectLevelMetrics = metricsPackageDetails[id]; | |
if (typeof objectLevelMetrics !== "undefined") { | |
if (formatType == "METADATA") { | |
var reads = objectLevelMetrics["viewCount"]; | |
} else { | |
var reads = objectLevelMetrics["downloadCount"]; | |
} | |
} else { | |
var reads = 0; | |
} | |
} else { | |
var reads = 0; | |
} | |
} | |
if (typeof reads !== "undefined" && reads) { | |
// giving labels | |
if (formatType == "METADATA" && reads == 1) reads += " view"; | |
else if (formatType == "METADATA") reads += " views"; | |
else if (reads == 1) reads += " download"; | |
else reads += " downloads"; | |
} else { | |
// returning an empty string if the metrics are 0 | |
reads = ""; | |
} | |
return reads; | |
}, | |
expand: function (e) { | |
//Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
//If this is a nested dataset, we need to actually draw the remaining rows | |
if (!this.rowsComplete) { | |
var tbody = this.$("tbody"); | |
//Create the HTML for each row | |
var members = this.sortedMembers.slice(this.numVisible); | |
_.each(members, function (solrResult) { | |
//Append the row element | |
$(tbody).append(view.getMemberRow(solrResult, { collapsable: true })); | |
}); | |
//Make the view as complete so we don't do this again | |
this.rowsComplete = true; | |
} | |
this.$("tr.collapse").fadeIn(); | |
this.$(".expand-control").fadeOut(function () { | |
view.$(".collapse-control").fadeIn("fast"); | |
view.$(".tooltip-this").tooltip(); | |
}); | |
}, | |
collapse: function (e) { | |
//Don't do anything... | |
e.preventDefault(); | |
var view = this; | |
this.$("tr.collapse").fadeOut(); | |
this.$(".collapse-control").fadeOut(function () { | |
view.$(".expand-control").fadeIn(); | |
}); | |
}, | |
checkForPrivateMembers: function () { | |
try { | |
var packageModel = this.model, | |
packageCollection = this.dataPackageCollection; | |
if (!packageModel || !packageCollection) { | |
return; | |
} | |
//Get the number of package members found in Solr and parsed from the RDF XML | |
var numMembersFromSolr = packageModel.get("members").length, | |
numMembersFromRDF = packageCollection.length; | |
//If there are more package members in the RDF XML tthan found in SOlr, we | |
// can assume that those objects are private. | |
if (numMembersFromRDF > numMembersFromSolr) { | |
var downloadButtons = this.$(".btn.download"); | |
for (var i = 0; i < downloadButtons.length; i++) { | |
var btn = downloadButtons[i]; | |
//Find the Download All button for the package | |
var downloadURL = $(btn).attr("href"); | |
if ( | |
downloadURL.indexOf(packageModel.get("id")) > -1 || | |
downloadURL.indexOf(encodeURIComponent(packageModel.get("id"))) > | |
-1 | |
) { | |
//Disable this download button | |
$(btn) | |
.attr("disabled", "disabled") | |
.addClass("disabled") | |
.attr("href", "") | |
.tooltip({ | |
trigger: "hover", | |
placement: "top", | |
delay: 500, | |
title: | |
"This dataset may contain private data, so each data file should be downloaded individually.", | |
}); | |
i = downloadButtons.length; | |
} | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
} | |
}, | |
/*showDownloadProgress: function(e){ | |
e.preventDefault(); | |
var button = $(e.target); | |
button.addClass("in-progress"); | |
button.html("Downloading... <i class='icon icon-on-right icon-spinner icon-spin'></i>"); | |
return true; | |
}*/ | |
}); |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/PackageTableView.js
Lines 297 to 450 in 554eadc
getMemberRow: function (memberModel, options) { | |
var formatType = memberModel.get("formatType"), | |
type = memberModel.type == "Package" ? "data" : memberModel.getType(), | |
id = memberModel.get("id"), | |
entityName = memberModel.get("fileName"), | |
url = memberModel.get("url"), | |
hidden = typeof options === "undefined" ? false : options.hidden, | |
collapsable = hidden | |
? true | |
: typeof options === "undefined" | |
? false | |
: options.collapsable; | |
if (!url) { | |
memberModel.setURL(); | |
url = memberModel.get("url"); | |
} | |
//Use the metadata title instead of the ID | |
if (!entityName && formatType == "METADATA") | |
entityName = memberModel.get("title"); | |
if (formatType == "METADATA" && entityName) | |
entityName = "Metadata: " + entityName; | |
else if (formatType == "METADATA" && !entityName) entityName = "Metadata"; | |
//Display the id in the table if not name is present | |
if (typeof entityName === "undefined" || !entityName) entityName = id; | |
//Create a row for this member of the data package | |
var tr = $(document.createElement("tr")); | |
//Icon cell (based on formatType) | |
var iconCell = $(document.createElement("td")).addClass("format-type"), | |
formatTypeIcon = document.createElement("i"), | |
icon = "icon-table"; | |
//Determine the icon type based on format type | |
if (type == "program") icon = "icon-code"; | |
else if (type == "data") icon = "icon-table"; | |
else if (type == "metadata") icon = "icon-file-text"; | |
else if (type == "image") icon = "icon-picture"; | |
else if (type == "pdf") icon = "icon-file pdf"; | |
$(formatTypeIcon) | |
.addClass(icon) | |
.tooltip({ | |
placement: "top", | |
trigger: "hover focus", | |
title: type.charAt(0).toUpperCase() + type.slice(1), | |
}); | |
$(iconCell).html(formatTypeIcon); | |
$(tr).append(iconCell); | |
//Name cell | |
var nameCell = $(document.createElement("td")).addClass( | |
"name wrap-contents", | |
); | |
var nameEl = $(document.createElement("span")).text(entityName); | |
$(nameCell).html(nameEl); | |
$(tr).append(nameCell); | |
if (entityName == id) | |
$(nameCell).addClass("entity-name-placeholder").attr("data-id", id); | |
//"More info" cell | |
var moreInfoCell = $(document.createElement("td")).addClass("more-info"); | |
//If we are on the metadata view and there is no entity details section, then append a blank cell | |
var entityDetails = this.hasEntityDetails | |
? this.parentView.findEntityDetailsContainer(memberModel) | |
: false, | |
currentlyViewing = id == this.currentlyViewing; | |
if ( | |
(this.onMetadataView && !this.hasEntityDetails) || | |
(this.onMetadataView && !entityDetails) || | |
currentlyViewing || | |
this.nested | |
) { | |
$(tr).append(moreInfoCell); | |
} else { | |
let metadataId = | |
this.onMetadataView && this.currentlyViewing | |
? this.currentlyViewing | |
: memberModel.get("isDocumentedBy")[0]; | |
var moreInfo = $(document.createElement("a")) | |
.attr( | |
"href", | |
MetacatUI.root + | |
"/view/" + | |
encodeURIComponent(metadataId) + | |
"#" + | |
encodeURIComponent(id), | |
) | |
.addClass("preview") | |
.attr("data-id", id) | |
.text("More info"); | |
$(moreInfoCell).append(moreInfo); | |
} | |
$(tr).append(moreInfoCell); | |
//Format id cell | |
var fileTypeCell = $(document.createElement("td")).addClass( | |
"formatId wrap-contents", | |
); | |
$(fileTypeCell).html(memberModel.getFormat()); | |
$(tr).append(fileTypeCell); | |
//File size cell | |
var sizeCell = $(document.createElement("td")).addClass("size"); | |
var size = Utilities.bytesToSize(memberModel.get("size")); | |
memberModel.set("sizeStr", size); | |
$(sizeCell).text(size); | |
$(tr).append(sizeCell); | |
if (MetacatUI.appModel.get("displayDatasetMetrics")) { | |
// Retreiving the Package Metrics Counts from the Metrics Model | |
// Adding a Metric Cell for the corresponding DataONE object in the table | |
var readsCell = $(document.createElement("td")) | |
.addClass("metrics-count downloads") | |
.attr("data-id", id); | |
$(tr).append(readsCell); | |
if (!memberModel.hideMetrics()) { | |
// If the model has already been fethced. | |
if (this.metricsModel.get("views") !== null) { | |
readsCell.append(this.getMemberRowMetrics(id, formatType)); | |
} else { | |
// Update the metrics later on | |
// If the fetch() is still in progress. | |
this.listenTo(this.metricsModel, "sync", function () { | |
var readsCell = this.$( | |
'.metrics-count.downloads[data-id="' + id + '"]', | |
); | |
readsCell.text(this.getMemberRowMetrics(id, formatType)); | |
}); | |
} | |
} | |
} | |
//Download button cell | |
var downloadBtnCell = $(document.createElement("td")).addClass( | |
"download-btn btn-container", | |
); | |
var downloadButton = new DownloadButtonView({ model: memberModel }); | |
downloadButton.render(); | |
$(downloadBtnCell).append(downloadButton.el); | |
$(tr).append(downloadBtnCell); | |
if (collapsable) tr.addClass("collapse"); | |
if (hidden) tr.css("display", "none"); | |
return tr; | |
}, |
🚫 [eslint] <spaced-comment> reported by reviewdog 🐶
Expected exception block, space or tab after '//' in comment.
metacatui/src/js/views/PackageTableView.js
Line 403 in 554eadc
//File size cell |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/views/PackageTableView.js
Line 404 in 554eadc
var sizeCell = $(document.createElement("td")).addClass("size"); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/PackageTableView.js
Line 404 in 554eadc
var sizeCell = $(document.createElement("td")).addClass("size"); |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/views/PackageTableView.js
Line 405 in 554eadc
var size = Utilities.bytesToSize(memberModel.get("size")); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/PackageTableView.js
Line 405 in 554eadc
var size = Utilities.bytesToSize(memberModel.get("size")); |
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/views/StatsView.js
Lines 21 to 1187 in 554eadc
], function ( | |
$, | |
_, | |
Backbone, | |
d3, | |
LineChart, | |
BarChart, | |
DonutChart, | |
CircleBadge, | |
Citations, | |
Utilities, | |
MetricsModel, | |
StatsModel, | |
MetricsChart, | |
CitationList, | |
MetricModalTemplate, | |
profileTemplate, | |
AlertTemplate, | |
LoadingTemplate, | |
MetricsLoadingTemplate, | |
) { | |
"use strict"; | |
var StatsView = Backbone.View.extend( | |
/** @lends StatsView.prototype */ { | |
el: "#Content", | |
model: null, | |
hideUpdatesChart: false, | |
/* | |
* Flag to indicate whether the statsview is a node summary view | |
* @type {boolean} | |
*/ | |
nodeSummaryView: false, | |
/** | |
* Whether or not to show the graph that indicated the assessment score for all metadata in the query. | |
* @type {boolean} | |
*/ | |
hideMetadataAssessment: false, | |
subviews: [], | |
template: _.template(profileTemplate), | |
metricTemplate: _.template(MetricModalTemplate), | |
alertTemplate: _.template(AlertTemplate), | |
loadingTemplate: _.template(LoadingTemplate), | |
metricsLoadingTemplate: _.template(MetricsLoadingTemplate), | |
initialize: function (options) { | |
if (!options) options = {}; | |
this.title = | |
typeof options.title === "undefined" | |
? "Summary of Holdings" | |
: options.title; | |
this.description = | |
typeof options.description === "undefined" | |
? "A summary of all datasets in our catalog." | |
: options.description; | |
this.metricsModel = | |
typeof options.metricsModel === undefined | |
? undefined | |
: options.metricsModel; | |
this.userType = | |
typeof options.userType === undefined ? undefined : options.userType; | |
this.userId = | |
typeof options.userId === undefined ? undefined : options.userId; | |
this.userLabel = | |
typeof options.userLabel === undefined | |
? undefined | |
: options.userLabel; | |
if (typeof options.el === "undefined") this.el = options.el; | |
this.hideUpdatesChart = | |
options.hideUpdatesChart === true ? true : false; | |
this.hideMetadataAssessment = | |
typeof options.hideMetadataAssessment === "undefined" | |
? true | |
: options.hideMetadataAssessment; | |
this.hideCitationsChart = | |
typeof options.hideCitationsChart === "undefined" | |
? true | |
: options.hideCitationsChart; | |
this.hideDownloadsChart = | |
typeof options.hideDownloadsChart === "undefined" | |
? true | |
: options.hideDownloadsChart; | |
this.hideViewsChart = | |
typeof options.hideViewsChart === "undefined" | |
? true | |
: options.hideViewsChart; | |
this.model = options.model || null; | |
}, | |
render: function (options) { | |
//The Node info needs to be fetched first since a lot of this code requires info about MNs | |
if ( | |
!MetacatUI.nodeModel.get("checked") && | |
!MetacatUI.nodeModel.get("error") | |
) { | |
this.listenToOnce( | |
MetacatUI.nodeModel, | |
"change:checked error", | |
function () { | |
//Remove listeners and render the view, even if there was an error fetching the NodeModel | |
this.stopListening(MetacatUI.nodeModel); | |
this.render(options); | |
}, | |
); | |
this.$el.html(this.loadingTemplate); | |
return; | |
} | |
if (!options) options = {}; | |
var view = this, | |
userIsCN = false, | |
nodeId, | |
isHostedRepo = false; | |
// Check if the node is a coordinating node | |
this.userIsCN = userIsCN; | |
if (this.userType !== undefined && this.userLabel !== undefined) { | |
if (this.userType === "repository") { | |
userIsCN = MetacatUI.nodeModel.isCN(this.userId); | |
if (userIsCN && typeof isCN !== "undefined") this.userIsCN = true; | |
} | |
} | |
if (options.nodeSummaryView) { | |
this.nodeSummaryView = true; | |
nodeId = MetacatUI.appModel.get("nodeId"); | |
userIsCN = MetacatUI.nodeModel.isCN(nodeId); | |
//Save whether this profile is for a CN | |
if (userIsCN && typeof userIsCN !== "undefined") { | |
this.userIsCN = true; | |
} | |
//Figure out if this profile is for a hosted repo | |
else if (nodeId) { | |
isHostedRepo = _.contains( | |
MetacatUI.appModel.get("dataoneHostedRepos"), | |
nodeId, | |
); | |
} | |
// Disable the metrics if the nodeId is not available or if it is not a DataONE Hosted Repo | |
if ( | |
!this.userIsCN && | |
(nodeId === "undefined" || nodeId === null || !isHostedRepo) | |
) { | |
this.hideCitationsChart = true; | |
this.hideDownloadsChart = true; | |
this.hideViewsChart = true; | |
this.hideMetadataAssessment = true; | |
} else { | |
// Overwrite the metrics display flags as set in the AppModel | |
this.hideMetadataAssessment = MetacatUI.appModel.get( | |
"hideSummaryMetadataAssessment", | |
); | |
this.hideCitationsChart = MetacatUI.appModel.get( | |
"hideSummaryCitationsChart", | |
); | |
this.hideDownloadsChart = MetacatUI.appModel.get( | |
"hideSummaryDownloadsChart", | |
); | |
this.hideViewsChart = MetacatUI.appModel.get( | |
"hideSummaryViewsChart", | |
); | |
} | |
} | |
if ( | |
!this.hideCitationsChart || | |
!this.hideDownloadsChart || | |
!this.hideViewsChart | |
) { | |
if (typeof this.metricsModel === "undefined") { | |
// Create a list with the repository ID | |
var pid_list = new Array(); | |
pid_list.push(nodeId); | |
// Create a new object of the metrics model | |
var metricsModel = new MetricsModel({ | |
pid_list: pid_list, | |
type: this.userType, | |
}); | |
metricsModel.fetch(); | |
this.metricsModel = metricsModel; | |
} | |
} | |
if (!this.model) { | |
this.model = new StatsModel({ | |
hideMetadataAssessment: this.hideMetadataAssessment, | |
mdqImageId: nodeId, | |
}); | |
} | |
//Clear the page | |
this.$el.html(""); | |
//Only trigger the functions that draw SVG charts if d3 loaded correctly | |
if (d3) { | |
//Draw a chart that shows the temporal coverage of all datasets in this collection | |
this.listenTo( | |
this.model, | |
"change:temporalCoverage", | |
this.drawCoverageChart, | |
); | |
//Draw charts that plot the latest updates of metadata and data files | |
this.listenTo( | |
this.model, | |
"change:dataUpdateDates", | |
this.drawDataUpdatesChart, | |
); | |
this.listenTo( | |
this.model, | |
"change:metadataUpdateDates", | |
this.drawMetadataUpdatesChart, | |
); | |
//Render the total file size of all contents in this collection | |
this.listenTo(this.model, "change:totalSize", this.displayTotalSize); | |
//Render the total number of datasets in this collection | |
this.listenTo( | |
this.model, | |
"change:metadataCount", | |
this.displayTotalCount, | |
); | |
// Display replicas only for member nodes | |
if (this.userType === "repository" && !this.userIsCN) | |
this.listenTo( | |
this.model, | |
"change:totalReplicas", | |
this.displayTotalReplicas, | |
); | |
//Draw charts that show the breakdown of format IDs for metadata and data files | |
this.listenTo( | |
this.model, | |
"change:dataFormatIDs", | |
this.drawDataCountChart, | |
); | |
this.listenTo( | |
this.model, | |
"change:metadataFormatIDs", | |
this.drawMetadataCountChart, | |
); | |
} | |
//When the last coverage endDate is found, draw a title for the temporal coverage chart | |
this.listenTo( | |
this.model, | |
"change:lastEndDate", | |
this.drawCoverageChartTitle, | |
); | |
//When the total count is updated, check if there if the count is 0, so we can show there is no "activity" for this collection | |
this.listenTo(this.model, "change:totalCount", this.showNoActivity); | |
// set the header type | |
MetacatUI.appModel.set("headerType", "default"); | |
// Loading template for the FAIR chart | |
var fairLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Running an assessment report...", | |
character: "none", | |
type: "FAIR", | |
}); | |
// Loading template for the citations section | |
var citationsLoadingHtml = this.metricsLoadingTemplate({ | |
message: | |
"Scouring our records for publications that cited these datasets...", | |
character: "none", | |
type: "citations", | |
}); | |
// Loading template for the downloads bar chart | |
var downloadsLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Crunching some numbers...", | |
character: "developer", | |
type: "barchart", | |
}); | |
// Loading template for the views bar chart | |
var viewsLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Just doing a few more calculations...", | |
character: "statistician", | |
type: "barchart", | |
}); | |
//Insert the template | |
this.$el.html( | |
this.template({ | |
query: this.model.get("query"), | |
title: this.title, | |
description: this.description, | |
userType: this.userType, | |
userIsCN: this.userIsCN, | |
fairLoadingHtml: fairLoadingHtml, | |
citationsLoadingHtml: citationsLoadingHtml, | |
downloadsLoadingHtml: downloadsLoadingHtml, | |
viewsLoadingHtml: viewsLoadingHtml, | |
hideUpdatesChart: this.hideUpdatesChart, | |
hideCitationsChart: this.hideCitationsChart, | |
hideDownloadsChart: this.hideDownloadsChart, | |
hideViewsChart: this.hideViewsChart, | |
hideMetadataAssessment: this.hideMetadataAssessment, | |
}), | |
); | |
// Insert the metadata assessment chart | |
var view = this; | |
if (this.hideMetadataAssessment !== true) { | |
this.listenTo( | |
this.model, | |
"change:mdqScoresImage", | |
this.drawMetadataAssessment, | |
); | |
this.listenTo(this.model, "change:mdqScoresError", function () { | |
view.renderMetadataAssessmentError(); | |
}); | |
} | |
//Insert the loading template into the space where the charts will go | |
if (d3) { | |
this.$(".chart").html(this.loadingTemplate); | |
this.$(".show-loading").html(this.loadingTemplate); | |
} | |
//If SVG isn't supported, insert an info warning | |
else { | |
this.$el.prepend( | |
this.alertTemplate({ | |
classes: "alert-info", | |
msg: "Please upgrade your browser or use a different browser to view graphs of these statistics.", | |
email: false, | |
}), | |
); | |
} | |
this.$el.data("view", this); | |
if (this.userType == "portal" || this.userType === "repository") { | |
if ( | |
!this.hideCitationsChart || | |
!this.hideDownloadsChart || | |
!this.hideViewsChart | |
) { | |
if (this.metricsModel.get("totalViews") !== null) { | |
this.renderMetrics(); | |
} else { | |
// render metrics on fetch success. | |
this.listenTo(view.metricsModel, "sync", this.renderMetrics); | |
// in case when there is an error for the fetch call. | |
this.listenTo( | |
view.metricsModel, | |
"error", | |
this.renderUsageMetricsError, | |
); | |
var view = this; | |
setTimeout(function () { | |
if ( | |
view | |
.$(".views-metrics, .downloads-metrics, #user-citations") | |
.find(".metric-chart-loading").length | |
) { | |
view.renderUsageMetricsError(); | |
view.stopListening( | |
view.metricsModel, | |
"error", | |
view.renderUsageMetricsError, | |
); | |
} | |
}, 6000); | |
} | |
} | |
} | |
//Start retrieving data from Solr | |
this.model.getAll(); | |
// Only gather replication stats if the view is a repository view | |
if (this.userType === "repository") { | |
if (this.userLabel !== undefined) { | |
var identifier = MetacatUI.appSearchModel.escapeSpecialChar( | |
encodeURIComponent(this.userId), | |
); | |
this.model.getTotalReplicas(identifier); | |
} else if (this.nodeSummaryView) { | |
var nodeId = MetacatUI.appModel.get("nodeId"); | |
var identifier = MetacatUI.appSearchModel.escapeSpecialChar( | |
encodeURIComponent(nodeId), | |
); | |
this.model.getTotalReplicas(identifier); | |
} | |
} | |
return this; | |
}, | |
/** | |
* drawMetadataAssessment - Insert the metadata assessment image into the view | |
*/ | |
drawMetadataAssessment: function () { | |
try { | |
var scoresImage = this.model.get("mdqScoresImage"); | |
if (scoresImage) { | |
// Replace the preloader figure with the assessment chart | |
this.$("#metadata-assessment-graphic").html(scoresImage); | |
} | |
// If there was no image received from the MDQ scores service, | |
// then show a warning message | |
else { | |
this.renderMetadataAssessmentError(); | |
} | |
} catch (e) { | |
// If there's an error inserting the image, log an error message | |
console.log( | |
"Error displaying the metadata assessment figure. Error message: " + | |
e, | |
); | |
this.renderMetadataAssessmentError(); | |
} | |
}, | |
renderMetrics: function () { | |
if (!this.hideCitationsChart) this.renderCitationMetric(); | |
if (!this.hideDownloadsChart) this.renderDownloadMetric(); | |
if (!this.hideViewsChart) this.renderViewMetric(); | |
}, | |
renderCitationMetric: function () { | |
var citationSectionEl = this.$("#user-citations"); | |
var citationEl = this.$(".citations-metrics-list"); | |
var citationCountEl = this.$(".citation-count"); | |
var metricName = "Citations"; | |
var metricCount = this.metricsModel.get("totalCitations"); | |
citationCountEl.text( | |
MetacatUI.appView.numberAbbreviator(metricCount, 1), | |
); | |
// Displaying Citations | |
var resultDetails = this.metricsModel.get("resultDetails"); | |
// Creating a new collection object | |
// Parsing result-details with citation dictionary format | |
var resultDetailsCitationCollection = new Array(); | |
for (var key in resultDetails["citations"]) { | |
resultDetailsCitationCollection.push(resultDetails["citations"][key]); | |
} | |
var citationCollection = new Citations( | |
resultDetailsCitationCollection, | |
{ parse: true }, | |
); | |
this.citationCollection = citationCollection; | |
// Checking if there are any citations available for the List display. | |
if (this.metricsModel.get("totalCitations") == 0) { | |
var citationList = new CitationList(); | |
// reattaching the citations at the bottom when the counts are 0. | |
var detachCitationEl = this.$(citationSectionEl).detach(); | |
this.$(".charts-container").append(detachCitationEl); | |
} else { | |
var citationList = new CitationList({ | |
citations: this.citationCollection, | |
}); | |
} | |
this.citationList = citationList; | |
citationEl.html(this.citationList.render().$el.html()); | |
}, | |
renderDownloadMetric: function () { | |
var downloadEl = this.$(".downloads-metrics > .metric-chart"); | |
var metricName = "Downloads"; | |
var metricCount = this.metricsModel.get("totalDownloads"); | |
var downloadCountEl = this.$(".download-count"); | |
downloadCountEl.text( | |
MetacatUI.appView.numberAbbreviator(metricCount, 1), | |
); | |
var metricChartView = this.createMetricsChart(metricName); | |
downloadEl.html(metricChartView.el); | |
metricChartView.render(); | |
}, | |
renderViewMetric: function () { | |
var viewEl = this.$(".views-metrics > .metric-chart"); | |
var metricName = "Views"; | |
var metricCount = this.metricsModel.get("totalViews"); | |
var viewCountEl = this.$(".view-count"); | |
viewCountEl.text(MetacatUI.appView.numberAbbreviator(metricCount, 1)); | |
var metricChartView = this.createMetricsChart(metricName); | |
viewEl.html(metricChartView.el); | |
metricChartView.render(); | |
}, | |
// Currently only being used for portals and profile views | |
createMetricsChart: function (metricName) { | |
var metricNameLemma = metricName.toLowerCase(); | |
var metricMonths = this.metricsModel.get("months"); | |
var metricCount = this.metricsModel.get(metricNameLemma); | |
var chartEl = document.getElementById( | |
"user-" + metricNameLemma + "-chart", | |
); | |
var viewType = this.userType; | |
// Draw a metric chart | |
var modalMetricChart = new MetricsChart({ | |
id: metricNameLemma + "-chart", | |
metricCount: metricCount, | |
metricMonths: metricMonths, | |
type: viewType, | |
metricName: metricName, | |
}); | |
this.subviews.push(modalMetricChart); | |
return modalMetricChart; | |
}, | |
drawDataCountChart: function () { | |
var dataCount = this.model.get("dataCount"); | |
var data = this.model.get("dataFormatIDs"); | |
if (dataCount) { | |
var svgClass = "data"; | |
} else if ( | |
!this.model.get("dataCount") && | |
this.model.get("metadataCount") | |
) { | |
//Are we drawing a blank chart (i.e. 0 data objects found)? | |
var svgClass = "data default"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
!this.model.get("dataCount") | |
) | |
var svgClass = "data no-activity"; | |
//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( | |
"<h2 class='" + | |
svgClass + | |
" fallback'>" + | |
MetacatUI.appView.commaSeparateNumber(dataCount) + | |
" data files</h2>", | |
); | |
return; | |
} | |
//Draw a donut chart | |
var donut = new DonutChart({ | |
id: "data-chart", | |
data: data, | |
total: this.model.get("dataCount"), | |
titleText: "data files", | |
titleCount: dataCount, | |
svgClass: svgClass, | |
countClass: "data", | |
height: 300, | |
width: 380, | |
formatLabel: function (name) { | |
//If this is the application/vnd.ms-excel formatID - let's just display "MS Excel" | |
if (name !== undefined && name.indexOf("ms-excel") > -1) | |
name = "MS Excel"; | |
else if ( | |
name != undefined && | |
name == | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | |
) | |
name = "MS Excel OpenXML"; | |
else if ( | |
name != undefined && | |
name == | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" | |
) | |
name = "MS Word OpenXML"; | |
//Application/octet-stream - shorten it | |
else if (name !== undefined && name == "application/octet-stream") | |
name = "Application file"; | |
if (name === undefined) name = ""; | |
return name; | |
}, | |
}); | |
this.$(".format-charts-data").html(donut.render().el); | |
}, | |
drawMetadataCountChart: function () { | |
var metadataCount = this.model.get("metadataCount"); | |
var data = this.model.get("metadataFormatIDs"); | |
if (metadataCount) { | |
var svgClass = "metadata"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
this.model.get("dataCount") | |
) { | |
//Are we drawing a blank chart (i.e. 0 data objects found)? | |
var svgClass = "metadata default"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
!this.model.get("dataCount") | |
) | |
var svgClass = "metadata no-activity"; | |
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead | |
if (!d3) { | |
this.$(".format-charts-metadata").html( | |
"<h2 class='" + | |
svgClass + | |
" fallback'>" + | |
MetacatUI.appView.commaSeparateNumber(metadataCount) + | |
" metadata files</h2>", | |
); | |
return; | |
} | |
//Draw a donut chart | |
var donut = new DonutChart({ | |
id: "metadata-chart", | |
data: data, | |
total: this.model.get("metadataCount"), | |
titleText: "metadata files", | |
titleCount: metadataCount, | |
svgClass: svgClass, | |
countClass: "metadata", | |
height: 300, | |
width: 380, | |
formatLabel: function (name) { | |
if ( | |
name !== undefined && | |
(name.indexOf("//ecoinformatics.org") > -1 || | |
name.indexOf("//eml.ecoinformatics.org") > -1) | |
) { | |
//EML - extract the version only | |
if ( | |
name.substring(0, 4) == "eml:" || | |
name.substring(0, 6) == "https:" | |
) | |
name = name | |
.substr(name.lastIndexOf("/") + 1) | |
.toUpperCase() | |
.replace("-", " "); | |
//EML modules | |
if ( | |
name.indexOf("-//ecoinformatics.org//eml-") > -1 || | |
name.indexOf("-//eml.ecoinformatics.org//eml-") > -1 | |
) | |
name = | |
"EML " + | |
name.substring( | |
name.indexOf("//eml-") + 6, | |
name.lastIndexOf("-"), | |
) + | |
" " + | |
name.substr(name.lastIndexOf("-") + 1, 5); | |
} | |
//Dryad - shorten it | |
else if ( | |
name !== undefined && | |
name == "http://datadryad.org/profile/v3.1" | |
) | |
name = "Dryad 3.1"; | |
//FGDC - just display "FGDC {year}" | |
else if (name !== undefined && name.indexOf("FGDC") > -1) | |
name = "FGDC " + name.substring(name.length - 4); | |
//Onedcx v1.0 | |
else if ( | |
name !== undefined && | |
name == "http://ns.dataone.org/metadata/schema/onedcx/v1.0" | |
) | |
name = "Onedcx v1.0"; | |
//GMD-NOAA | |
else if ( | |
name !== undefined && | |
name == "http://www.isotc211.org/2005/gmd-noaa" | |
) | |
name = "GMD-NOAA"; | |
//GMD-PANGAEA | |
else if ( | |
name !== undefined && | |
name == "http://www.isotc211.org/2005/gmd-pangaea" | |
) | |
name = "GMD-PANGAEA"; | |
if (name === undefined) name = ""; | |
return name; | |
}, | |
}); | |
this.$(".format-charts-metadata").html(donut.render().el); | |
}, | |
//drawUploadChart will get the upload stats from the stats model and draw a time series cumulative chart | |
drawUploadChart: function () { | |
//Get the width of the chart by using the parent container width | |
var parentEl = this.$(".upload-chart"); | |
var width = parentEl.width() || null; | |
//If there was no first upload, draw a blank chart and exit | |
if ( | |
(!this.model.get("metadataUploads") || | |
!this.model.get("metadataUploads").length) && | |
(!this.model.get("dataUploads") || | |
!this.model.get("dataUploads").length) | |
) { | |
var lineChartView = new LineChart({ | |
id: "upload-chart", | |
yLabel: "files uploaded", | |
frequency: 0, | |
width: width, | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
return; | |
} | |
//Set the frequency of our points | |
var frequency = 12; | |
//Check which line we should draw first since the scale will be based off the first line | |
if (this.model.get("metadataUploads") > this.model.get("dataUploads")) { | |
//If there isn't a lot of point to graph, draw points more frequently on the line | |
if (this.model.get("metadataUploadDates").length < 40) frequency = 1; | |
//Create the line chart and draw the metadata line | |
var lineChartView = new LineChart({ | |
data: this.model.get("metadataUploadDates"), | |
formatFromSolrFacets: true, | |
cumulative: true, | |
id: "upload-chart", | |
className: "metadata", | |
yLabel: "files uploaded", | |
labelValue: "Metadata: ", | |
width: width, | |
labelDate: "M-y", | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
//Only draw the data file line if there was at least one uploaded | |
if (this.model.get("dataUploads")) { | |
//Add a line to our chart for data uploads | |
lineChartView.className = "data"; | |
lineChartView.labelValue = "Data: "; | |
lineChartView.addLine(this.model.get("dataUploadDates")); | |
} | |
} else { | |
var lineChartView = new LineChart({ | |
data: this.model.get("dataUploadDates"), | |
formatFromSolrFacets: true, | |
cumulative: true, | |
id: "upload-chart", | |
className: "data", | |
yLabel: "files uploaded", | |
labelValue: "Data: ", | |
width: width, | |
labelDate: "M-y", | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
//If no metadata files were uploaded, we don't want to draw the data file line | |
if (this.model.get("metadataUploads")) { | |
//Add a line to our chart for metadata uploads | |
lineChartView.className = "metadata"; | |
lineChartView.labelValue = "Metadata: "; | |
lineChartView.addLine(this.model.get("metadataUploadDates")); | |
} | |
} | |
}, | |
//drawUploadTitle will draw a circle badge title for the uploads time series chart | |
drawUploadTitle: function () { | |
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead | |
if (!d3) { | |
this.$("#uploads-title").html( | |
"<h2 class='packages fallback'>" + | |
MetacatUI.appView.commaSeparateNumber( | |
this.model.get("totalCount"), | |
) + | |
"</h2>", | |
); | |
return; | |
} | |
if ( | |
!this.model.get("dataUploads") && | |
!this.model.get("metadataUploads") | |
) { | |
//Draw the upload chart title | |
var uploadChartTitle = new CircleBadge({ | |
id: "upload-chart-title", | |
className: "no-activity", | |
globalR: 60, | |
data: [{ count: 0, label: "uploads" }], | |
}); | |
this.$("#uploads-title").prepend(uploadChartTitle.render().el); | |
return; | |
} | |
//Get information for our upload chart title | |
var titleChartData = [], | |
metadataUploads = this.model.get("metadataUploads"), | |
dataUploads = this.model.get("dataUploads"), | |
metadataClass = "metadata", | |
dataClass = "data"; | |
if (metadataUploads == 0) metadataClass = "default"; | |
if (dataUploads == 0) dataClass = "default"; | |
var titleChartData = [ | |
{ | |
count: this.model.get("metadataUploads"), | |
label: "metadata", | |
className: metadataClass, | |
}, | |
{ | |
count: this.model.get("dataUploads"), | |
label: "data", | |
className: dataClass, | |
}, | |
]; | |
//Draw the upload chart title | |
var uploadChartTitle = new CircleBadge({ | |
id: "upload-chart-title", | |
data: titleChartData, | |
className: "chart-title", | |
useGlobalR: true, | |
globalR: 60, | |
}); | |
this.$("#uploads-title").prepend(uploadChartTitle.render().el); | |
}, | |
/* | |
* displayTotalCount - renders a simple count of total metadata files/datasets | |
*/ | |
displayTotalCount: function () { | |
var className = "quick-stats-count"; | |
if (!this.model.get("metadataCount") && !this.model.get("dataCount")) | |
className += " no-activity"; | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text( | |
MetacatUI.appView.commaSeparateNumber( | |
this.model.get("metadataCount"), | |
), | |
); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("datasets"); | |
this.$("#total-datasets").html(countEl); | |
this.$("#total-datasets").append(titleEl); | |
}, | |
/* | |
* displayTotalSize renders a count of the total file size of | |
* all current metadata and data files | |
*/ | |
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 = Utilities.bytesToSize(view.model.get("totalSize")); | |
} | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("of content"); | |
this.$("#total-size").html(countEl); | |
this.$("#total-size").append(titleEl); | |
}, | |
/** | |
* Draws both the metadata and data update date charts. | |
* Note that this function may be deprecated in the future. | |
* Views should directly call drawMetadataUpdatesChart() or drawDataUpdatesChart() directly, | |
* since metadata and data dates are fetched via separate AJAX calls. | |
*/ | |
drawUpdatesChart: function () { | |
//Draw the metadata and data updates charts | |
this.drawMetadataUpdatesChart(); | |
this.drawDataUpdatesChart(); | |
}, | |
/** | |
* Draws a line chart representing the latest metadata updates over time | |
*/ | |
drawMetadataUpdatesChart: function () { | |
//Set some configurations for the LineChart | |
var chartClasses = "data", | |
data; | |
//If the number of metadata objects in this data collection is 0, then set the data for the LineChart to null. | |
// And add a "no-activity" class to the chart. | |
if ( | |
!this.model.get("metadataUpdateDates") || | |
!this.model.get("metadataUpdateDates").length | |
) { | |
data = null; | |
chartClasses += " no-activity"; | |
} else { | |
//Use the metadata update dates for the LineChart | |
data = this.model.get("metadataUpdateDates"); | |
} | |
//Create the line chart for metadata updates | |
var metadataLineChart = new LineChart({ | |
data: data, | |
formatFromSolrFacets: true, | |
cumulative: false, | |
id: "updates-chart", | |
className: chartClasses, | |
yLabel: "metadata files updated", | |
width: this.$(".metadata-updates-chart").width(), | |
labelDate: "M-y", | |
}); | |
//Render the LineChart and insert it into the container element | |
this.$(".metadata-updates-chart").html(metadataLineChart.render().el); | |
}, | |
/** | |
* Draws a line chart representing the latest metadata updates over time | |
*/ | |
drawDataUpdatesChart: function () { | |
//Set some configurations for the LineChart | |
var chartClasses = "data", | |
view = this, | |
data; | |
//Use the data update dates for the LineChart | |
if (this.model.get("dataCount")) { | |
data = this.model.get("dataUpdateDates"); | |
} else { | |
//If the number of data objects in this data collection is 0, then set the data for the LineChart to null. | |
// And add a "no-activity" class to the chart. | |
data = null; | |
chartClasses += " no-activity"; | |
} | |
//Create the line chart for data updates | |
var dataLineChart = new LineChart({ | |
data: data, | |
formatFromSolrFacets: true, | |
cumulative: false, | |
id: "updates-chart", | |
className: chartClasses, | |
yLabel: "data files updated", | |
width: this.$(".data-updates-chart").width(), | |
labelDate: "M-y", | |
}); | |
//Render the LineChart and insert it into the container element | |
this.$(".data-updates-chart").html(dataLineChart.render().el); | |
// redraw the charts to avoid overlap at different widths | |
$(window).on("resize", function () { | |
if (!view.hideUpdatesChart) view.drawUpdatesChart(); | |
}); | |
}, | |
//Draw a bar chart for the temporal coverage | |
drawCoverageChart: function (e, data) { | |
//Get the width of the chart by using the parent container width | |
var parentEl = this.$(".temporal-coverage-chart"); | |
if (this.userType == "repository") { | |
parentEl.addClass("repository-portal-view"); | |
} | |
var width = parentEl.width() || null; | |
// If results were found but none have temporal coverage, draw a default chart | |
if (!this.model.get("temporalCoverage")) { | |
parentEl.html( | |
"<p class='subtle center'>There are no metadata documents that describe temporal coverage.</p>", | |
); | |
return; | |
} | |
var options = { | |
data: data, | |
formatFromSolrFacets: true, | |
id: "temporal-coverage-chart", | |
yLabel: "data packages", | |
yFormat: d3.format(",d"), | |
barClass: "packages", | |
roundedRect: true, | |
roundedRadius: 3, | |
barLabelClass: "packages", | |
width: width, | |
}; | |
var barChart = new BarChart(options); | |
parentEl.html(barChart.render().el); | |
}, | |
drawCoverageChartTitle: function () { | |
if ( | |
!this.model.get("firstBeginDate") || | |
!this.model.get("lastEndDate") || | |
!this.model.get("temporalCoverage") | |
) | |
return; | |
//Create the range query | |
var yearRange = | |
this.model.get("firstBeginDate").getUTCFullYear() + | |
" - " + | |
this.model.get("lastEndDate").getUTCFullYear(); | |
//Find the year range element | |
this.$("#data-coverage-year-range").text(yearRange); | |
}, | |
/* | |
* Shows that this person/group/node has no activity | |
*/ | |
showNoActivity: function () { | |
if ( | |
this.model.get("metadataCount") === 0 && | |
this.model.get("dataCount") === 0 | |
) { | |
this.$(".show-loading .loading").remove(); | |
this.$(".stripe").addClass("no-activity"); | |
this.$(".metric-chart-loading svg animate").remove(); | |
$.each($(".metric-chart-loading .message"), function (i, messageEl) { | |
$(messageEl).html("No metrics to show"); | |
}); | |
} | |
}, | |
renderUsageMetricsError: function () { | |
var message = | |
"<p class='check-back-message'><strong>This might take some time. Check back in 24 hours to see these results.</strong></p>"; | |
$.each( | |
$(".views-metrics, .downloads-metrics, #user-citations"), | |
function (i, metricEl) { | |
$(metricEl).find(".check-back-message").remove(); | |
$(metricEl).find(".message").append(message); | |
}, | |
); | |
}, | |
/** | |
* renderMetadataAssessmentError - update the metadata assessment | |
* pre-loading figure to indicate to the user that the assessment is not | |
* available at the moment. | |
*/ | |
renderMetadataAssessmentError: function () { | |
try { | |
$("#metadata-assessment-graphic .message").append( | |
"<br><strong>This might take some time. Check back in 24 hours to see these results.</strong>", | |
); | |
} catch (e) { | |
console.log( | |
"Error showing the metadata assessment error message in the metrics. " + | |
e, | |
); | |
} | |
}, | |
/* | |
* getReplicas gets the number of replicas in this member node | |
*/ | |
displayTotalReplicas: function () { | |
var view = this; | |
var className = "quick-stats-count"; | |
var count; | |
if (this.model.get("totalReplicas") > 0) { | |
count = MetacatUI.appView.commaSeparateNumber( | |
view.model.get("totalReplicas"), | |
); | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("replicas"); | |
// display the totals | |
this.$("#total-replicas").html(countEl); | |
this.$("#total-replicas").append(titleEl); | |
} else { | |
// hide the replicas container if the replica count is 0. | |
this.$("#replicas-container").hide(); | |
} | |
}, | |
onClose: function () { | |
//Clear the template | |
this.$el.html(""); | |
//Stop listening to changes in the model | |
this.stopListening(this.model); | |
//Stop listening to resize | |
$(window).off("resize"); | |
//Reset the stats model | |
this.model = null; | |
}, | |
}, | |
); | |
return StatsView; | |
}); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Lines 44 to 1184 in 554eadc
var StatsView = Backbone.View.extend( | |
/** @lends StatsView.prototype */ { | |
el: "#Content", | |
model: null, | |
hideUpdatesChart: false, | |
/* | |
* Flag to indicate whether the statsview is a node summary view | |
* @type {boolean} | |
*/ | |
nodeSummaryView: false, | |
/** | |
* Whether or not to show the graph that indicated the assessment score for all metadata in the query. | |
* @type {boolean} | |
*/ | |
hideMetadataAssessment: false, | |
subviews: [], | |
template: _.template(profileTemplate), | |
metricTemplate: _.template(MetricModalTemplate), | |
alertTemplate: _.template(AlertTemplate), | |
loadingTemplate: _.template(LoadingTemplate), | |
metricsLoadingTemplate: _.template(MetricsLoadingTemplate), | |
initialize: function (options) { | |
if (!options) options = {}; | |
this.title = | |
typeof options.title === "undefined" | |
? "Summary of Holdings" | |
: options.title; | |
this.description = | |
typeof options.description === "undefined" | |
? "A summary of all datasets in our catalog." | |
: options.description; | |
this.metricsModel = | |
typeof options.metricsModel === undefined | |
? undefined | |
: options.metricsModel; | |
this.userType = | |
typeof options.userType === undefined ? undefined : options.userType; | |
this.userId = | |
typeof options.userId === undefined ? undefined : options.userId; | |
this.userLabel = | |
typeof options.userLabel === undefined | |
? undefined | |
: options.userLabel; | |
if (typeof options.el === "undefined") this.el = options.el; | |
this.hideUpdatesChart = | |
options.hideUpdatesChart === true ? true : false; | |
this.hideMetadataAssessment = | |
typeof options.hideMetadataAssessment === "undefined" | |
? true | |
: options.hideMetadataAssessment; | |
this.hideCitationsChart = | |
typeof options.hideCitationsChart === "undefined" | |
? true | |
: options.hideCitationsChart; | |
this.hideDownloadsChart = | |
typeof options.hideDownloadsChart === "undefined" | |
? true | |
: options.hideDownloadsChart; | |
this.hideViewsChart = | |
typeof options.hideViewsChart === "undefined" | |
? true | |
: options.hideViewsChart; | |
this.model = options.model || null; | |
}, | |
render: function (options) { | |
//The Node info needs to be fetched first since a lot of this code requires info about MNs | |
if ( | |
!MetacatUI.nodeModel.get("checked") && | |
!MetacatUI.nodeModel.get("error") | |
) { | |
this.listenToOnce( | |
MetacatUI.nodeModel, | |
"change:checked error", | |
function () { | |
//Remove listeners and render the view, even if there was an error fetching the NodeModel | |
this.stopListening(MetacatUI.nodeModel); | |
this.render(options); | |
}, | |
); | |
this.$el.html(this.loadingTemplate); | |
return; | |
} | |
if (!options) options = {}; | |
var view = this, | |
userIsCN = false, | |
nodeId, | |
isHostedRepo = false; | |
// Check if the node is a coordinating node | |
this.userIsCN = userIsCN; | |
if (this.userType !== undefined && this.userLabel !== undefined) { | |
if (this.userType === "repository") { | |
userIsCN = MetacatUI.nodeModel.isCN(this.userId); | |
if (userIsCN && typeof isCN !== "undefined") this.userIsCN = true; | |
} | |
} | |
if (options.nodeSummaryView) { | |
this.nodeSummaryView = true; | |
nodeId = MetacatUI.appModel.get("nodeId"); | |
userIsCN = MetacatUI.nodeModel.isCN(nodeId); | |
//Save whether this profile is for a CN | |
if (userIsCN && typeof userIsCN !== "undefined") { | |
this.userIsCN = true; | |
} | |
//Figure out if this profile is for a hosted repo | |
else if (nodeId) { | |
isHostedRepo = _.contains( | |
MetacatUI.appModel.get("dataoneHostedRepos"), | |
nodeId, | |
); | |
} | |
// Disable the metrics if the nodeId is not available or if it is not a DataONE Hosted Repo | |
if ( | |
!this.userIsCN && | |
(nodeId === "undefined" || nodeId === null || !isHostedRepo) | |
) { | |
this.hideCitationsChart = true; | |
this.hideDownloadsChart = true; | |
this.hideViewsChart = true; | |
this.hideMetadataAssessment = true; | |
} else { | |
// Overwrite the metrics display flags as set in the AppModel | |
this.hideMetadataAssessment = MetacatUI.appModel.get( | |
"hideSummaryMetadataAssessment", | |
); | |
this.hideCitationsChart = MetacatUI.appModel.get( | |
"hideSummaryCitationsChart", | |
); | |
this.hideDownloadsChart = MetacatUI.appModel.get( | |
"hideSummaryDownloadsChart", | |
); | |
this.hideViewsChart = MetacatUI.appModel.get( | |
"hideSummaryViewsChart", | |
); | |
} | |
} | |
if ( | |
!this.hideCitationsChart || | |
!this.hideDownloadsChart || | |
!this.hideViewsChart | |
) { | |
if (typeof this.metricsModel === "undefined") { | |
// Create a list with the repository ID | |
var pid_list = new Array(); | |
pid_list.push(nodeId); | |
// Create a new object of the metrics model | |
var metricsModel = new MetricsModel({ | |
pid_list: pid_list, | |
type: this.userType, | |
}); | |
metricsModel.fetch(); | |
this.metricsModel = metricsModel; | |
} | |
} | |
if (!this.model) { | |
this.model = new StatsModel({ | |
hideMetadataAssessment: this.hideMetadataAssessment, | |
mdqImageId: nodeId, | |
}); | |
} | |
//Clear the page | |
this.$el.html(""); | |
//Only trigger the functions that draw SVG charts if d3 loaded correctly | |
if (d3) { | |
//Draw a chart that shows the temporal coverage of all datasets in this collection | |
this.listenTo( | |
this.model, | |
"change:temporalCoverage", | |
this.drawCoverageChart, | |
); | |
//Draw charts that plot the latest updates of metadata and data files | |
this.listenTo( | |
this.model, | |
"change:dataUpdateDates", | |
this.drawDataUpdatesChart, | |
); | |
this.listenTo( | |
this.model, | |
"change:metadataUpdateDates", | |
this.drawMetadataUpdatesChart, | |
); | |
//Render the total file size of all contents in this collection | |
this.listenTo(this.model, "change:totalSize", this.displayTotalSize); | |
//Render the total number of datasets in this collection | |
this.listenTo( | |
this.model, | |
"change:metadataCount", | |
this.displayTotalCount, | |
); | |
// Display replicas only for member nodes | |
if (this.userType === "repository" && !this.userIsCN) | |
this.listenTo( | |
this.model, | |
"change:totalReplicas", | |
this.displayTotalReplicas, | |
); | |
//Draw charts that show the breakdown of format IDs for metadata and data files | |
this.listenTo( | |
this.model, | |
"change:dataFormatIDs", | |
this.drawDataCountChart, | |
); | |
this.listenTo( | |
this.model, | |
"change:metadataFormatIDs", | |
this.drawMetadataCountChart, | |
); | |
} | |
//When the last coverage endDate is found, draw a title for the temporal coverage chart | |
this.listenTo( | |
this.model, | |
"change:lastEndDate", | |
this.drawCoverageChartTitle, | |
); | |
//When the total count is updated, check if there if the count is 0, so we can show there is no "activity" for this collection | |
this.listenTo(this.model, "change:totalCount", this.showNoActivity); | |
// set the header type | |
MetacatUI.appModel.set("headerType", "default"); | |
// Loading template for the FAIR chart | |
var fairLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Running an assessment report...", | |
character: "none", | |
type: "FAIR", | |
}); | |
// Loading template for the citations section | |
var citationsLoadingHtml = this.metricsLoadingTemplate({ | |
message: | |
"Scouring our records for publications that cited these datasets...", | |
character: "none", | |
type: "citations", | |
}); | |
// Loading template for the downloads bar chart | |
var downloadsLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Crunching some numbers...", | |
character: "developer", | |
type: "barchart", | |
}); | |
// Loading template for the views bar chart | |
var viewsLoadingHtml = this.metricsLoadingTemplate({ | |
message: "Just doing a few more calculations...", | |
character: "statistician", | |
type: "barchart", | |
}); | |
//Insert the template | |
this.$el.html( | |
this.template({ | |
query: this.model.get("query"), | |
title: this.title, | |
description: this.description, | |
userType: this.userType, | |
userIsCN: this.userIsCN, | |
fairLoadingHtml: fairLoadingHtml, | |
citationsLoadingHtml: citationsLoadingHtml, | |
downloadsLoadingHtml: downloadsLoadingHtml, | |
viewsLoadingHtml: viewsLoadingHtml, | |
hideUpdatesChart: this.hideUpdatesChart, | |
hideCitationsChart: this.hideCitationsChart, | |
hideDownloadsChart: this.hideDownloadsChart, | |
hideViewsChart: this.hideViewsChart, | |
hideMetadataAssessment: this.hideMetadataAssessment, | |
}), | |
); | |
// Insert the metadata assessment chart | |
var view = this; | |
if (this.hideMetadataAssessment !== true) { | |
this.listenTo( | |
this.model, | |
"change:mdqScoresImage", | |
this.drawMetadataAssessment, | |
); | |
this.listenTo(this.model, "change:mdqScoresError", function () { | |
view.renderMetadataAssessmentError(); | |
}); | |
} | |
//Insert the loading template into the space where the charts will go | |
if (d3) { | |
this.$(".chart").html(this.loadingTemplate); | |
this.$(".show-loading").html(this.loadingTemplate); | |
} | |
//If SVG isn't supported, insert an info warning | |
else { | |
this.$el.prepend( | |
this.alertTemplate({ | |
classes: "alert-info", | |
msg: "Please upgrade your browser or use a different browser to view graphs of these statistics.", | |
email: false, | |
}), | |
); | |
} | |
this.$el.data("view", this); | |
if (this.userType == "portal" || this.userType === "repository") { | |
if ( | |
!this.hideCitationsChart || | |
!this.hideDownloadsChart || | |
!this.hideViewsChart | |
) { | |
if (this.metricsModel.get("totalViews") !== null) { | |
this.renderMetrics(); | |
} else { | |
// render metrics on fetch success. | |
this.listenTo(view.metricsModel, "sync", this.renderMetrics); | |
// in case when there is an error for the fetch call. | |
this.listenTo( | |
view.metricsModel, | |
"error", | |
this.renderUsageMetricsError, | |
); | |
var view = this; | |
setTimeout(function () { | |
if ( | |
view | |
.$(".views-metrics, .downloads-metrics, #user-citations") | |
.find(".metric-chart-loading").length | |
) { | |
view.renderUsageMetricsError(); | |
view.stopListening( | |
view.metricsModel, | |
"error", | |
view.renderUsageMetricsError, | |
); | |
} | |
}, 6000); | |
} | |
} | |
} | |
//Start retrieving data from Solr | |
this.model.getAll(); | |
// Only gather replication stats if the view is a repository view | |
if (this.userType === "repository") { | |
if (this.userLabel !== undefined) { | |
var identifier = MetacatUI.appSearchModel.escapeSpecialChar( | |
encodeURIComponent(this.userId), | |
); | |
this.model.getTotalReplicas(identifier); | |
} else if (this.nodeSummaryView) { | |
var nodeId = MetacatUI.appModel.get("nodeId"); | |
var identifier = MetacatUI.appSearchModel.escapeSpecialChar( | |
encodeURIComponent(nodeId), | |
); | |
this.model.getTotalReplicas(identifier); | |
} | |
} | |
return this; | |
}, | |
/** | |
* drawMetadataAssessment - Insert the metadata assessment image into the view | |
*/ | |
drawMetadataAssessment: function () { | |
try { | |
var scoresImage = this.model.get("mdqScoresImage"); | |
if (scoresImage) { | |
// Replace the preloader figure with the assessment chart | |
this.$("#metadata-assessment-graphic").html(scoresImage); | |
} | |
// If there was no image received from the MDQ scores service, | |
// then show a warning message | |
else { | |
this.renderMetadataAssessmentError(); | |
} | |
} catch (e) { | |
// If there's an error inserting the image, log an error message | |
console.log( | |
"Error displaying the metadata assessment figure. Error message: " + | |
e, | |
); | |
this.renderMetadataAssessmentError(); | |
} | |
}, | |
renderMetrics: function () { | |
if (!this.hideCitationsChart) this.renderCitationMetric(); | |
if (!this.hideDownloadsChart) this.renderDownloadMetric(); | |
if (!this.hideViewsChart) this.renderViewMetric(); | |
}, | |
renderCitationMetric: function () { | |
var citationSectionEl = this.$("#user-citations"); | |
var citationEl = this.$(".citations-metrics-list"); | |
var citationCountEl = this.$(".citation-count"); | |
var metricName = "Citations"; | |
var metricCount = this.metricsModel.get("totalCitations"); | |
citationCountEl.text( | |
MetacatUI.appView.numberAbbreviator(metricCount, 1), | |
); | |
// Displaying Citations | |
var resultDetails = this.metricsModel.get("resultDetails"); | |
// Creating a new collection object | |
// Parsing result-details with citation dictionary format | |
var resultDetailsCitationCollection = new Array(); | |
for (var key in resultDetails["citations"]) { | |
resultDetailsCitationCollection.push(resultDetails["citations"][key]); | |
} | |
var citationCollection = new Citations( | |
resultDetailsCitationCollection, | |
{ parse: true }, | |
); | |
this.citationCollection = citationCollection; | |
// Checking if there are any citations available for the List display. | |
if (this.metricsModel.get("totalCitations") == 0) { | |
var citationList = new CitationList(); | |
// reattaching the citations at the bottom when the counts are 0. | |
var detachCitationEl = this.$(citationSectionEl).detach(); | |
this.$(".charts-container").append(detachCitationEl); | |
} else { | |
var citationList = new CitationList({ | |
citations: this.citationCollection, | |
}); | |
} | |
this.citationList = citationList; | |
citationEl.html(this.citationList.render().$el.html()); | |
}, | |
renderDownloadMetric: function () { | |
var downloadEl = this.$(".downloads-metrics > .metric-chart"); | |
var metricName = "Downloads"; | |
var metricCount = this.metricsModel.get("totalDownloads"); | |
var downloadCountEl = this.$(".download-count"); | |
downloadCountEl.text( | |
MetacatUI.appView.numberAbbreviator(metricCount, 1), | |
); | |
var metricChartView = this.createMetricsChart(metricName); | |
downloadEl.html(metricChartView.el); | |
metricChartView.render(); | |
}, | |
renderViewMetric: function () { | |
var viewEl = this.$(".views-metrics > .metric-chart"); | |
var metricName = "Views"; | |
var metricCount = this.metricsModel.get("totalViews"); | |
var viewCountEl = this.$(".view-count"); | |
viewCountEl.text(MetacatUI.appView.numberAbbreviator(metricCount, 1)); | |
var metricChartView = this.createMetricsChart(metricName); | |
viewEl.html(metricChartView.el); | |
metricChartView.render(); | |
}, | |
// Currently only being used for portals and profile views | |
createMetricsChart: function (metricName) { | |
var metricNameLemma = metricName.toLowerCase(); | |
var metricMonths = this.metricsModel.get("months"); | |
var metricCount = this.metricsModel.get(metricNameLemma); | |
var chartEl = document.getElementById( | |
"user-" + metricNameLemma + "-chart", | |
); | |
var viewType = this.userType; | |
// Draw a metric chart | |
var modalMetricChart = new MetricsChart({ | |
id: metricNameLemma + "-chart", | |
metricCount: metricCount, | |
metricMonths: metricMonths, | |
type: viewType, | |
metricName: metricName, | |
}); | |
this.subviews.push(modalMetricChart); | |
return modalMetricChart; | |
}, | |
drawDataCountChart: function () { | |
var dataCount = this.model.get("dataCount"); | |
var data = this.model.get("dataFormatIDs"); | |
if (dataCount) { | |
var svgClass = "data"; | |
} else if ( | |
!this.model.get("dataCount") && | |
this.model.get("metadataCount") | |
) { | |
//Are we drawing a blank chart (i.e. 0 data objects found)? | |
var svgClass = "data default"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
!this.model.get("dataCount") | |
) | |
var svgClass = "data no-activity"; | |
//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( | |
"<h2 class='" + | |
svgClass + | |
" fallback'>" + | |
MetacatUI.appView.commaSeparateNumber(dataCount) + | |
" data files</h2>", | |
); | |
return; | |
} | |
//Draw a donut chart | |
var donut = new DonutChart({ | |
id: "data-chart", | |
data: data, | |
total: this.model.get("dataCount"), | |
titleText: "data files", | |
titleCount: dataCount, | |
svgClass: svgClass, | |
countClass: "data", | |
height: 300, | |
width: 380, | |
formatLabel: function (name) { | |
//If this is the application/vnd.ms-excel formatID - let's just display "MS Excel" | |
if (name !== undefined && name.indexOf("ms-excel") > -1) | |
name = "MS Excel"; | |
else if ( | |
name != undefined && | |
name == | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | |
) | |
name = "MS Excel OpenXML"; | |
else if ( | |
name != undefined && | |
name == | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" | |
) | |
name = "MS Word OpenXML"; | |
//Application/octet-stream - shorten it | |
else if (name !== undefined && name == "application/octet-stream") | |
name = "Application file"; | |
if (name === undefined) name = ""; | |
return name; | |
}, | |
}); | |
this.$(".format-charts-data").html(donut.render().el); | |
}, | |
drawMetadataCountChart: function () { | |
var metadataCount = this.model.get("metadataCount"); | |
var data = this.model.get("metadataFormatIDs"); | |
if (metadataCount) { | |
var svgClass = "metadata"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
this.model.get("dataCount") | |
) { | |
//Are we drawing a blank chart (i.e. 0 data objects found)? | |
var svgClass = "metadata default"; | |
} else if ( | |
!this.model.get("metadataCount") && | |
!this.model.get("dataCount") | |
) | |
var svgClass = "metadata no-activity"; | |
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead | |
if (!d3) { | |
this.$(".format-charts-metadata").html( | |
"<h2 class='" + | |
svgClass + | |
" fallback'>" + | |
MetacatUI.appView.commaSeparateNumber(metadataCount) + | |
" metadata files</h2>", | |
); | |
return; | |
} | |
//Draw a donut chart | |
var donut = new DonutChart({ | |
id: "metadata-chart", | |
data: data, | |
total: this.model.get("metadataCount"), | |
titleText: "metadata files", | |
titleCount: metadataCount, | |
svgClass: svgClass, | |
countClass: "metadata", | |
height: 300, | |
width: 380, | |
formatLabel: function (name) { | |
if ( | |
name !== undefined && | |
(name.indexOf("//ecoinformatics.org") > -1 || | |
name.indexOf("//eml.ecoinformatics.org") > -1) | |
) { | |
//EML - extract the version only | |
if ( | |
name.substring(0, 4) == "eml:" || | |
name.substring(0, 6) == "https:" | |
) | |
name = name | |
.substr(name.lastIndexOf("/") + 1) | |
.toUpperCase() | |
.replace("-", " "); | |
//EML modules | |
if ( | |
name.indexOf("-//ecoinformatics.org//eml-") > -1 || | |
name.indexOf("-//eml.ecoinformatics.org//eml-") > -1 | |
) | |
name = | |
"EML " + | |
name.substring( | |
name.indexOf("//eml-") + 6, | |
name.lastIndexOf("-"), | |
) + | |
" " + | |
name.substr(name.lastIndexOf("-") + 1, 5); | |
} | |
//Dryad - shorten it | |
else if ( | |
name !== undefined && | |
name == "http://datadryad.org/profile/v3.1" | |
) | |
name = "Dryad 3.1"; | |
//FGDC - just display "FGDC {year}" | |
else if (name !== undefined && name.indexOf("FGDC") > -1) | |
name = "FGDC " + name.substring(name.length - 4); | |
//Onedcx v1.0 | |
else if ( | |
name !== undefined && | |
name == "http://ns.dataone.org/metadata/schema/onedcx/v1.0" | |
) | |
name = "Onedcx v1.0"; | |
//GMD-NOAA | |
else if ( | |
name !== undefined && | |
name == "http://www.isotc211.org/2005/gmd-noaa" | |
) | |
name = "GMD-NOAA"; | |
//GMD-PANGAEA | |
else if ( | |
name !== undefined && | |
name == "http://www.isotc211.org/2005/gmd-pangaea" | |
) | |
name = "GMD-PANGAEA"; | |
if (name === undefined) name = ""; | |
return name; | |
}, | |
}); | |
this.$(".format-charts-metadata").html(donut.render().el); | |
}, | |
//drawUploadChart will get the upload stats from the stats model and draw a time series cumulative chart | |
drawUploadChart: function () { | |
//Get the width of the chart by using the parent container width | |
var parentEl = this.$(".upload-chart"); | |
var width = parentEl.width() || null; | |
//If there was no first upload, draw a blank chart and exit | |
if ( | |
(!this.model.get("metadataUploads") || | |
!this.model.get("metadataUploads").length) && | |
(!this.model.get("dataUploads") || | |
!this.model.get("dataUploads").length) | |
) { | |
var lineChartView = new LineChart({ | |
id: "upload-chart", | |
yLabel: "files uploaded", | |
frequency: 0, | |
width: width, | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
return; | |
} | |
//Set the frequency of our points | |
var frequency = 12; | |
//Check which line we should draw first since the scale will be based off the first line | |
if (this.model.get("metadataUploads") > this.model.get("dataUploads")) { | |
//If there isn't a lot of point to graph, draw points more frequently on the line | |
if (this.model.get("metadataUploadDates").length < 40) frequency = 1; | |
//Create the line chart and draw the metadata line | |
var lineChartView = new LineChart({ | |
data: this.model.get("metadataUploadDates"), | |
formatFromSolrFacets: true, | |
cumulative: true, | |
id: "upload-chart", | |
className: "metadata", | |
yLabel: "files uploaded", | |
labelValue: "Metadata: ", | |
width: width, | |
labelDate: "M-y", | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
//Only draw the data file line if there was at least one uploaded | |
if (this.model.get("dataUploads")) { | |
//Add a line to our chart for data uploads | |
lineChartView.className = "data"; | |
lineChartView.labelValue = "Data: "; | |
lineChartView.addLine(this.model.get("dataUploadDates")); | |
} | |
} else { | |
var lineChartView = new LineChart({ | |
data: this.model.get("dataUploadDates"), | |
formatFromSolrFacets: true, | |
cumulative: true, | |
id: "upload-chart", | |
className: "data", | |
yLabel: "files uploaded", | |
labelValue: "Data: ", | |
width: width, | |
labelDate: "M-y", | |
}); | |
this.$(".upload-chart").html(lineChartView.render().el); | |
//If no metadata files were uploaded, we don't want to draw the data file line | |
if (this.model.get("metadataUploads")) { | |
//Add a line to our chart for metadata uploads | |
lineChartView.className = "metadata"; | |
lineChartView.labelValue = "Metadata: "; | |
lineChartView.addLine(this.model.get("metadataUploadDates")); | |
} | |
} | |
}, | |
//drawUploadTitle will draw a circle badge title for the uploads time series chart | |
drawUploadTitle: function () { | |
//If d3 isn't supported in this browser or didn't load correctly, insert a text title instead | |
if (!d3) { | |
this.$("#uploads-title").html( | |
"<h2 class='packages fallback'>" + | |
MetacatUI.appView.commaSeparateNumber( | |
this.model.get("totalCount"), | |
) + | |
"</h2>", | |
); | |
return; | |
} | |
if ( | |
!this.model.get("dataUploads") && | |
!this.model.get("metadataUploads") | |
) { | |
//Draw the upload chart title | |
var uploadChartTitle = new CircleBadge({ | |
id: "upload-chart-title", | |
className: "no-activity", | |
globalR: 60, | |
data: [{ count: 0, label: "uploads" }], | |
}); | |
this.$("#uploads-title").prepend(uploadChartTitle.render().el); | |
return; | |
} | |
//Get information for our upload chart title | |
var titleChartData = [], | |
metadataUploads = this.model.get("metadataUploads"), | |
dataUploads = this.model.get("dataUploads"), | |
metadataClass = "metadata", | |
dataClass = "data"; | |
if (metadataUploads == 0) metadataClass = "default"; | |
if (dataUploads == 0) dataClass = "default"; | |
var titleChartData = [ | |
{ | |
count: this.model.get("metadataUploads"), | |
label: "metadata", | |
className: metadataClass, | |
}, | |
{ | |
count: this.model.get("dataUploads"), | |
label: "data", | |
className: dataClass, | |
}, | |
]; | |
//Draw the upload chart title | |
var uploadChartTitle = new CircleBadge({ | |
id: "upload-chart-title", | |
data: titleChartData, | |
className: "chart-title", | |
useGlobalR: true, | |
globalR: 60, | |
}); | |
this.$("#uploads-title").prepend(uploadChartTitle.render().el); | |
}, | |
/* | |
* displayTotalCount - renders a simple count of total metadata files/datasets | |
*/ | |
displayTotalCount: function () { | |
var className = "quick-stats-count"; | |
if (!this.model.get("metadataCount") && !this.model.get("dataCount")) | |
className += " no-activity"; | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text( | |
MetacatUI.appView.commaSeparateNumber( | |
this.model.get("metadataCount"), | |
), | |
); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("datasets"); | |
this.$("#total-datasets").html(countEl); | |
this.$("#total-datasets").append(titleEl); | |
}, | |
/* | |
* displayTotalSize renders a count of the total file size of | |
* all current metadata and data files | |
*/ | |
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 = Utilities.bytesToSize(view.model.get("totalSize")); | |
} | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("of content"); | |
this.$("#total-size").html(countEl); | |
this.$("#total-size").append(titleEl); | |
}, | |
/** | |
* Draws both the metadata and data update date charts. | |
* Note that this function may be deprecated in the future. | |
* Views should directly call drawMetadataUpdatesChart() or drawDataUpdatesChart() directly, | |
* since metadata and data dates are fetched via separate AJAX calls. | |
*/ | |
drawUpdatesChart: function () { | |
//Draw the metadata and data updates charts | |
this.drawMetadataUpdatesChart(); | |
this.drawDataUpdatesChart(); | |
}, | |
/** | |
* Draws a line chart representing the latest metadata updates over time | |
*/ | |
drawMetadataUpdatesChart: function () { | |
//Set some configurations for the LineChart | |
var chartClasses = "data", | |
data; | |
//If the number of metadata objects in this data collection is 0, then set the data for the LineChart to null. | |
// And add a "no-activity" class to the chart. | |
if ( | |
!this.model.get("metadataUpdateDates") || | |
!this.model.get("metadataUpdateDates").length | |
) { | |
data = null; | |
chartClasses += " no-activity"; | |
} else { | |
//Use the metadata update dates for the LineChart | |
data = this.model.get("metadataUpdateDates"); | |
} | |
//Create the line chart for metadata updates | |
var metadataLineChart = new LineChart({ | |
data: data, | |
formatFromSolrFacets: true, | |
cumulative: false, | |
id: "updates-chart", | |
className: chartClasses, | |
yLabel: "metadata files updated", | |
width: this.$(".metadata-updates-chart").width(), | |
labelDate: "M-y", | |
}); | |
//Render the LineChart and insert it into the container element | |
this.$(".metadata-updates-chart").html(metadataLineChart.render().el); | |
}, | |
/** | |
* Draws a line chart representing the latest metadata updates over time | |
*/ | |
drawDataUpdatesChart: function () { | |
//Set some configurations for the LineChart | |
var chartClasses = "data", | |
view = this, | |
data; | |
//Use the data update dates for the LineChart | |
if (this.model.get("dataCount")) { | |
data = this.model.get("dataUpdateDates"); | |
} else { | |
//If the number of data objects in this data collection is 0, then set the data for the LineChart to null. | |
// And add a "no-activity" class to the chart. | |
data = null; | |
chartClasses += " no-activity"; | |
} | |
//Create the line chart for data updates | |
var dataLineChart = new LineChart({ | |
data: data, | |
formatFromSolrFacets: true, | |
cumulative: false, | |
id: "updates-chart", | |
className: chartClasses, | |
yLabel: "data files updated", | |
width: this.$(".data-updates-chart").width(), | |
labelDate: "M-y", | |
}); | |
//Render the LineChart and insert it into the container element | |
this.$(".data-updates-chart").html(dataLineChart.render().el); | |
// redraw the charts to avoid overlap at different widths | |
$(window).on("resize", function () { | |
if (!view.hideUpdatesChart) view.drawUpdatesChart(); | |
}); | |
}, | |
//Draw a bar chart for the temporal coverage | |
drawCoverageChart: function (e, data) { | |
//Get the width of the chart by using the parent container width | |
var parentEl = this.$(".temporal-coverage-chart"); | |
if (this.userType == "repository") { | |
parentEl.addClass("repository-portal-view"); | |
} | |
var width = parentEl.width() || null; | |
// If results were found but none have temporal coverage, draw a default chart | |
if (!this.model.get("temporalCoverage")) { | |
parentEl.html( | |
"<p class='subtle center'>There are no metadata documents that describe temporal coverage.</p>", | |
); | |
return; | |
} | |
var options = { | |
data: data, | |
formatFromSolrFacets: true, | |
id: "temporal-coverage-chart", | |
yLabel: "data packages", | |
yFormat: d3.format(",d"), | |
barClass: "packages", | |
roundedRect: true, | |
roundedRadius: 3, | |
barLabelClass: "packages", | |
width: width, | |
}; | |
var barChart = new BarChart(options); | |
parentEl.html(barChart.render().el); | |
}, | |
drawCoverageChartTitle: function () { | |
if ( | |
!this.model.get("firstBeginDate") || | |
!this.model.get("lastEndDate") || | |
!this.model.get("temporalCoverage") | |
) | |
return; | |
//Create the range query | |
var yearRange = | |
this.model.get("firstBeginDate").getUTCFullYear() + | |
" - " + | |
this.model.get("lastEndDate").getUTCFullYear(); | |
//Find the year range element | |
this.$("#data-coverage-year-range").text(yearRange); | |
}, | |
/* | |
* Shows that this person/group/node has no activity | |
*/ | |
showNoActivity: function () { | |
if ( | |
this.model.get("metadataCount") === 0 && | |
this.model.get("dataCount") === 0 | |
) { | |
this.$(".show-loading .loading").remove(); | |
this.$(".stripe").addClass("no-activity"); | |
this.$(".metric-chart-loading svg animate").remove(); | |
$.each($(".metric-chart-loading .message"), function (i, messageEl) { | |
$(messageEl).html("No metrics to show"); | |
}); | |
} | |
}, | |
renderUsageMetricsError: function () { | |
var message = | |
"<p class='check-back-message'><strong>This might take some time. Check back in 24 hours to see these results.</strong></p>"; | |
$.each( | |
$(".views-metrics, .downloads-metrics, #user-citations"), | |
function (i, metricEl) { | |
$(metricEl).find(".check-back-message").remove(); | |
$(metricEl).find(".message").append(message); | |
}, | |
); | |
}, | |
/** | |
* renderMetadataAssessmentError - update the metadata assessment | |
* pre-loading figure to indicate to the user that the assessment is not | |
* available at the moment. | |
*/ | |
renderMetadataAssessmentError: function () { | |
try { | |
$("#metadata-assessment-graphic .message").append( | |
"<br><strong>This might take some time. Check back in 24 hours to see these results.</strong>", | |
); | |
} catch (e) { | |
console.log( | |
"Error showing the metadata assessment error message in the metrics. " + | |
e, | |
); | |
} | |
}, | |
/* | |
* getReplicas gets the number of replicas in this member node | |
*/ | |
displayTotalReplicas: function () { | |
var view = this; | |
var className = "quick-stats-count"; | |
var count; | |
if (this.model.get("totalReplicas") > 0) { | |
count = MetacatUI.appView.commaSeparateNumber( | |
view.model.get("totalReplicas"), | |
); | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("replicas"); | |
// display the totals | |
this.$("#total-replicas").html(countEl); | |
this.$("#total-replicas").append(titleEl); | |
} else { | |
// hide the replicas container if the replica count is 0. | |
this.$("#replicas-container").hide(); | |
} | |
}, | |
onClose: function () { | |
//Clear the template | |
this.$el.html(""); | |
//Stop listening to changes in the model | |
this.stopListening(this.model); | |
//Stop listening to resize | |
$(window).off("resize"); | |
//Reset the stats model | |
this.model = null; | |
}, | |
}, | |
); |
Unexpected unnamed method 'displayTotalSize'.
metacatui/src/js/views/StatsView.js
Line 925 in 554eadc
displayTotalSize: function () { |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/StatsView.js
Lines 925 to 947 in 554eadc
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 = Utilities.bytesToSize(view.model.get("totalSize")); | |
} | |
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); | |
var titleEl = $(document.createElement("p")) | |
.addClass("chart-title") | |
.text("of content"); | |
this.$("#total-size").html(countEl); | |
this.$("#total-size").append(titleEl); | |
}, |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Line 926 in 554eadc
var className = "quick-stats-count"; |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Line 927 in 554eadc
var count = ""; |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Line 928 in 554eadc
var view = this; |
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
metacatui/src/js/views/StatsView.js
Lines 937 to 939 in 554eadc
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Lines 937 to 939 in 554eadc
var countEl = $(document.createElement("p")) | |
.addClass(className) | |
.text(count); |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/StatsView.js
Lines 1095 to 1107 in 554eadc
showNoActivity: function () { | |
if ( | |
this.model.get("metadataCount") === 0 && | |
this.model.get("dataCount") === 0 | |
) { | |
this.$(".show-loading .loading").remove(); | |
this.$(".stripe").addClass("no-activity"); | |
this.$(".metric-chart-loading svg animate").remove(); | |
$.each($(".metric-chart-loading .message"), function (i, messageEl) { | |
$(messageEl).html("No metrics to show"); | |
}); | |
} | |
}, |
Unexpected unnamed method 'renderUsageMetricsError'.
metacatui/src/js/views/StatsView.js
Line 1109 in 554eadc
renderUsageMetricsError: function () { |
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/views/StatsView.js
Lines 1109 to 1120 in 554eadc
renderUsageMetricsError: function () { | |
var message = | |
"<p class='check-back-message'><strong>This might take some time. Check back in 24 hours to see these results.</strong></p>"; | |
$.each( | |
$(".views-metrics, .downloads-metrics, #user-citations"), | |
function (i, metricEl) { | |
$(metricEl).find(".check-back-message").remove(); | |
$(metricEl).find(".message").append(message); | |
}, | |
); | |
}, |
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/views/StatsView.js
Lines 1110 to 1111 in 554eadc
var message = | |
"<p class='check-back-message'><strong>This might take some time. Check back in 24 hours to see these results.</strong></p>"; |
src/js/common/Utilities.js
Outdated
/** | ||
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing JSDoc @param "bytes" declaration.
/** | |
* 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 | |
/** | |
* 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 |
src/js/common/Utilities.js
Outdated
/** | ||
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing JSDoc @param "precision" declaration.
/** | |
* 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 | |
/** | |
* 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 | |
* @param bytes | |
* @param precision |
src/js/common/Utilities.js
Outdated
/** | ||
* 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 | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing JSDoc @returns declaration.
src/js/common/Utilities.js
Outdated
/** | ||
* Convert number of bytes into human readable format | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected only 0 line after block description
/** | |
* Convert number of bytes into human readable format | |
* | |
/** | |
* Convert number of bytes into human readable format |
/** | ||
* Convert number of bytes into human readable format | ||
* | ||
* @param integer bytes Number of bytes to convert |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing JSDoc @param "integer" type.
src/js/common/Utilities.js
Outdated
precision = 2; | ||
return (bytes / TEBIBYTE).toFixed(precision) + " TiB"; | ||
} else { | ||
return bytes + " B"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <prefer-template> reported by reviewdog 🐶
Unexpected string concatenation.
return bytes + " B"; | |
return `${bytes } B`; |
src/js/models/DataONEObject.js
Outdated
"md5", | ||
], function ($, _, Backbone, uuid, he, AccessPolicy, ObjectFormats, md5) { | ||
], function ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unexpected unnamed function.
src/js/models/DataONEObject.js
Outdated
ObjectFormats, | ||
Utilities, | ||
md5, | ||
) { | ||
/** | ||
* @class DataONEObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected JSDoc block to be aligned.
reviewdog suggestion error
GitHub comment range and suggestion line range must be same. L23-L23 v.s. L22-L32
src/js/models/DataONEObject.js
Outdated
@@ -124,8 +135,15 @@ define([ | |||
|
|||
this.set("accessPolicy", this.createAccessPolicy()); | |||
|
|||
this.on("change:size", this.bytesToSize); | |||
if (attrs.size) this.bytesToSize(); | |||
var model = this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <vars-on-top> reported by reviewdog 🐶
All 'var' declarations must be at the top of the function scope.
src/js/models/DataONEObject.js
Outdated
@@ -124,8 +135,15 @@ define([ | |||
|
|||
this.set("accessPolicy", this.createAccessPolicy()); | |||
|
|||
this.on("change:size", this.bytesToSize); | |||
if (attrs.size) this.bytesToSize(); | |||
var model = this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/models/DataONEObject.js
Lines 11 to 2626 in 554eadc
], function ( | |
$, | |
_, | |
Backbone, | |
uuid, | |
he, | |
AccessPolicy, | |
ObjectFormats, | |
Utilities, | |
md5, | |
) { | |
/** | |
* @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 | |
*/ | |
var DataONEObject = Backbone.Model.extend( | |
/** @lends DataONEObject.prototype */ { | |
type: "DataONEObject", | |
selectedInEditor: false, // Has this package member been selected and displayed in the provenance editor? | |
PROV: "http://www.w3.org/ns/prov#", | |
PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#", | |
defaults: function () { | |
return { | |
// System Metadata attributes | |
serialVersion: null, | |
identifier: null, | |
formatId: null, | |
size: null, | |
checksum: null, | |
originalChecksum: null, | |
checksumAlgorithm: "MD5", | |
submitter: null, | |
rightsHolder: null, | |
accessPolicy: [], //An array of accessPolicy literal JS objects | |
replicationAllowed: null, | |
replicationPolicy: [], | |
obsoletes: null, | |
obsoletedBy: null, | |
archived: null, | |
dateUploaded: null, | |
dateSysMetadataModified: null, | |
originMemberNode: null, | |
authoritativeMemberNode: null, | |
replica: [], | |
seriesId: null, // uuid.v4(), (decide if we want to auto-set this) | |
mediaType: null, | |
fileName: null, | |
// Non-system metadata attributes: | |
isNew: null, | |
datasource: null, | |
insert_count_i: null, | |
read_count_i: null, | |
changePermission: null, | |
writePermission: null, | |
readPermission: null, | |
isPublic: null, | |
dateModified: null, | |
id: "urn:uuid:" + uuid.v4(), | |
sizeStr: null, | |
type: "", // Data, Metadata, or DataPackage | |
formatType: "", | |
metadataEntity: null, // A model that represents the metadata for this file, e.g. an EMLEntity model | |
latestVersion: null, | |
isDocumentedBy: null, | |
documents: [], | |
members: [], | |
resourceMap: [], | |
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 | |
uploadProgress: null, | |
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 | |
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 | |
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. | |
provSources: [], | |
provDerivations: [], | |
prov_generated: [], | |
prov_generatedByExecution: [], | |
prov_generatedByProgram: [], | |
prov_generatedByUser: [], | |
prov_hasDerivations: [], | |
prov_hasSources: [], | |
prov_instanceOfClass: [], | |
prov_used: [], | |
prov_usedByExecution: [], | |
prov_usedByProgram: [], | |
prov_usedByUser: [], | |
prov_wasDerivedFrom: [], | |
prov_wasExecutedByExecution: [], | |
prov_wasExecutedByUser: [], | |
prov_wasInformedBy: [], | |
}; | |
}, | |
initialize: function (attrs, options) { | |
if (typeof attrs == "undefined") var attrs = {}; | |
this.set("accessPolicy", this.createAccessPolicy()); | |
var model = this; | |
this.on("change:size", function () { | |
var size = Utilities.bytesToSize(model.get("size")); | |
model.set("sizeStr", size); | |
}); | |
if (attrs.size) { | |
var 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") | |
this.set("originalAttrs", Object.keys(this.attributes)); | |
else | |
this.set( | |
"originalAttrs", | |
Object.keys(DataONEObject.prototype.defaults()), | |
); | |
this.on("successSaving", this.updateRelationships); | |
//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"); | |
if (entityMetadataModel) | |
entityMetadataModel.set("dataONEObject", this); | |
}); | |
this.on("sync", function () { | |
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 | |
this.setPossibleAuthMNs(); | |
}, | |
/** | |
* Maps the lower-case sys meta node names (valid in HTML DOM) to the | |
* camel-cased sys meta node names (valid in DataONE). | |
* Used during parse() and serialize() | |
*/ | |
nodeNameMap: function () { | |
return { | |
accesspolicy: "accessPolicy", | |
accessrule: "accessRule", | |
authoritativemembernode: "authoritativeMemberNode", | |
checksumalgorithm: "checksumAlgorithm", | |
dateuploaded: "dateUploaded", | |
datesysmetadatamodified: "dateSysMetadataModified", | |
formatid: "formatId", | |
filename: "fileName", | |
nodereference: "nodeReference", | |
numberreplicas: "numberReplicas", | |
obsoletedby: "obsoletedBy", | |
originmembernode: "originMemberNode", | |
replicamembernode: "replicaMemberNode", | |
replicationallowed: "replicationAllowed", | |
replicationpolicy: "replicationPolicy", | |
replicationstatus: "replicationStatus", | |
replicaverified: "replicaVerified", | |
rightsholder: "rightsHolder", | |
serialversion: "serialVersion", | |
seriesid: "seriesId", | |
}; | |
}, | |
/** | |
* Returns the URL string where this DataONEObject can be fetched from or saved to | |
* @returns {string} | |
*/ | |
url: function () { | |
// 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(); | |
//Start the base URL string | |
var 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 | |
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 | |
else { | |
baseUrl = MetacatUI.appModel.get("objectServiceUrl"); | |
} | |
//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.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"))) | |
); | |
} | |
} | |
}, | |
/** | |
* Create the URL string that is used to download this package | |
* @returns PackageURL string for this DataONE Object | |
* @since 2.28.0 | |
*/ | |
getPackageURL: function () { | |
var 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 ( | |
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, | |
}); | |
//If this node has MNRead v2 services... | |
if (node && node.readv2) | |
url = | |
node.baseURL + | |
"/v2/packages/application%2Fbagit-097/" + | |
encodeURIComponent(this.get("id")); | |
} else if (MetacatUI.appModel.get("packageServiceUrl")) | |
url = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")); | |
return url; | |
}, | |
/** | |
* Overload Backbone.Model.fetch, so that we can set custom options for each fetch() request | |
*/ | |
fetch: function (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 = ""; | |
//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 | |
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 | |
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 | |
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", | |
}; | |
//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 | |
var fetchOptions = _.extend( | |
{ | |
dataType: "text", | |
}, | |
options, | |
); | |
} else { | |
//Use custom options for retreiving XML | |
var fetchOptions = _.extend({ | |
dataType: "text", | |
}); | |
} | |
//Add the authorization options | |
fetchOptions = _.extend( | |
fetchOptions, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//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. | |
*/ | |
parse: function (response) { | |
// If the response is XML | |
if (typeof response == "string" && response.indexOf("<") == 0) { | |
var responseDoc = $.parseHTML(response), | |
systemMetadata; | |
//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++) { | |
if ( | |
responseDoc[i].nodeType == 1 && | |
responseDoc[i].localName.indexOf("systemmetadata") > -1 | |
) { | |
systemMetadata = responseDoc[i]; | |
break; | |
} | |
} | |
//Parse the XML to JSON | |
var sysMetaValues = this.toJson(systemMetadata); | |
//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]; | |
if (camelCasedKey) { | |
sysMetaValues[camelCasedKey] = sysMetaValues[key]; | |
delete sysMetaValues[key]; | |
} | |
}, | |
this, | |
); | |
//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 | |
sysMetaValues.id = sysMetaValues.identifier; | |
//Parse the Access Policy | |
if ( | |
this.get("accessPolicy") && | |
AccessPolicy.prototype.isPrototypeOf(this.get("accessPolicy")) | |
) { | |
this.get("accessPolicy").parse( | |
$(systemMetadata).find("accesspolicy"), | |
); | |
sysMetaValues.accessPolicy = this.get("accessPolicy"); | |
} else { | |
//Create a new AccessPolicy collection, if there isn't one already. | |
sysMetaValues.accessPolicy = this.createAccessPolicy( | |
$(systemMetadata).find("accesspolicy"), | |
); | |
} | |
return sysMetaValues; | |
// If the response is a list of Solr docs | |
} else if ( | |
typeof response === "object" && | |
response.response && | |
response.response.docs | |
) { | |
//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]; | |
//Take out any empty values | |
_.each(Object.keys(doc), function (field) { | |
if (!doc[field] && doc[field] !== 0) delete doc[field]; | |
}); | |
//Remove any erroneous white space from fields | |
this.removeWhiteSpaceFromSolrFields(doc); | |
return doc; | |
} | |
// Default to returning the raw response | |
else return response; | |
}, | |
/** A utility function for converting XML to JSON */ | |
toJson: function (xml) { | |
// Create the return object | |
var obj = {}; | |
// do children | |
if (xml.hasChildNodes()) { | |
for (var i = 0; i < xml.childNodes.length; i++) { | |
var item = xml.childNodes.item(i); | |
//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; | |
//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 | |
else if ( | |
typeof obj[nodeName] == "undefined" && | |
item.nodeType == 3 | |
) { | |
obj = | |
item.nodeValue == "false" | |
? false | |
: item.nodeValue == "true" | |
? 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 (!Array.isArray(old)) old = [old]; | |
//Create a new object to store this node info | |
var newNode = {}; | |
//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); | |
} else if (item.nodeType == 3) { | |
newNode = item.nodeValue; | |
var newArray = old.concat(newNode); | |
} | |
//Store the attributes for this node | |
_.each(item.attributes, function (attr) { | |
newNode[attr.localName] = attr.nodeValue; | |
}); | |
//Replace the old array with the updated one | |
obj[nodeName] = newArray; | |
//Exit | |
continue; | |
} | |
//Store the attributes for this node | |
/*_.each(item.attributes, function(attr){ | |
obj[nodeName][attr.localName] = attr.nodeValue; | |
});*/ | |
} | |
} | |
return obj; | |
}, | |
/** | |
Serialize the DataONE object JSON to XML | |
@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") { | |
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]; | |
var node = document.createElement(key); | |
//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") { | |
containerNode.textContent = contents; | |
return containerNode; | |
} else if (Array.isArray(contents)) { | |
var allNewNodes = []; | |
for (var 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") { | |
$(node).append(this.toXML(contents, node)); | |
var attributeNames = _.without(Object.keys(json[key]), "content"); | |
} | |
$(containerNode).append(node); | |
} | |
return containerNode; | |
}, | |
/** | |
* Saves the DataONEObject System Metadata to the server | |
*/ | |
save: function (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, | |
); | |
this.set( | |
"fileName", | |
fileNameWithoutExt.replace(/[^a-zA-Z0-9]/g, "_") + extension, | |
); | |
} | |
if (!this.hasUpdates()) { | |
this.set("uploadStatus", null); | |
return; | |
} | |
//Set the upload transfer as in progress | |
this.set("uploadProgress", 2); | |
this.set("uploadStatus", "p"); | |
//Check if the checksum has been calculated yet. | |
if (!this.get("checksum")) { | |
//When it is calculated, restart this function | |
this.on("checksumCalculated", this.save); | |
//Calculate the checksum for this file | |
this.calculateChecksum(); | |
//Exit this function until the checksum is done | |
return; | |
} | |
//Create a FormData object to send data with our XHR | |
var formData = new FormData(); | |
//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 | |
if (!this.get("id")) { | |
this.set("id", "urn:uuid:" + uuid.v4()); | |
} | |
//Add the identifier to the XHR data | |
formData.append("pid", this.get("id")); | |
} | |
//Create the system metadata XML | |
var 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 | |
formData.append("sysmeta", xmlBlob, "sysmeta.xml"); | |
// Create the new object (MN.create()) | |
formData.append("object", this.get("uploadFile"), this.get("fileName")); | |
var model = this; | |
// On create(), add to the package and the metadata | |
// Note: This should be added to the parent collection | |
// but for now we are using the root collection | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
if (collection.type == "DataPackage") { | |
this.off("successSaving", collection.addNewModel); | |
this.once("successSaving", collection.addNewModel, collection); | |
} | |
}, | |
this, | |
); | |
//Put together the AJAX and Backbone.save() options | |
var requestSettings = { | |
url: this.url(), | |
cache: false, | |
contentType: false, | |
dataType: "text", | |
processData: false, | |
data: formData, | |
parse: false, | |
xhr: function () { | |
var xhr = new window.XMLHttpRequest(); | |
//Upload progress | |
xhr.upload.addEventListener( | |
"progress", | |
function (evt) { | |
if (evt.lengthComputable) { | |
var percentComplete = (evt.loaded / evt.total) * 100; | |
model.set("uploadProgress", percentComplete); | |
} | |
}, | |
false, | |
); | |
return xhr; | |
}, | |
success: this.onSuccessfulSave, | |
error: function (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. | |
if (model.serialize) { | |
model.set("checksum", model.defaults().checksum); | |
} | |
model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); | |
var numSaveAttempts = model.get("numSaveAttempts"); | |
if ( | |
numSaveAttempts < 3 && | |
(response.status == 408 || response.status == 0) | |
) { | |
//Try saving again in 10, 40, and 90 seconds | |
setTimeout( | |
function () { | |
model.save.call(model); | |
}, | |
numSaveAttempts * numSaveAttempts * 10000, | |
); | |
} else { | |
model.set("numSaveAttempts", 0); | |
var parsedResponse = $(response.responseText) | |
.not("style, title") | |
.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. " + | |
"Make sure you are connected to a reliable internet connection."; | |
model.set("errorMessage", parsedResponse); | |
//Set the model status as e for error | |
model.set("uploadStatus", "e"); | |
//Trigger a custom event for the model save error | |
model.trigger("errorSaving", parsedResponse); | |
// Track this error in our analytics | |
MetacatUI.analytics?.trackException( | |
`DataONEObject save error: ${parsedResponse}`, | |
model.get("id"), | |
true, | |
); | |
} | |
}, | |
}; | |
//Add the user settings | |
requestSettings = _.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//Send the Save request | |
Backbone.Model.prototype.save.call(this, null, requestSettings); | |
}, | |
/** | |
* This function is executed when the XHR that saves this DataONEObject has | |
* successfully completed. It can be called directly if a DataONEObject is saved | |
* without directly using the DataONEObject.save() function. | |
* @param {DataONEObject} [model] A reference to this DataONEObject model | |
* @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") { | |
var model = this; | |
} | |
model.set("numSaveAttempts", 0); | |
model.set("uploadStatus", "c"); | |
model.set("isNew", false); | |
model.trigger("successSaving", model); | |
// Get the newest sysmeta set by the MN | |
model.fetch({ | |
merge: true, | |
systemMetadataOnly: true, | |
}); | |
// Reset the content changes status | |
model.set("hasContentChanges", false); | |
//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 | |
model.set("originalChecksum", model.get("checksum")); | |
model.set("checksum", model.defaults().checksum); | |
}, | |
/** | |
* Updates the DataONEObject System Metadata to the server | |
*/ | |
updateSysMeta: function () { | |
//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 | |
// knows that the system metadata, specifically, is being updated. | |
this.set("sysMetaUploadStatus", "p"); | |
var formData = new FormData(); | |
//Add the identifier to the XHR data | |
formData.append("pid", this.get("id")); | |
var 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 | |
formData.append("sysmeta", xmlBlob, "sysmeta.xml"); | |
var model = this; | |
var baseUrl = "", | |
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 | |
else { | |
baseUrl = MetacatUI.appModel.get("metaServiceUrl"); | |
} | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(this.get("id")), | |
cache: false, | |
contentType: false, | |
dataType: "text", | |
type: "PUT", | |
processData: false, | |
data: formData, | |
parse: false, | |
success: function () { | |
model.set("numSaveAttempts", 0); | |
//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" | |
model.set("uploadStatus", "c"); | |
model.set("sysMetaUploadStatus", "c"); | |
//Trigger a custom event that the sys meta was updated | |
model.trigger("sysMetaUpdated"); | |
}, | |
error: function (xhr, status, statusCode) { | |
model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); | |
var numSaveAttempts = model.get("numSaveAttempts"); | |
if (numSaveAttempts < 3 && (statusCode == 408 || statusCode == 0)) { | |
//Try saving again in 10, 40, and 90 seconds | |
setTimeout( | |
function () { | |
model.updateSysMeta.call(model); | |
}, | |
numSaveAttempts * numSaveAttempts * 10000, | |
); | |
} else { | |
model.set("numSaveAttempts", 0); | |
var parsedResponse = $(xhr.responseText) | |
.not("style, title") | |
.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. " + | |
"Make sure you are connected to a reliable internet connection."; | |
model.set("errorMessage", parsedResponse); | |
model.set("sysMetaErrorCode", statusCode); | |
model.set("uploadStatus", "e"); | |
model.set("sysMetaUploadStatus", "e"); | |
// Trigger a custom event for the sysmeta update that | |
// errored | |
model.trigger("sysMetaUpdateError"); | |
// Track this error in our analytics | |
MetacatUI.analytics?.trackException( | |
`DataONEObject update system metadata ` + | |
`error: ${parsedResponse}`, | |
model.get("id"), | |
true, | |
); | |
} | |
}, | |
}; | |
//Add the user settings | |
requestSettings = _.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//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 | |
* 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} | |
*/ | |
checkAuthority: function (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") { | |
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) { | |
if (typeof options[userFunction] !== "function") { | |
options[userFunction] = null; | |
} | |
}); | |
// If PID is not present - check authority with seriesId | |
var identifier = this.get("id"); | |
if (identifier == null) { | |
identifier = this.get("seriesId"); | |
} | |
//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"); | |
//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 | |
baseUrl = possibleAuthMNs[0].authServiceUrl; | |
} | |
} else { | |
//Get the auth service URL from the AppModel | |
baseUrl = MetacatUI.appModel.get("authServiceUrl"); | |
} | |
if (!baseUrl) { | |
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); | |
} | |
}; | |
var model = this; | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(identifier) + "?action=" + action, | |
type: "GET", | |
success: onSuccess, | |
error: onError, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} catch (e) { | |
//Log an error to the console | |
console.error("Couldn't check the authority for this user: ", e); | |
// Track this error in our analytics | |
const name = MetacatUI.appModel.get("username"); | |
MetacatUI.analytics?.trackException( | |
`Couldn't check the authority for the user ${name}: ${e}`, | |
this.get("id"), | |
true, | |
); | |
//Set the user as unauthorized | |
model.set("isAuthorized_" + action, false); | |
model.set("isAuthorized", false); | |
return false; | |
} | |
}, | |
/** | |
* 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 | |
if (typeof sysMetaXML === "undefined" || sysMetaXML === null) { | |
xml = this.createSysMeta(); | |
} else { | |
xml = $($.parseHTML(sysMetaXML)); | |
} | |
//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("submitter") | |
.text( | |
this.get("submitter") || MetacatUI.appUserModel.get("username"), | |
); | |
xml.find("formatid").text(this.get("formatId") || this.getFormatId()); | |
//If there is a seriesId, add it | |
if (this.get("seriesId")) { | |
//Get the seriesId XML node | |
var seriesIdNode = xml.find("seriesId"); | |
//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 | |
seriesIdNode.text(this.get("seriesId")); | |
} | |
//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 | |
if (this.get("uploadFile")) { | |
xml.find("size").text(this.get("uploadFile").size); | |
} | |
//Otherwise, use the last known size | |
else { | |
xml.find("size").text(this.get("size")); | |
} | |
//Save the original checksum | |
if (!this.get("checksum") && this.get("originalChecksum")) { | |
xml.find("checksum").text(this.get("originalChecksum")); | |
} | |
//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 | |
xml | |
.find("rightsholder") | |
.text( | |
this.get("rightsHolder") || MetacatUI.appUserModel.get("username"), | |
); | |
//Write the access policy | |
accessPolicyXML = this.get("accessPolicy").serialize(); | |
// Get the access policy node, if it exists | |
accessPolicyNode = xml.find("accesspolicy"); | |
previousSiblingNode = xml.find("rightsholder"); | |
// Create an access policy node if needed | |
if (!accessPolicyNode.length && accessPolicyXML) { | |
accessPolicyNode = $(document.createElement("accesspolicy")); | |
previousSiblingNode.after(accessPolicyNode); | |
} | |
//Replace the old access policy with the new one if it exists | |
if (accessPolicyXML) { | |
accessPolicyNode.replaceWith(accessPolicyXML); | |
} else { | |
// Remove the node if it is empty | |
accessPolicyNode.remove(); | |
} | |
// Set the obsoletes node after replPolicy or accessPolicy, or rightsHolder | |
replicationPolicyNode = xml.find("replicationpolicy"); | |
accessPolicyNode = xml.find("accesspolicy"); | |
rightsHolderNode = xml.find("rightsholder"); | |
if (replicationPolicyNode.length) { | |
previousSiblingNode = replicationPolicyNode; | |
} else if (accessPolicyNode.length) { | |
previousSiblingNode = accessPolicyNode; | |
} else { | |
previousSiblingNode = rightsHolderNode; | |
} | |
obsoletesNode = xml.find("obsoletes"); | |
if (this.get("obsoletes")) { | |
if (obsoletesNode.length) { | |
obsoletesNode.text(this.get("obsoletes")); | |
} else { | |
obsoletesNode = $(document.createElement("obsoletes")).text( | |
this.get("obsoletes"), | |
); | |
previousSiblingNode.after(obsoletesNode); | |
} | |
} else { | |
if (obsoletesNode) { | |
obsoletesNode.remove(); | |
} | |
} | |
if (obsoletesNode) { | |
previousSiblingNode = obsoletesNode; | |
} | |
obsoletedByNode = xml.find("obsoletedby"); | |
//remove the obsoletedBy node if it exists | |
// TODO: Verify this is what we want to do | |
if (obsoletedByNode) { | |
obsoletedByNode.remove(); | |
} | |
xml.find("archived").text(this.get("archived") || "false"); | |
xml | |
.find("dateuploaded") | |
.text(this.get("dateUploaded") || new Date().toISOString()); | |
//Get the filename node | |
fileNameNode = xml.find("filename"); | |
//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 | |
$(fileNameNode).text(this.get("fileName")); | |
xmlString = $(document.createElement("div")).append(xml.clone()).html(); | |
//Now camel case the nodes | |
nodeNameMap = this.nodeNameMap(); | |
_.each( | |
Object.keys(nodeNameMap), | |
function (name, i) { | |
var 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] + ">"); | |
//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] + "=", | |
); | |
} | |
}, | |
this, | |
); | |
xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata"); | |
return xmlString; | |
}, | |
/** | |
* 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 | |
objectFormats["mediaTypes"] = MetacatUI.objectFormats.where({ | |
formatId: this.get("mediaType"), | |
}); | |
if ( | |
typeof fileName !== "undefined" && | |
fileName !== null && | |
fileName.length > 1 | |
) { | |
ext = fileName.substring( | |
fileName.lastIndexOf(".") + 1, | |
fileName.length, | |
); | |
objectFormats["extensions"] = MetacatUI.objectFormats.where({ | |
extension: ext, | |
}); | |
} | |
if ( | |
objectFormats["mediaTypes"].length > 0 && | |
objectFormats["extensions"].length > 0 | |
) { | |
var firstMediaType = objectFormats["mediaTypes"][0].get("formatId"); | |
var firstExtension = objectFormats["extensions"][0].get("formatId"); | |
// Check if they're equal | |
if (firstMediaType === firstExtension) { | |
formatId = firstMediaType; | |
return formatId; | |
} | |
// Handle mismatched mediaType and extension cases - additional cases can be added below | |
if ( | |
firstMediaType === "application/vnd.ms-excel" && | |
firstExtension === "text/csv" | |
) { | |
formatId = firstExtension; | |
return 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 (ext == "nc") { | |
formatId = "netCDF-3"; | |
} else { | |
formatId = objectFormats["extensions"][0].get("formatId"); | |
} | |
return formatId; | |
} | |
return formatId; | |
}, | |
/** | |
* Looks up human readable format of the DataONE Object | |
* @returns format String | |
* @since 2.28.0 | |
*/ | |
getFormat: function () { | |
var formatMap = { | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": | |
"Microsoft Excel OpenXML", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": | |
"Microsoft Word OpenXML", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12": | |
"Microsoft Office Excel 2007 binary workbooks", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation": | |
"Microsoft Office OpenXML Presentation", | |
"application/vnd.ms-excel": "Microsoft Excel", | |
"application/msword": "Microsoft Word", | |
"application/vnd.ms-powerpoint": "Microsoft Powerpoint", | |
"text/html": "HTML", | |
"text/plain": "plain text (.txt)", | |
"video/avi": "Microsoft AVI file", | |
"video/x-ms-wmv": "Windows Media Video (.wmv)", | |
"audio/x-ms-wma": "Windows Media Audio (.wma)", | |
"application/vnd.google-earth.kml xml": | |
"Google Earth Keyhole Markup Language (KML)", | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html": | |
"annotation", | |
"application/mathematica": "Mathematica Notebook", | |
"application/postscript": "Postscript", | |
"application/rtf": "Rich Text Format (RTF)", | |
"application/xml": "XML Application", | |
"text/xml": "XML", | |
"application/x-fasta": "FASTA sequence file", | |
"nexus/1997": "NEXUS File Format for Systematic Information", | |
"anvl/erc-v02": | |
"Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13", | |
"http://purl.org/dryad/terms/": | |
"Dryad Metadata Application Profile Version 3.0", | |
"http://datadryad.org/profile/v3.1": | |
"Dryad Metadata Application Profile Version 3.1", | |
"application/pdf": "PDF", | |
"application/zip": "ZIP file", | |
"http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML", | |
"http://www.w3.org/TR/rdfa-syntax": "RDFa", | |
"application/rdf xml": "RDF", | |
"text/turtle": "TURTLE", | |
"text/n3": "N3", | |
"application/x-gzip": "GZIP Format", | |
"application/x-python": "Python script", | |
"http://www.w3.org/2005/Atom": "ATOM-1.0", | |
"application/octet-stream": "octet stream (application file)", | |
"http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd": | |
"Darwin Core, v2.0", | |
"http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core", | |
"eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0", | |
"eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1", | |
"eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1", | |
"eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0", | |
"https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0", | |
}; | |
return formatMap[this.get("formatId")] || this.get("formatId"); | |
}, | |
/** | |
* 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 | |
sysmetaXML.push( | |
//'<?xml version="1.0" encoding="UTF-8"?>', | |
"<d1_v2.0:systemmetadata", | |
' xmlns:d1_v2.0="http://ns.dataone.org/service/types/v2.0"', | |
' xmlns:d1="http://ns.dataone.org/service/types/v1">', | |
" <serialversion />", | |
" <identifier />", | |
" <formatid />", | |
" <size />", | |
" <checksum />", | |
" <submitter />", | |
" <rightsholder />", | |
" <filename />", | |
"</d1_v2.0:systemmetadata>", | |
); | |
sysmetaDOM = $($.parseHTML(sysmetaXML.join(""))); | |
return sysmetaDOM; | |
}, | |
/** | |
* Create an access policy for this DataONEObject using the default access | |
* policy set in the AppModel. | |
* | |
* @param {Element} [accessPolicyXML] - An <accessPolicy> XML node | |
* that contains a list of access rules. | |
* @return {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(); | |
accessPolicy.dataONEObject = this; | |
//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, | |
// then get the parent metadata and copy it's AccessPolicy | |
let scienceMetadata = this.get("isDocumentedByModels"); | |
if ( | |
MetacatUI.appModel.get("inheritAccessPolicy") && | |
scienceMetadata && | |
scienceMetadata.length | |
) { | |
let sciMetaAccessPolicy = scienceMetadata[0].get("accessPolicy"); | |
if (sciMetaAccessPolicy) { | |
accessPolicy.copyAccessPolicy(sciMetaAccessPolicy); | |
} else { | |
accessPolicy.createDefaultPolicy(); | |
} | |
} | |
//Otherwise, set the default access policy using the AppModel configuration | |
else { | |
accessPolicy.createDefaultPolicy(); | |
} | |
} catch (e) { | |
console.error( | |
"Could create access policy, so defaulting to default", | |
e, | |
); | |
accessPolicy.createDefaultPolicy(); | |
} | |
} else { | |
//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; | |
this.listenTo(accessPolicy, "change update", function () { | |
self.trigger("change"); | |
this.addToUploadQueue(); | |
}); | |
return accessPolicy; | |
}, | |
/** | |
* Update identifiers for this object | |
* | |
* @param {string} id - Optional identifier to update with. Generated | |
* automatically when not given. | |
* | |
* Note that this method caches the objects attributes prior to | |
* updating so this.resetID() can be called in case of a failure | |
* state. | |
* | |
* Also note that this method won't run if theh oldPid attribute is | |
* set. This enables knowing before this.save is called what the next | |
* PID will be such as the case where we want to update a matching | |
* EML entity when replacing files. | |
*/ | |
updateID: function (id) { | |
// Only run once until oldPid is reset | |
if (this.get("oldPid")) { | |
return; | |
} | |
//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; | |
//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 | |
if (!this.get("seriesId") && this.get("createSeriesId")) { | |
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 | |
if (id) { | |
this.set("id", id); | |
} else { | |
if (this.get("type") == "DataPackage") { | |
this.set("id", "resource_map_urn:uuid:" + uuid.v4()); | |
} else { | |
this.set("id", "urn:uuid:" + uuid.v4()); | |
} | |
} | |
// Remove the old pid from the documents list if present | |
if (selfDocuments) { | |
index = this.get("documents").indexOf(oldPid); | |
if (index > -1) { | |
this.get("documents").splice(index, 1); | |
} | |
// And add the new pid in | |
this.get("documents").push(this.get("id")); | |
} | |
// Remove the old pid from the isDocumentedBy list if present | |
if (selfDocumentedBy) { | |
index = this.get("isDocumentedBy").indexOf(oldPid); | |
if (index > -1) { | |
this.get("isDocumentedBy").splice(index, 1); | |
} | |
// And add the new pid in | |
this.get("isDocumentedBy").push(this.get("id")); | |
} | |
// Update all models documented by this pid with the new id | |
_.each( | |
this.get("documents"), | |
function (id) { | |
(documentedModels = MetacatUI.rootDataPackage.where({ id: id })), | |
documentedModel; | |
if (documentedModels.length > 0) { | |
documentedModel = documentedModels[0]; | |
} | |
if (typeof documentedModel !== "undefined") { | |
// Find the oldPid in the array | |
if (Array.isArray(documentedModel.get("isDocumentedBy"))) { | |
index = documentedModel.get("isDocumentedBy").indexOf("oldPid"); | |
if (index > -1) { | |
// Remove it | |
documentedModel.get("isDocumentedBy").splice(index, 1); | |
} | |
// And add the new pid in | |
documentedModel.get("isDocumentedBy").push(this.get("id")); | |
} | |
} | |
}, | |
this, | |
); | |
this.trigger("change:id"); | |
//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 | |
this.set("archived", false); | |
}, | |
/** | |
* Resets the identifier for this model. This undos all of the changes made in {DataONEObject#updateID} | |
*/ | |
resetID: function () { | |
if (!this.attributeCache) return false; | |
this.set("oldPid", this.attributeCache.oldPid, { silent: true }); | |
this.set("id", this.attributeCache.id, { silent: true }); | |
this.set("obsoletes", this.attributeCache.obsoletes, { silent: true }); | |
this.set("obsoletedBy", this.attributeCache.obsoletedBy, { | |
silent: true, | |
}); | |
this.set("archived", this.attributeCache.archived, { silent: true }); | |
this.set("latestVersion", this.attributeCache.latestVersion, { | |
silent: true, | |
}); | |
//Reset the attribute cache | |
this.attributeCache = {}; | |
}, | |
/** | |
* Checks if this system metadata XML has updates that need to be synced with the server. | |
* @returns {boolean} | |
*/ | |
hasUpdates: function () { | |
if (this.isNew()) return true; | |
// Compare the new system metadata XML to the old system metadata XML | |
//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"), | |
); | |
D1ObjectClone.set(oldSysMetaAttrs); | |
var oldSysMeta = D1ObjectClone.serializeSysMeta(); | |
var newSysMeta = this.serializeSysMeta(); | |
if (oldSysMeta === "") return false; | |
return !(newSysMeta == oldSysMeta); | |
}, | |
/** | |
Set the changed flag on any system metadata or content attribute changes, | |
and set the hasContentChanges flag on content changes only | |
@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) { | |
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 ... | |
// Get a list of all changed sysmeta and content attributes | |
changedSysMetaOrContentAttrs = _.difference( | |
allChangedAttrs, | |
nonSysMetaNonContentAttrs, | |
); | |
if (changedSysMetaOrContentAttrs.length > 0) { | |
// For any sysmeta or content change, set the package dirty flag | |
if ( | |
MetacatUI.rootDataPackage && | |
MetacatUI.rootDataPackage.packageModel && | |
!MetacatUI.rootDataPackage.packageModel.get("changed") && | |
model.get("synced") | |
) { | |
MetacatUI.rootDataPackage.packageModel.set("changed", true); | |
} | |
} | |
// And get a list of all changed content attributes | |
changedContentAttrs = _.difference( | |
changedSysMetaOrContentAttrs, | |
sysMetaAttrs, | |
); | |
if ( | |
(changedContentAttrs.length > 0 && | |
!this.get("hasContentChanges") && | |
model.get("synced")) || | |
(options && options.force) | |
) { | |
this.set("hasContentChanges", true); | |
this.addToUploadQueue(); | |
} | |
}, | |
/** | |
* 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} | |
*/ | |
isNew: function () { | |
//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) { | |
return true; | |
} | |
//Check if there is an upload date that was retrieved from the server | |
return ( | |
this.get("dateUploaded") === this.defaults().dateUploaded && | |
this.get("synced") | |
); | |
}, | |
/** | |
* Updates the upload status attribute on this model and marks the collection as changed | |
*/ | |
addToUploadQueue: function () { | |
if (!this.get("synced")) { | |
return; | |
} | |
//Add this item to the queue | |
if ( | |
this.get("uploadStatus") == "c" || | |
this.get("uploadStatus") == "e" || | |
!this.get("uploadStatus") | |
) { | |
this.set("uploadStatus", "q"); | |
//Mark each DataPackage collection this model is in as changed | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
if (collection.packageModel) | |
collection.packageModel.set("changed", true); | |
}, | |
this, | |
); | |
} | |
}, | |
/** | |
* Updates the progress percentage when the model is getting uploaded | |
* @param {ProgressEvent} e - The ProgressEvent when this file is being uploaded | |
*/ | |
updateProgress: function (e) { | |
if (e.lengthComputable) { | |
var max = e.total; | |
var current = e.loaded; | |
var Percentage = (current * 100) / max; | |
if (Percentage >= 100) { | |
// process completed | |
} | |
} | |
}, | |
/** | |
* Updates the relationships with other models when this model has been updated | |
*/ | |
updateRelationships: function () { | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
//Get the old id for this model | |
var 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); | |
}); | |
//Update the documents array in each model | |
_.each( | |
outdatedModels, | |
function (model) { | |
var updatedDocuments = _.without(model.get("documents"), oldId); | |
updatedDocuments.push(this.get("id")); | |
model.set("documents", updatedDocuments); | |
}, | |
this, | |
); | |
}, | |
this, | |
); | |
}, | |
/** | |
* 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 | |
if (activeAltRepo) { | |
baseUrl = activeAltRepo.metaServiceUrl; | |
} | |
//If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel | |
else { | |
baseUrl = MetacatUI.appModel.get("metaServiceUrl"); | |
} | |
if (!baseUrl) { | |
return; | |
} | |
//If there is no system metadata, then retrieve it first | |
if (!this.get("sysMetaXML")) { | |
this.once("sync", this.findLatestVersion); | |
this.once("systemMetadataSync", this.findLatestVersion); | |
this.fetch({ | |
url: baseUrl + encodeURIComponent(this.get("id")), | |
dataType: "text", | |
systemMetadataOnly: true, | |
}); | |
return; | |
} | |
//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") { | |
this.set("latestVersion", 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 | |
this.stopListening("sync", this.findLatestVersion); | |
this.stopListening("systemMetadataSync", this.findLatestVersion); | |
return; | |
} | |
var model = this; | |
//Get the system metadata for the possibly newer version | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(possiblyNewer), | |
type: "GET", | |
success: function (data) { | |
// the response may have an obsoletedBy element | |
var obsoletedBy = $(data).find("obsoletedBy").text(); | |
//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 | |
else { | |
model.set("latestVersion", possiblyNewer); | |
model.trigger("latestVersionFound", model); | |
//Remove the listeners now that we found the latest version | |
model.stopListening("sync", model.findLatestVersion); | |
model.stopListening( | |
"systemMetadataSync", | |
model.findLatestVersion, | |
); | |
} | |
}, | |
error: function (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); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/** | |
* A utility function that will format an XML string or XML nodes by camel-casing the node names, as necessary | |
* @param {string|Element} xml - The XML to format | |
* @returns {string} The formatted XML string | |
*/ | |
formatXML: function (xml) { | |
var nodeNameMap = this.nodeNameMap(), | |
xmlString = ""; | |
//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 | |
else if (xml.nodeType == 1) xmlString = xml.outerHTML; | |
//Text node types are left as-is | |
else if (xml.nodeType == 3) return xml.nodeValue; | |
} | |
//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. `<name>` | |
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. `<name ` | |
regEx = new RegExp("<" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + " "); | |
//Check for this node name when it's preceeded by a namespace, e.g. `:name ` | |
regEx = new RegExp(":" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + " "); | |
//Check for this node name when it's a closing tag preceeded by a namespace, e.g. `:name>` | |
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. `</name>` | |
regEx = new RegExp("</" + name + ">", "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
"</" + nodeNameMap[name] + ">", | |
); | |
//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] + "=", | |
); | |
} | |
}, | |
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)); | |
}); | |
return xmlString; | |
}, | |
/** | |
* 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; | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var xhr = new XMLHttpRequest(); | |
//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 | |
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"); | |
if (!filename) { | |
filename = | |
model.get("fileName") || | |
model.get("title") || | |
model.get("id") || | |
"download"; | |
} else | |
filename = filename | |
.substring(filename.indexOf("filename=") + 9) | |
.replace(/"/g, ""); | |
//Replace any whitespaces | |
filename = filename.trim().replace(/ /g, "_"); | |
//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 | |
else { | |
var a = document.createElement("a"); | |
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob | |
// Set the file name. | |
a.download = filename; | |
a.style.display = "none"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download DataONEObject", | |
model.get("id"), | |
); | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track the error | |
MetacatUI.analytics?.trackException( | |
`Download DataONEObject error: ${e || ""}`, | |
model.get("id"), | |
true, | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.responseType = "blob"; | |
if (MetacatUI.appUserModel.get("loggedIn")) | |
xhr.setRequestHeader( | |
"Authorization", | |
"Bearer " + MetacatUI.appUserModel.get("token"), | |
); | |
xhr.send(); | |
}, | |
/** | |
* Creates a file name for this DataONEObject and updates the `fileName` attribute | |
*/ | |
setMissingFileName: function () { | |
var objectFormats, filename, extension; | |
objectFormats = MetacatUI.objectFormats.where({ | |
formatId: this.get("formatId"), | |
}); | |
if (objectFormats.length > 0) { | |
extension = objectFormats[0].get("extension"); | |
} | |
//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 | |
else if (this.get("type") == "DataPackage") { | |
filename = "resource_map_" + this.get("id"); | |
extension = ".rdf.xml"; | |
} | |
//All other object types will just use the id | |
else { | |
filename = this.get("id"); | |
} | |
//Replace all non-alphanumeric characters with underscores | |
filename = filename.replace(/[^a-zA-Z0-9]/g, "_"); | |
if (typeof extension !== "undefined") { | |
filename = filename + "." + extension; | |
} | |
this.set("fileName", filename); | |
}, | |
/** | |
* Creates a URL for viewing more information about this object | |
* @return {string} | |
*/ | |
createViewURL: function () { | |
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 | |
* neither the seriesId nor the PID match a DOI regex. | |
* @since 2.26.0 | |
*/ | |
getCanonicalDOIIRI: function () { | |
const id = this.get("id"); | |
const seriesId = this.get("seriesId"); | |
let DOI = null; | |
if (this.isDOI(seriesId)) DOI = seriesId; | |
else if (this.isDOI(id)) DOI = id; | |
return MetacatUI.appModel.DOItoURL(DOI); | |
}, | |
/** | |
* 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 | |
*/ | |
getXMLSafeID: function (id) { | |
if (typeof id == "undefined") { | |
var id = this.get("id"); | |
} | |
//Replace XML id attribute invalid characters and patterns in the identifier | |
id = id | |
.replace(/</g, "-") | |
.replace(/:/g, "-") | |
.replace(/&[a-zA-Z0-9]+;/g); | |
return id; | |
}, | |
/**** Provenance-related functions ****/ | |
/** | |
* Returns true if this provenance field points to a source of this data or metadata object | |
* @param {string} field | |
* @returns {boolean} | |
*/ | |
isSourceField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
// Is the field we are checking a prov field? | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_generatedByExecution" || | |
field == "prov_generatedByProgram" || | |
field == "prov_used" || | |
field == "prov_wasDerivedFrom" || | |
field == "prov_wasInformedBy" | |
) | |
return true; | |
else return false; | |
}, | |
/** | |
* Returns true if this provenance field points to a derivation of this data or metadata object | |
* @param {string} field | |
* @returns {boolean} | |
*/ | |
isDerivationField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_usedByExecution" || | |
field == "prov_usedByProgram" || | |
field == "prov_hasDerivations" || | |
field == "prov_generated" | |
) | |
return true; | |
else return false; | |
}, | |
/** | |
* Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data | |
*/ | |
getType: function () { | |
//The list of formatIds that are images | |
//The list of formatIds that are images | |
var pdfIds = ["application/pdf"]; | |
var annotationIds = [ | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html", | |
]; | |
// Type has already been set, use that. | |
if (this.get("type").toLowerCase() == "metadata") return "metadata"; | |
//Determine the type via provONE | |
var instanceOfClass = this.get("prov_instanceOfClass"); | |
if ( | |
typeof instanceOfClass !== "undefined" && | |
Array.isArray(instanceOfClass) && | |
instanceOfClass.length | |
) { | |
var programClass = _.filter(instanceOfClass, function (className) { | |
return 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"; | |
} | |
//Determine the type via file format | |
if (this.isSoftware()) return "program"; | |
if (this.isData()) return "data"; | |
if (this.get("type").toLowerCase() == "metadata") return "metadata"; | |
if (this.isImage()) return "image"; | |
if (_.contains(pdfIds, this.get("formatId"))) return "PDF"; | |
if (_.contains(annotationIds, this.get("formatId"))) | |
return "annotation"; | |
else 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"]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(imageIds, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Checks the formatId of this model and determines if it is a data file. | |
* This determination is mostly used for display and the provenance editor. In the | |
* DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized | |
* 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 = [ | |
"application/atom+xml", | |
"application/mathematica", | |
"application/msword", | |
"application/netcdf", | |
"application/octet-stream", | |
"application/pdf", | |
"application/postscript", | |
"application/rdf+xml", | |
"application/rtf", | |
"application/vnd.google-earth.kml+xml", | |
"application/vnd.ms-excel", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12", | |
"application/vnd.ms-powerpoint", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation", | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", | |
"application/x-bzip2", | |
"application/x-fasta", | |
"application/x-gzip", | |
"application/x-rar-compressed", | |
"application/x-tar", | |
"application/xhtml+xml", | |
"application/xml", | |
"application/zip", | |
"audio/mpeg", | |
"audio/x-ms-wma", | |
"audio/x-wav", | |
"image/svg xml", | |
"image/svg+xml", | |
"image/bmp", | |
"image/tiff", | |
"text/anvl", | |
"text/csv", | |
"text/html", | |
"text/n3", | |
"text/plain", | |
"text/tab-separated-values", | |
"text/turtle", | |
"text/xml", | |
"video/avi", | |
"video/mp4", | |
"video/mpeg", | |
"video/quicktime", | |
"video/x-ms-wmv", | |
]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(dataIds, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Checks the formatId of this model and determines if it is a software file. | |
* This determination is mostly used for display and the provenance editor. In the | |
* DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized | |
* 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 = [ | |
"text/x-python", | |
"text/x-rsrc", | |
"text/x-matlab", | |
"text/x-sas", | |
"application/R", | |
"application/x-ipynb+json", | |
]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(softwareIds, this.get("formatId")) == -1) return false; | |
else 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"]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(ids, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Set the DataONE ProvONE provenance class | |
* param className - the shortened form of the actual classname value. The | |
* shortname will be appened to the ProvONE namespace, for example, | |
* the className "program" will result in the final class name | |
* "http://purl.dataone.org/provone/2015/01/15/ontology#Program" | |
* see https://github.com/DataONEorg/sem-prov-ontologies/blob/master/provenance/ProvONE/v1/provone.html | |
* @param {string} className | |
*/ | |
setProvClass: function (className) { | |
className = className.toLowerCase(); | |
className = className.charAt(0).toUpperCase() + className.slice(1); | |
/* This function is intended to be used for the ProvONE classes that are | |
* typically represented in DataONEObjects: "Data", "Program", and hopefully | |
* someday "Execution", as we don't allow the user to set the namespace | |
* e.g. to "PROV", so therefor we check for the currently known ProvONE classes. | |
*/ | |
if ( | |
_.contains( | |
[ | |
"Program", | |
"Data", | |
"Visualization", | |
"Document", | |
"Execution", | |
"User", | |
], | |
className, | |
) | |
) { | |
this.set("prov_instanceOfClass", [this.PROVONE + className]); | |
} else if ( | |
_.contains( | |
["Entity", "Usage", "Generation", "Association"], | |
className, | |
) | |
) { | |
this.set("prov_instanceOfClass", [this.PROV + className]); | |
} else { | |
message = | |
"The given class name: " + | |
className + | |
" is not in the known ProvONE or PROV classes."; | |
throw new Error(message); | |
} | |
}, | |
/** | |
* 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 | |
*/ | |
calculateChecksum: function (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; | |
// Do we have a file? | |
if (this.get("uploadFile") instanceof Blob) { | |
file = this.get("uploadFile"); | |
reader = new FileReader(); | |
/* Handle load errors */ | |
reader.onerror = function (event) { | |
console.log("Error reading: " + event); | |
}; | |
/* Show progress */ | |
reader.onprogress = function (event) {}; | |
/* Handle load finish */ | |
reader.onloadend = function (event) { | |
if (event.target.readyState == FileReader.DONE) { | |
hash.update(event.target.result); | |
} | |
offset += sliceSize; | |
if (_seek()) { | |
model.set("checksum", hash.hex()); | |
model.set("checksumAlgorithm", checksum.algorithm); | |
model.trigger("checksumCalculated", model.attributes); | |
} | |
}; | |
} else { | |
message = "The given object is not a blob or a file object."; | |
throw new Error(message); | |
} | |
switch (algorithm) { | |
case "MD5": | |
checksum.algorithm = algorithm; | |
hash = md5.create(); | |
_seek(); | |
break; | |
case "SHA-1": | |
// TODO: Support SHA-1 | |
// break; | |
default: | |
message = | |
"The given algorithm: " + algorithm + " is not supported."; | |
throw new Error(message); | |
} | |
/* | |
* 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; | |
// Digest the checksum when we're done calculating | |
if (offset >= file.size) { | |
hash.digest(); | |
calculated = true; | |
return calculated; | |
} | |
// slice the file and read the slice | |
slice = file.slice(offset, offset + sliceSize); | |
reader.readAsArrayBuffer(slice); | |
return calculated; | |
} | |
}, | |
/** | |
* 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) { | |
return ( | |
isDOI(customString) || | |
isDOI(this.get("id")) || | |
isDOI(this.get("seriesId")) | |
); | |
}, | |
/** | |
* 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. | |
if (MetacatUI.appModel.get("alternateRepositories").length) { | |
//Set the possibleAuthMNs attribute | |
var 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 (datasource) { | |
//Find the MN object that matches the datasource node ID | |
var 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); | |
possibleAuthMNs.push(clonedDatasourceMN); | |
} | |
} | |
//If there is an active alternate repo, move that to the top of the list of auth MNs | |
var activeAltRepo = | |
MetacatUI.appModel.get("activeAlternateRepositoryId") || ""; | |
if (activeAltRepo) { | |
var 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); | |
possibleAuthMNs.push(clonedActiveAltRepoMN); | |
} | |
} | |
//Add all the other alternate repositories to the list of auth MNs | |
var otherPossibleAuthMNs = _.reject( | |
MetacatUI.appModel.get("alternateRepositories"), | |
function (mn) { | |
return ( | |
mn.identifier == datasource || mn.identifier == activeAltRepo | |
); | |
}, | |
); | |
//Clone each MN object and add to the array | |
_.each(otherPossibleAuthMNs, function (mn) { | |
var clonedMN = Object.assign({}, mn); | |
possibleAuthMNs.push(clonedMN); | |
}); | |
//Update this model | |
this.set("possibleAuthMNs", possibleAuthMNs); | |
} | |
}, | |
/** | |
* Removes white space from string values returned by Solr when the white space causes issues. | |
* For now this only effects the `resourceMap` field, which will index new line characters and spaces | |
* when the RDF XML has those in the `identifier` XML element content. This was causing bugs where DataONEObject | |
* 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") { | |
json.resourceMap = json.resourceMap.trim(); | |
} else if (Array.isArray(json.resourceMap)) { | |
let newResourceMapIds = []; | |
_.each(json.resourceMap, function (rMapId) { | |
if (typeof rMapId == "string") { | |
newResourceMapIds.push(rMapId.trim()); | |
} | |
}); | |
json.resourceMap = newResourceMapIds; | |
} | |
}, | |
}, | |
/** @lends DataONEObject.prototype */ | |
{ | |
/** | |
* 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 = | |
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split( | |
"", | |
); | |
for (var i = 0; i < length; i++) { | |
idStr += chars[Math.floor(Math.random() * chars.length)]; | |
} | |
return idStr; | |
}, | |
}, | |
); | |
return DataONEObject; | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/models/DataONEObject.js
Lines 33 to 2623 in 554eadc
var DataONEObject = Backbone.Model.extend( | |
/** @lends DataONEObject.prototype */ { | |
type: "DataONEObject", | |
selectedInEditor: false, // Has this package member been selected and displayed in the provenance editor? | |
PROV: "http://www.w3.org/ns/prov#", | |
PROVONE: "http://purl.dataone.org/provone/2015/01/15/ontology#", | |
defaults: function () { | |
return { | |
// System Metadata attributes | |
serialVersion: null, | |
identifier: null, | |
formatId: null, | |
size: null, | |
checksum: null, | |
originalChecksum: null, | |
checksumAlgorithm: "MD5", | |
submitter: null, | |
rightsHolder: null, | |
accessPolicy: [], //An array of accessPolicy literal JS objects | |
replicationAllowed: null, | |
replicationPolicy: [], | |
obsoletes: null, | |
obsoletedBy: null, | |
archived: null, | |
dateUploaded: null, | |
dateSysMetadataModified: null, | |
originMemberNode: null, | |
authoritativeMemberNode: null, | |
replica: [], | |
seriesId: null, // uuid.v4(), (decide if we want to auto-set this) | |
mediaType: null, | |
fileName: null, | |
// Non-system metadata attributes: | |
isNew: null, | |
datasource: null, | |
insert_count_i: null, | |
read_count_i: null, | |
changePermission: null, | |
writePermission: null, | |
readPermission: null, | |
isPublic: null, | |
dateModified: null, | |
id: "urn:uuid:" + uuid.v4(), | |
sizeStr: null, | |
type: "", // Data, Metadata, or DataPackage | |
formatType: "", | |
metadataEntity: null, // A model that represents the metadata for this file, e.g. an EMLEntity model | |
latestVersion: null, | |
isDocumentedBy: null, | |
documents: [], | |
members: [], | |
resourceMap: [], | |
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 | |
uploadProgress: null, | |
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 | |
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 | |
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. | |
provSources: [], | |
provDerivations: [], | |
prov_generated: [], | |
prov_generatedByExecution: [], | |
prov_generatedByProgram: [], | |
prov_generatedByUser: [], | |
prov_hasDerivations: [], | |
prov_hasSources: [], | |
prov_instanceOfClass: [], | |
prov_used: [], | |
prov_usedByExecution: [], | |
prov_usedByProgram: [], | |
prov_usedByUser: [], | |
prov_wasDerivedFrom: [], | |
prov_wasExecutedByExecution: [], | |
prov_wasExecutedByUser: [], | |
prov_wasInformedBy: [], | |
}; | |
}, | |
initialize: function (attrs, options) { | |
if (typeof attrs == "undefined") var attrs = {}; | |
this.set("accessPolicy", this.createAccessPolicy()); | |
var model = this; | |
this.on("change:size", function () { | |
var size = Utilities.bytesToSize(model.get("size")); | |
model.set("sizeStr", size); | |
}); | |
if (attrs.size) { | |
var 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") | |
this.set("originalAttrs", Object.keys(this.attributes)); | |
else | |
this.set( | |
"originalAttrs", | |
Object.keys(DataONEObject.prototype.defaults()), | |
); | |
this.on("successSaving", this.updateRelationships); | |
//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"); | |
if (entityMetadataModel) | |
entityMetadataModel.set("dataONEObject", this); | |
}); | |
this.on("sync", function () { | |
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 | |
this.setPossibleAuthMNs(); | |
}, | |
/** | |
* Maps the lower-case sys meta node names (valid in HTML DOM) to the | |
* camel-cased sys meta node names (valid in DataONE). | |
* Used during parse() and serialize() | |
*/ | |
nodeNameMap: function () { | |
return { | |
accesspolicy: "accessPolicy", | |
accessrule: "accessRule", | |
authoritativemembernode: "authoritativeMemberNode", | |
checksumalgorithm: "checksumAlgorithm", | |
dateuploaded: "dateUploaded", | |
datesysmetadatamodified: "dateSysMetadataModified", | |
formatid: "formatId", | |
filename: "fileName", | |
nodereference: "nodeReference", | |
numberreplicas: "numberReplicas", | |
obsoletedby: "obsoletedBy", | |
originmembernode: "originMemberNode", | |
replicamembernode: "replicaMemberNode", | |
replicationallowed: "replicationAllowed", | |
replicationpolicy: "replicationPolicy", | |
replicationstatus: "replicationStatus", | |
replicaverified: "replicaVerified", | |
rightsholder: "rightsHolder", | |
serialversion: "serialVersion", | |
seriesid: "seriesId", | |
}; | |
}, | |
/** | |
* Returns the URL string where this DataONEObject can be fetched from or saved to | |
* @returns {string} | |
*/ | |
url: function () { | |
// 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(); | |
//Start the base URL string | |
var 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 | |
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 | |
else { | |
baseUrl = MetacatUI.appModel.get("objectServiceUrl"); | |
} | |
//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.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"))) | |
); | |
} | |
} | |
}, | |
/** | |
* Create the URL string that is used to download this package | |
* @returns PackageURL string for this DataONE Object | |
* @since 2.28.0 | |
*/ | |
getPackageURL: function () { | |
var 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 ( | |
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, | |
}); | |
//If this node has MNRead v2 services... | |
if (node && node.readv2) | |
url = | |
node.baseURL + | |
"/v2/packages/application%2Fbagit-097/" + | |
encodeURIComponent(this.get("id")); | |
} else if (MetacatUI.appModel.get("packageServiceUrl")) | |
url = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")); | |
return url; | |
}, | |
/** | |
* Overload Backbone.Model.fetch, so that we can set custom options for each fetch() request | |
*/ | |
fetch: function (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 = ""; | |
//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 | |
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 | |
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 | |
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", | |
}; | |
//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 | |
var fetchOptions = _.extend( | |
{ | |
dataType: "text", | |
}, | |
options, | |
); | |
} else { | |
//Use custom options for retreiving XML | |
var fetchOptions = _.extend({ | |
dataType: "text", | |
}); | |
} | |
//Add the authorization options | |
fetchOptions = _.extend( | |
fetchOptions, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//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. | |
*/ | |
parse: function (response) { | |
// If the response is XML | |
if (typeof response == "string" && response.indexOf("<") == 0) { | |
var responseDoc = $.parseHTML(response), | |
systemMetadata; | |
//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++) { | |
if ( | |
responseDoc[i].nodeType == 1 && | |
responseDoc[i].localName.indexOf("systemmetadata") > -1 | |
) { | |
systemMetadata = responseDoc[i]; | |
break; | |
} | |
} | |
//Parse the XML to JSON | |
var sysMetaValues = this.toJson(systemMetadata); | |
//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]; | |
if (camelCasedKey) { | |
sysMetaValues[camelCasedKey] = sysMetaValues[key]; | |
delete sysMetaValues[key]; | |
} | |
}, | |
this, | |
); | |
//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 | |
sysMetaValues.id = sysMetaValues.identifier; | |
//Parse the Access Policy | |
if ( | |
this.get("accessPolicy") && | |
AccessPolicy.prototype.isPrototypeOf(this.get("accessPolicy")) | |
) { | |
this.get("accessPolicy").parse( | |
$(systemMetadata).find("accesspolicy"), | |
); | |
sysMetaValues.accessPolicy = this.get("accessPolicy"); | |
} else { | |
//Create a new AccessPolicy collection, if there isn't one already. | |
sysMetaValues.accessPolicy = this.createAccessPolicy( | |
$(systemMetadata).find("accesspolicy"), | |
); | |
} | |
return sysMetaValues; | |
// If the response is a list of Solr docs | |
} else if ( | |
typeof response === "object" && | |
response.response && | |
response.response.docs | |
) { | |
//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]; | |
//Take out any empty values | |
_.each(Object.keys(doc), function (field) { | |
if (!doc[field] && doc[field] !== 0) delete doc[field]; | |
}); | |
//Remove any erroneous white space from fields | |
this.removeWhiteSpaceFromSolrFields(doc); | |
return doc; | |
} | |
// Default to returning the raw response | |
else return response; | |
}, | |
/** A utility function for converting XML to JSON */ | |
toJson: function (xml) { | |
// Create the return object | |
var obj = {}; | |
// do children | |
if (xml.hasChildNodes()) { | |
for (var i = 0; i < xml.childNodes.length; i++) { | |
var item = xml.childNodes.item(i); | |
//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; | |
//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 | |
else if ( | |
typeof obj[nodeName] == "undefined" && | |
item.nodeType == 3 | |
) { | |
obj = | |
item.nodeValue == "false" | |
? false | |
: item.nodeValue == "true" | |
? 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 (!Array.isArray(old)) old = [old]; | |
//Create a new object to store this node info | |
var newNode = {}; | |
//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); | |
} else if (item.nodeType == 3) { | |
newNode = item.nodeValue; | |
var newArray = old.concat(newNode); | |
} | |
//Store the attributes for this node | |
_.each(item.attributes, function (attr) { | |
newNode[attr.localName] = attr.nodeValue; | |
}); | |
//Replace the old array with the updated one | |
obj[nodeName] = newArray; | |
//Exit | |
continue; | |
} | |
//Store the attributes for this node | |
/*_.each(item.attributes, function(attr){ | |
obj[nodeName][attr.localName] = attr.nodeValue; | |
});*/ | |
} | |
} | |
return obj; | |
}, | |
/** | |
Serialize the DataONE object JSON to XML | |
@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") { | |
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]; | |
var node = document.createElement(key); | |
//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") { | |
containerNode.textContent = contents; | |
return containerNode; | |
} else if (Array.isArray(contents)) { | |
var allNewNodes = []; | |
for (var 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") { | |
$(node).append(this.toXML(contents, node)); | |
var attributeNames = _.without(Object.keys(json[key]), "content"); | |
} | |
$(containerNode).append(node); | |
} | |
return containerNode; | |
}, | |
/** | |
* Saves the DataONEObject System Metadata to the server | |
*/ | |
save: function (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, | |
); | |
this.set( | |
"fileName", | |
fileNameWithoutExt.replace(/[^a-zA-Z0-9]/g, "_") + extension, | |
); | |
} | |
if (!this.hasUpdates()) { | |
this.set("uploadStatus", null); | |
return; | |
} | |
//Set the upload transfer as in progress | |
this.set("uploadProgress", 2); | |
this.set("uploadStatus", "p"); | |
//Check if the checksum has been calculated yet. | |
if (!this.get("checksum")) { | |
//When it is calculated, restart this function | |
this.on("checksumCalculated", this.save); | |
//Calculate the checksum for this file | |
this.calculateChecksum(); | |
//Exit this function until the checksum is done | |
return; | |
} | |
//Create a FormData object to send data with our XHR | |
var formData = new FormData(); | |
//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 | |
if (!this.get("id")) { | |
this.set("id", "urn:uuid:" + uuid.v4()); | |
} | |
//Add the identifier to the XHR data | |
formData.append("pid", this.get("id")); | |
} | |
//Create the system metadata XML | |
var 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 | |
formData.append("sysmeta", xmlBlob, "sysmeta.xml"); | |
// Create the new object (MN.create()) | |
formData.append("object", this.get("uploadFile"), this.get("fileName")); | |
var model = this; | |
// On create(), add to the package and the metadata | |
// Note: This should be added to the parent collection | |
// but for now we are using the root collection | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
if (collection.type == "DataPackage") { | |
this.off("successSaving", collection.addNewModel); | |
this.once("successSaving", collection.addNewModel, collection); | |
} | |
}, | |
this, | |
); | |
//Put together the AJAX and Backbone.save() options | |
var requestSettings = { | |
url: this.url(), | |
cache: false, | |
contentType: false, | |
dataType: "text", | |
processData: false, | |
data: formData, | |
parse: false, | |
xhr: function () { | |
var xhr = new window.XMLHttpRequest(); | |
//Upload progress | |
xhr.upload.addEventListener( | |
"progress", | |
function (evt) { | |
if (evt.lengthComputable) { | |
var percentComplete = (evt.loaded / evt.total) * 100; | |
model.set("uploadProgress", percentComplete); | |
} | |
}, | |
false, | |
); | |
return xhr; | |
}, | |
success: this.onSuccessfulSave, | |
error: function (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. | |
if (model.serialize) { | |
model.set("checksum", model.defaults().checksum); | |
} | |
model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); | |
var numSaveAttempts = model.get("numSaveAttempts"); | |
if ( | |
numSaveAttempts < 3 && | |
(response.status == 408 || response.status == 0) | |
) { | |
//Try saving again in 10, 40, and 90 seconds | |
setTimeout( | |
function () { | |
model.save.call(model); | |
}, | |
numSaveAttempts * numSaveAttempts * 10000, | |
); | |
} else { | |
model.set("numSaveAttempts", 0); | |
var parsedResponse = $(response.responseText) | |
.not("style, title") | |
.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. " + | |
"Make sure you are connected to a reliable internet connection."; | |
model.set("errorMessage", parsedResponse); | |
//Set the model status as e for error | |
model.set("uploadStatus", "e"); | |
//Trigger a custom event for the model save error | |
model.trigger("errorSaving", parsedResponse); | |
// Track this error in our analytics | |
MetacatUI.analytics?.trackException( | |
`DataONEObject save error: ${parsedResponse}`, | |
model.get("id"), | |
true, | |
); | |
} | |
}, | |
}; | |
//Add the user settings | |
requestSettings = _.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//Send the Save request | |
Backbone.Model.prototype.save.call(this, null, requestSettings); | |
}, | |
/** | |
* This function is executed when the XHR that saves this DataONEObject has | |
* successfully completed. It can be called directly if a DataONEObject is saved | |
* without directly using the DataONEObject.save() function. | |
* @param {DataONEObject} [model] A reference to this DataONEObject model | |
* @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") { | |
var model = this; | |
} | |
model.set("numSaveAttempts", 0); | |
model.set("uploadStatus", "c"); | |
model.set("isNew", false); | |
model.trigger("successSaving", model); | |
// Get the newest sysmeta set by the MN | |
model.fetch({ | |
merge: true, | |
systemMetadataOnly: true, | |
}); | |
// Reset the content changes status | |
model.set("hasContentChanges", false); | |
//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 | |
model.set("originalChecksum", model.get("checksum")); | |
model.set("checksum", model.defaults().checksum); | |
}, | |
/** | |
* Updates the DataONEObject System Metadata to the server | |
*/ | |
updateSysMeta: function () { | |
//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 | |
// knows that the system metadata, specifically, is being updated. | |
this.set("sysMetaUploadStatus", "p"); | |
var formData = new FormData(); | |
//Add the identifier to the XHR data | |
formData.append("pid", this.get("id")); | |
var 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 | |
formData.append("sysmeta", xmlBlob, "sysmeta.xml"); | |
var model = this; | |
var baseUrl = "", | |
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 | |
else { | |
baseUrl = MetacatUI.appModel.get("metaServiceUrl"); | |
} | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(this.get("id")), | |
cache: false, | |
contentType: false, | |
dataType: "text", | |
type: "PUT", | |
processData: false, | |
data: formData, | |
parse: false, | |
success: function () { | |
model.set("numSaveAttempts", 0); | |
//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" | |
model.set("uploadStatus", "c"); | |
model.set("sysMetaUploadStatus", "c"); | |
//Trigger a custom event that the sys meta was updated | |
model.trigger("sysMetaUpdated"); | |
}, | |
error: function (xhr, status, statusCode) { | |
model.set("numSaveAttempts", model.get("numSaveAttempts") + 1); | |
var numSaveAttempts = model.get("numSaveAttempts"); | |
if (numSaveAttempts < 3 && (statusCode == 408 || statusCode == 0)) { | |
//Try saving again in 10, 40, and 90 seconds | |
setTimeout( | |
function () { | |
model.updateSysMeta.call(model); | |
}, | |
numSaveAttempts * numSaveAttempts * 10000, | |
); | |
} else { | |
model.set("numSaveAttempts", 0); | |
var parsedResponse = $(xhr.responseText) | |
.not("style, title") | |
.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. " + | |
"Make sure you are connected to a reliable internet connection."; | |
model.set("errorMessage", parsedResponse); | |
model.set("sysMetaErrorCode", statusCode); | |
model.set("uploadStatus", "e"); | |
model.set("sysMetaUploadStatus", "e"); | |
// Trigger a custom event for the sysmeta update that | |
// errored | |
model.trigger("sysMetaUpdateError"); | |
// Track this error in our analytics | |
MetacatUI.analytics?.trackException( | |
`DataONEObject update system metadata ` + | |
`error: ${parsedResponse}`, | |
model.get("id"), | |
true, | |
); | |
} | |
}, | |
}; | |
//Add the user settings | |
requestSettings = _.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
//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 | |
* 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} | |
*/ | |
checkAuthority: function (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") { | |
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) { | |
if (typeof options[userFunction] !== "function") { | |
options[userFunction] = null; | |
} | |
}); | |
// If PID is not present - check authority with seriesId | |
var identifier = this.get("id"); | |
if (identifier == null) { | |
identifier = this.get("seriesId"); | |
} | |
//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"); | |
//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 | |
baseUrl = possibleAuthMNs[0].authServiceUrl; | |
} | |
} else { | |
//Get the auth service URL from the AppModel | |
baseUrl = MetacatUI.appModel.get("authServiceUrl"); | |
} | |
if (!baseUrl) { | |
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); | |
} | |
}; | |
var model = this; | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(identifier) + "?action=" + action, | |
type: "GET", | |
success: onSuccess, | |
error: onError, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} catch (e) { | |
//Log an error to the console | |
console.error("Couldn't check the authority for this user: ", e); | |
// Track this error in our analytics | |
const name = MetacatUI.appModel.get("username"); | |
MetacatUI.analytics?.trackException( | |
`Couldn't check the authority for the user ${name}: ${e}`, | |
this.get("id"), | |
true, | |
); | |
//Set the user as unauthorized | |
model.set("isAuthorized_" + action, false); | |
model.set("isAuthorized", false); | |
return false; | |
} | |
}, | |
/** | |
* 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 | |
if (typeof sysMetaXML === "undefined" || sysMetaXML === null) { | |
xml = this.createSysMeta(); | |
} else { | |
xml = $($.parseHTML(sysMetaXML)); | |
} | |
//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("submitter") | |
.text( | |
this.get("submitter") || MetacatUI.appUserModel.get("username"), | |
); | |
xml.find("formatid").text(this.get("formatId") || this.getFormatId()); | |
//If there is a seriesId, add it | |
if (this.get("seriesId")) { | |
//Get the seriesId XML node | |
var seriesIdNode = xml.find("seriesId"); | |
//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 | |
seriesIdNode.text(this.get("seriesId")); | |
} | |
//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 | |
if (this.get("uploadFile")) { | |
xml.find("size").text(this.get("uploadFile").size); | |
} | |
//Otherwise, use the last known size | |
else { | |
xml.find("size").text(this.get("size")); | |
} | |
//Save the original checksum | |
if (!this.get("checksum") && this.get("originalChecksum")) { | |
xml.find("checksum").text(this.get("originalChecksum")); | |
} | |
//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 | |
xml | |
.find("rightsholder") | |
.text( | |
this.get("rightsHolder") || MetacatUI.appUserModel.get("username"), | |
); | |
//Write the access policy | |
accessPolicyXML = this.get("accessPolicy").serialize(); | |
// Get the access policy node, if it exists | |
accessPolicyNode = xml.find("accesspolicy"); | |
previousSiblingNode = xml.find("rightsholder"); | |
// Create an access policy node if needed | |
if (!accessPolicyNode.length && accessPolicyXML) { | |
accessPolicyNode = $(document.createElement("accesspolicy")); | |
previousSiblingNode.after(accessPolicyNode); | |
} | |
//Replace the old access policy with the new one if it exists | |
if (accessPolicyXML) { | |
accessPolicyNode.replaceWith(accessPolicyXML); | |
} else { | |
// Remove the node if it is empty | |
accessPolicyNode.remove(); | |
} | |
// Set the obsoletes node after replPolicy or accessPolicy, or rightsHolder | |
replicationPolicyNode = xml.find("replicationpolicy"); | |
accessPolicyNode = xml.find("accesspolicy"); | |
rightsHolderNode = xml.find("rightsholder"); | |
if (replicationPolicyNode.length) { | |
previousSiblingNode = replicationPolicyNode; | |
} else if (accessPolicyNode.length) { | |
previousSiblingNode = accessPolicyNode; | |
} else { | |
previousSiblingNode = rightsHolderNode; | |
} | |
obsoletesNode = xml.find("obsoletes"); | |
if (this.get("obsoletes")) { | |
if (obsoletesNode.length) { | |
obsoletesNode.text(this.get("obsoletes")); | |
} else { | |
obsoletesNode = $(document.createElement("obsoletes")).text( | |
this.get("obsoletes"), | |
); | |
previousSiblingNode.after(obsoletesNode); | |
} | |
} else { | |
if (obsoletesNode) { | |
obsoletesNode.remove(); | |
} | |
} | |
if (obsoletesNode) { | |
previousSiblingNode = obsoletesNode; | |
} | |
obsoletedByNode = xml.find("obsoletedby"); | |
//remove the obsoletedBy node if it exists | |
// TODO: Verify this is what we want to do | |
if (obsoletedByNode) { | |
obsoletedByNode.remove(); | |
} | |
xml.find("archived").text(this.get("archived") || "false"); | |
xml | |
.find("dateuploaded") | |
.text(this.get("dateUploaded") || new Date().toISOString()); | |
//Get the filename node | |
fileNameNode = xml.find("filename"); | |
//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 | |
$(fileNameNode).text(this.get("fileName")); | |
xmlString = $(document.createElement("div")).append(xml.clone()).html(); | |
//Now camel case the nodes | |
nodeNameMap = this.nodeNameMap(); | |
_.each( | |
Object.keys(nodeNameMap), | |
function (name, i) { | |
var 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] + ">"); | |
//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] + "=", | |
); | |
} | |
}, | |
this, | |
); | |
xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata"); | |
return xmlString; | |
}, | |
/** | |
* 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 | |
objectFormats["mediaTypes"] = MetacatUI.objectFormats.where({ | |
formatId: this.get("mediaType"), | |
}); | |
if ( | |
typeof fileName !== "undefined" && | |
fileName !== null && | |
fileName.length > 1 | |
) { | |
ext = fileName.substring( | |
fileName.lastIndexOf(".") + 1, | |
fileName.length, | |
); | |
objectFormats["extensions"] = MetacatUI.objectFormats.where({ | |
extension: ext, | |
}); | |
} | |
if ( | |
objectFormats["mediaTypes"].length > 0 && | |
objectFormats["extensions"].length > 0 | |
) { | |
var firstMediaType = objectFormats["mediaTypes"][0].get("formatId"); | |
var firstExtension = objectFormats["extensions"][0].get("formatId"); | |
// Check if they're equal | |
if (firstMediaType === firstExtension) { | |
formatId = firstMediaType; | |
return formatId; | |
} | |
// Handle mismatched mediaType and extension cases - additional cases can be added below | |
if ( | |
firstMediaType === "application/vnd.ms-excel" && | |
firstExtension === "text/csv" | |
) { | |
formatId = firstExtension; | |
return 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 (ext == "nc") { | |
formatId = "netCDF-3"; | |
} else { | |
formatId = objectFormats["extensions"][0].get("formatId"); | |
} | |
return formatId; | |
} | |
return formatId; | |
}, | |
/** | |
* Looks up human readable format of the DataONE Object | |
* @returns format String | |
* @since 2.28.0 | |
*/ | |
getFormat: function () { | |
var formatMap = { | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": | |
"Microsoft Excel OpenXML", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": | |
"Microsoft Word OpenXML", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12": | |
"Microsoft Office Excel 2007 binary workbooks", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation": | |
"Microsoft Office OpenXML Presentation", | |
"application/vnd.ms-excel": "Microsoft Excel", | |
"application/msword": "Microsoft Word", | |
"application/vnd.ms-powerpoint": "Microsoft Powerpoint", | |
"text/html": "HTML", | |
"text/plain": "plain text (.txt)", | |
"video/avi": "Microsoft AVI file", | |
"video/x-ms-wmv": "Windows Media Video (.wmv)", | |
"audio/x-ms-wma": "Windows Media Audio (.wma)", | |
"application/vnd.google-earth.kml xml": | |
"Google Earth Keyhole Markup Language (KML)", | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html": | |
"annotation", | |
"application/mathematica": "Mathematica Notebook", | |
"application/postscript": "Postscript", | |
"application/rtf": "Rich Text Format (RTF)", | |
"application/xml": "XML Application", | |
"text/xml": "XML", | |
"application/x-fasta": "FASTA sequence file", | |
"nexus/1997": "NEXUS File Format for Systematic Information", | |
"anvl/erc-v02": | |
"Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13", | |
"http://purl.org/dryad/terms/": | |
"Dryad Metadata Application Profile Version 3.0", | |
"http://datadryad.org/profile/v3.1": | |
"Dryad Metadata Application Profile Version 3.1", | |
"application/pdf": "PDF", | |
"application/zip": "ZIP file", | |
"http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML", | |
"http://www.w3.org/TR/rdfa-syntax": "RDFa", | |
"application/rdf xml": "RDF", | |
"text/turtle": "TURTLE", | |
"text/n3": "N3", | |
"application/x-gzip": "GZIP Format", | |
"application/x-python": "Python script", | |
"http://www.w3.org/2005/Atom": "ATOM-1.0", | |
"application/octet-stream": "octet stream (application file)", | |
"http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd": | |
"Darwin Core, v2.0", | |
"http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core", | |
"eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0", | |
"eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1", | |
"eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1", | |
"eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0", | |
"https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0", | |
}; | |
return formatMap[this.get("formatId")] || this.get("formatId"); | |
}, | |
/** | |
* 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 | |
sysmetaXML.push( | |
//'<?xml version="1.0" encoding="UTF-8"?>', | |
"<d1_v2.0:systemmetadata", | |
' xmlns:d1_v2.0="http://ns.dataone.org/service/types/v2.0"', | |
' xmlns:d1="http://ns.dataone.org/service/types/v1">', | |
" <serialversion />", | |
" <identifier />", | |
" <formatid />", | |
" <size />", | |
" <checksum />", | |
" <submitter />", | |
" <rightsholder />", | |
" <filename />", | |
"</d1_v2.0:systemmetadata>", | |
); | |
sysmetaDOM = $($.parseHTML(sysmetaXML.join(""))); | |
return sysmetaDOM; | |
}, | |
/** | |
* Create an access policy for this DataONEObject using the default access | |
* policy set in the AppModel. | |
* | |
* @param {Element} [accessPolicyXML] - An <accessPolicy> XML node | |
* that contains a list of access rules. | |
* @return {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(); | |
accessPolicy.dataONEObject = this; | |
//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, | |
// then get the parent metadata and copy it's AccessPolicy | |
let scienceMetadata = this.get("isDocumentedByModels"); | |
if ( | |
MetacatUI.appModel.get("inheritAccessPolicy") && | |
scienceMetadata && | |
scienceMetadata.length | |
) { | |
let sciMetaAccessPolicy = scienceMetadata[0].get("accessPolicy"); | |
if (sciMetaAccessPolicy) { | |
accessPolicy.copyAccessPolicy(sciMetaAccessPolicy); | |
} else { | |
accessPolicy.createDefaultPolicy(); | |
} | |
} | |
//Otherwise, set the default access policy using the AppModel configuration | |
else { | |
accessPolicy.createDefaultPolicy(); | |
} | |
} catch (e) { | |
console.error( | |
"Could create access policy, so defaulting to default", | |
e, | |
); | |
accessPolicy.createDefaultPolicy(); | |
} | |
} else { | |
//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; | |
this.listenTo(accessPolicy, "change update", function () { | |
self.trigger("change"); | |
this.addToUploadQueue(); | |
}); | |
return accessPolicy; | |
}, | |
/** | |
* Update identifiers for this object | |
* | |
* @param {string} id - Optional identifier to update with. Generated | |
* automatically when not given. | |
* | |
* Note that this method caches the objects attributes prior to | |
* updating so this.resetID() can be called in case of a failure | |
* state. | |
* | |
* Also note that this method won't run if theh oldPid attribute is | |
* set. This enables knowing before this.save is called what the next | |
* PID will be such as the case where we want to update a matching | |
* EML entity when replacing files. | |
*/ | |
updateID: function (id) { | |
// Only run once until oldPid is reset | |
if (this.get("oldPid")) { | |
return; | |
} | |
//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; | |
//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 | |
if (!this.get("seriesId") && this.get("createSeriesId")) { | |
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 | |
if (id) { | |
this.set("id", id); | |
} else { | |
if (this.get("type") == "DataPackage") { | |
this.set("id", "resource_map_urn:uuid:" + uuid.v4()); | |
} else { | |
this.set("id", "urn:uuid:" + uuid.v4()); | |
} | |
} | |
// Remove the old pid from the documents list if present | |
if (selfDocuments) { | |
index = this.get("documents").indexOf(oldPid); | |
if (index > -1) { | |
this.get("documents").splice(index, 1); | |
} | |
// And add the new pid in | |
this.get("documents").push(this.get("id")); | |
} | |
// Remove the old pid from the isDocumentedBy list if present | |
if (selfDocumentedBy) { | |
index = this.get("isDocumentedBy").indexOf(oldPid); | |
if (index > -1) { | |
this.get("isDocumentedBy").splice(index, 1); | |
} | |
// And add the new pid in | |
this.get("isDocumentedBy").push(this.get("id")); | |
} | |
// Update all models documented by this pid with the new id | |
_.each( | |
this.get("documents"), | |
function (id) { | |
(documentedModels = MetacatUI.rootDataPackage.where({ id: id })), | |
documentedModel; | |
if (documentedModels.length > 0) { | |
documentedModel = documentedModels[0]; | |
} | |
if (typeof documentedModel !== "undefined") { | |
// Find the oldPid in the array | |
if (Array.isArray(documentedModel.get("isDocumentedBy"))) { | |
index = documentedModel.get("isDocumentedBy").indexOf("oldPid"); | |
if (index > -1) { | |
// Remove it | |
documentedModel.get("isDocumentedBy").splice(index, 1); | |
} | |
// And add the new pid in | |
documentedModel.get("isDocumentedBy").push(this.get("id")); | |
} | |
} | |
}, | |
this, | |
); | |
this.trigger("change:id"); | |
//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 | |
this.set("archived", false); | |
}, | |
/** | |
* Resets the identifier for this model. This undos all of the changes made in {DataONEObject#updateID} | |
*/ | |
resetID: function () { | |
if (!this.attributeCache) return false; | |
this.set("oldPid", this.attributeCache.oldPid, { silent: true }); | |
this.set("id", this.attributeCache.id, { silent: true }); | |
this.set("obsoletes", this.attributeCache.obsoletes, { silent: true }); | |
this.set("obsoletedBy", this.attributeCache.obsoletedBy, { | |
silent: true, | |
}); | |
this.set("archived", this.attributeCache.archived, { silent: true }); | |
this.set("latestVersion", this.attributeCache.latestVersion, { | |
silent: true, | |
}); | |
//Reset the attribute cache | |
this.attributeCache = {}; | |
}, | |
/** | |
* Checks if this system metadata XML has updates that need to be synced with the server. | |
* @returns {boolean} | |
*/ | |
hasUpdates: function () { | |
if (this.isNew()) return true; | |
// Compare the new system metadata XML to the old system metadata XML | |
//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"), | |
); | |
D1ObjectClone.set(oldSysMetaAttrs); | |
var oldSysMeta = D1ObjectClone.serializeSysMeta(); | |
var newSysMeta = this.serializeSysMeta(); | |
if (oldSysMeta === "") return false; | |
return !(newSysMeta == oldSysMeta); | |
}, | |
/** | |
Set the changed flag on any system metadata or content attribute changes, | |
and set the hasContentChanges flag on content changes only | |
@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) { | |
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 ... | |
// Get a list of all changed sysmeta and content attributes | |
changedSysMetaOrContentAttrs = _.difference( | |
allChangedAttrs, | |
nonSysMetaNonContentAttrs, | |
); | |
if (changedSysMetaOrContentAttrs.length > 0) { | |
// For any sysmeta or content change, set the package dirty flag | |
if ( | |
MetacatUI.rootDataPackage && | |
MetacatUI.rootDataPackage.packageModel && | |
!MetacatUI.rootDataPackage.packageModel.get("changed") && | |
model.get("synced") | |
) { | |
MetacatUI.rootDataPackage.packageModel.set("changed", true); | |
} | |
} | |
// And get a list of all changed content attributes | |
changedContentAttrs = _.difference( | |
changedSysMetaOrContentAttrs, | |
sysMetaAttrs, | |
); | |
if ( | |
(changedContentAttrs.length > 0 && | |
!this.get("hasContentChanges") && | |
model.get("synced")) || | |
(options && options.force) | |
) { | |
this.set("hasContentChanges", true); | |
this.addToUploadQueue(); | |
} | |
}, | |
/** | |
* 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} | |
*/ | |
isNew: function () { | |
//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) { | |
return true; | |
} | |
//Check if there is an upload date that was retrieved from the server | |
return ( | |
this.get("dateUploaded") === this.defaults().dateUploaded && | |
this.get("synced") | |
); | |
}, | |
/** | |
* Updates the upload status attribute on this model and marks the collection as changed | |
*/ | |
addToUploadQueue: function () { | |
if (!this.get("synced")) { | |
return; | |
} | |
//Add this item to the queue | |
if ( | |
this.get("uploadStatus") == "c" || | |
this.get("uploadStatus") == "e" || | |
!this.get("uploadStatus") | |
) { | |
this.set("uploadStatus", "q"); | |
//Mark each DataPackage collection this model is in as changed | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
if (collection.packageModel) | |
collection.packageModel.set("changed", true); | |
}, | |
this, | |
); | |
} | |
}, | |
/** | |
* Updates the progress percentage when the model is getting uploaded | |
* @param {ProgressEvent} e - The ProgressEvent when this file is being uploaded | |
*/ | |
updateProgress: function (e) { | |
if (e.lengthComputable) { | |
var max = e.total; | |
var current = e.loaded; | |
var Percentage = (current * 100) / max; | |
if (Percentage >= 100) { | |
// process completed | |
} | |
} | |
}, | |
/** | |
* Updates the relationships with other models when this model has been updated | |
*/ | |
updateRelationships: function () { | |
_.each( | |
this.get("collections"), | |
function (collection) { | |
//Get the old id for this model | |
var 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); | |
}); | |
//Update the documents array in each model | |
_.each( | |
outdatedModels, | |
function (model) { | |
var updatedDocuments = _.without(model.get("documents"), oldId); | |
updatedDocuments.push(this.get("id")); | |
model.set("documents", updatedDocuments); | |
}, | |
this, | |
); | |
}, | |
this, | |
); | |
}, | |
/** | |
* 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 | |
if (activeAltRepo) { | |
baseUrl = activeAltRepo.metaServiceUrl; | |
} | |
//If this MetacatUI deployment is pointing to a MN, use the meta service URL from the AppModel | |
else { | |
baseUrl = MetacatUI.appModel.get("metaServiceUrl"); | |
} | |
if (!baseUrl) { | |
return; | |
} | |
//If there is no system metadata, then retrieve it first | |
if (!this.get("sysMetaXML")) { | |
this.once("sync", this.findLatestVersion); | |
this.once("systemMetadataSync", this.findLatestVersion); | |
this.fetch({ | |
url: baseUrl + encodeURIComponent(this.get("id")), | |
dataType: "text", | |
systemMetadataOnly: true, | |
}); | |
return; | |
} | |
//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") { | |
this.set("latestVersion", 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 | |
this.stopListening("sync", this.findLatestVersion); | |
this.stopListening("systemMetadataSync", this.findLatestVersion); | |
return; | |
} | |
var model = this; | |
//Get the system metadata for the possibly newer version | |
var requestSettings = { | |
url: baseUrl + encodeURIComponent(possiblyNewer), | |
type: "GET", | |
success: function (data) { | |
// the response may have an obsoletedBy element | |
var obsoletedBy = $(data).find("obsoletedBy").text(); | |
//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 | |
else { | |
model.set("latestVersion", possiblyNewer); | |
model.trigger("latestVersionFound", model); | |
//Remove the listeners now that we found the latest version | |
model.stopListening("sync", model.findLatestVersion); | |
model.stopListening( | |
"systemMetadataSync", | |
model.findLatestVersion, | |
); | |
} | |
}, | |
error: function (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); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/** | |
* A utility function that will format an XML string or XML nodes by camel-casing the node names, as necessary | |
* @param {string|Element} xml - The XML to format | |
* @returns {string} The formatted XML string | |
*/ | |
formatXML: function (xml) { | |
var nodeNameMap = this.nodeNameMap(), | |
xmlString = ""; | |
//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 | |
else if (xml.nodeType == 1) xmlString = xml.outerHTML; | |
//Text node types are left as-is | |
else if (xml.nodeType == 3) return xml.nodeValue; | |
} | |
//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. `<name>` | |
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. `<name ` | |
regEx = new RegExp("<" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + " "); | |
//Check for this node name when it's preceeded by a namespace, e.g. `:name ` | |
regEx = new RegExp(":" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + " "); | |
//Check for this node name when it's a closing tag preceeded by a namespace, e.g. `:name>` | |
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. `</name>` | |
regEx = new RegExp("</" + name + ">", "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
"</" + nodeNameMap[name] + ">", | |
); | |
//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] + "=", | |
); | |
} | |
}, | |
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)); | |
}); | |
return xmlString; | |
}, | |
/** | |
* 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; | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var xhr = new XMLHttpRequest(); | |
//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 | |
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"); | |
if (!filename) { | |
filename = | |
model.get("fileName") || | |
model.get("title") || | |
model.get("id") || | |
"download"; | |
} else | |
filename = filename | |
.substring(filename.indexOf("filename=") + 9) | |
.replace(/"/g, ""); | |
//Replace any whitespaces | |
filename = filename.trim().replace(/ /g, "_"); | |
//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 | |
else { | |
var a = document.createElement("a"); | |
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob | |
// Set the file name. | |
a.download = filename; | |
a.style.display = "none"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download DataONEObject", | |
model.get("id"), | |
); | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track the error | |
MetacatUI.analytics?.trackException( | |
`Download DataONEObject error: ${e || ""}`, | |
model.get("id"), | |
true, | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.responseType = "blob"; | |
if (MetacatUI.appUserModel.get("loggedIn")) | |
xhr.setRequestHeader( | |
"Authorization", | |
"Bearer " + MetacatUI.appUserModel.get("token"), | |
); | |
xhr.send(); | |
}, | |
/** | |
* Creates a file name for this DataONEObject and updates the `fileName` attribute | |
*/ | |
setMissingFileName: function () { | |
var objectFormats, filename, extension; | |
objectFormats = MetacatUI.objectFormats.where({ | |
formatId: this.get("formatId"), | |
}); | |
if (objectFormats.length > 0) { | |
extension = objectFormats[0].get("extension"); | |
} | |
//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 | |
else if (this.get("type") == "DataPackage") { | |
filename = "resource_map_" + this.get("id"); | |
extension = ".rdf.xml"; | |
} | |
//All other object types will just use the id | |
else { | |
filename = this.get("id"); | |
} | |
//Replace all non-alphanumeric characters with underscores | |
filename = filename.replace(/[^a-zA-Z0-9]/g, "_"); | |
if (typeof extension !== "undefined") { | |
filename = filename + "." + extension; | |
} | |
this.set("fileName", filename); | |
}, | |
/** | |
* Creates a URL for viewing more information about this object | |
* @return {string} | |
*/ | |
createViewURL: function () { | |
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 | |
* neither the seriesId nor the PID match a DOI regex. | |
* @since 2.26.0 | |
*/ | |
getCanonicalDOIIRI: function () { | |
const id = this.get("id"); | |
const seriesId = this.get("seriesId"); | |
let DOI = null; | |
if (this.isDOI(seriesId)) DOI = seriesId; | |
else if (this.isDOI(id)) DOI = id; | |
return MetacatUI.appModel.DOItoURL(DOI); | |
}, | |
/** | |
* 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 | |
*/ | |
getXMLSafeID: function (id) { | |
if (typeof id == "undefined") { | |
var id = this.get("id"); | |
} | |
//Replace XML id attribute invalid characters and patterns in the identifier | |
id = id | |
.replace(/</g, "-") | |
.replace(/:/g, "-") | |
.replace(/&[a-zA-Z0-9]+;/g); | |
return id; | |
}, | |
/**** Provenance-related functions ****/ | |
/** | |
* Returns true if this provenance field points to a source of this data or metadata object | |
* @param {string} field | |
* @returns {boolean} | |
*/ | |
isSourceField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
// Is the field we are checking a prov field? | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_generatedByExecution" || | |
field == "prov_generatedByProgram" || | |
field == "prov_used" || | |
field == "prov_wasDerivedFrom" || | |
field == "prov_wasInformedBy" | |
) | |
return true; | |
else return false; | |
}, | |
/** | |
* Returns true if this provenance field points to a derivation of this data or metadata object | |
* @param {string} field | |
* @returns {boolean} | |
*/ | |
isDerivationField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_usedByExecution" || | |
field == "prov_usedByProgram" || | |
field == "prov_hasDerivations" || | |
field == "prov_generated" | |
) | |
return true; | |
else return false; | |
}, | |
/** | |
* Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data | |
*/ | |
getType: function () { | |
//The list of formatIds that are images | |
//The list of formatIds that are images | |
var pdfIds = ["application/pdf"]; | |
var annotationIds = [ | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html", | |
]; | |
// Type has already been set, use that. | |
if (this.get("type").toLowerCase() == "metadata") return "metadata"; | |
//Determine the type via provONE | |
var instanceOfClass = this.get("prov_instanceOfClass"); | |
if ( | |
typeof instanceOfClass !== "undefined" && | |
Array.isArray(instanceOfClass) && | |
instanceOfClass.length | |
) { | |
var programClass = _.filter(instanceOfClass, function (className) { | |
return 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"; | |
} | |
//Determine the type via file format | |
if (this.isSoftware()) return "program"; | |
if (this.isData()) return "data"; | |
if (this.get("type").toLowerCase() == "metadata") return "metadata"; | |
if (this.isImage()) return "image"; | |
if (_.contains(pdfIds, this.get("formatId"))) return "PDF"; | |
if (_.contains(annotationIds, this.get("formatId"))) | |
return "annotation"; | |
else 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"]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(imageIds, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Checks the formatId of this model and determines if it is a data file. | |
* This determination is mostly used for display and the provenance editor. In the | |
* DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized | |
* 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 = [ | |
"application/atom+xml", | |
"application/mathematica", | |
"application/msword", | |
"application/netcdf", | |
"application/octet-stream", | |
"application/pdf", | |
"application/postscript", | |
"application/rdf+xml", | |
"application/rtf", | |
"application/vnd.google-earth.kml+xml", | |
"application/vnd.ms-excel", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12", | |
"application/vnd.ms-powerpoint", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation", | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", | |
"application/x-bzip2", | |
"application/x-fasta", | |
"application/x-gzip", | |
"application/x-rar-compressed", | |
"application/x-tar", | |
"application/xhtml+xml", | |
"application/xml", | |
"application/zip", | |
"audio/mpeg", | |
"audio/x-ms-wma", | |
"audio/x-wav", | |
"image/svg xml", | |
"image/svg+xml", | |
"image/bmp", | |
"image/tiff", | |
"text/anvl", | |
"text/csv", | |
"text/html", | |
"text/n3", | |
"text/plain", | |
"text/tab-separated-values", | |
"text/turtle", | |
"text/xml", | |
"video/avi", | |
"video/mp4", | |
"video/mpeg", | |
"video/quicktime", | |
"video/x-ms-wmv", | |
]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(dataIds, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Checks the formatId of this model and determines if it is a software file. | |
* This determination is mostly used for display and the provenance editor. In the | |
* DataONE API, many formatIds are considered `DATA` formatTypes, but they are categorized | |
* 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 = [ | |
"text/x-python", | |
"text/x-rsrc", | |
"text/x-matlab", | |
"text/x-sas", | |
"application/R", | |
"application/x-ipynb+json", | |
]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(softwareIds, this.get("formatId")) == -1) return false; | |
else 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"]; | |
//Does this data object match one of these IDs? | |
if (_.indexOf(ids, this.get("formatId")) == -1) return false; | |
else return true; | |
}, | |
/** | |
* Set the DataONE ProvONE provenance class | |
* param className - the shortened form of the actual classname value. The | |
* shortname will be appened to the ProvONE namespace, for example, | |
* the className "program" will result in the final class name | |
* "http://purl.dataone.org/provone/2015/01/15/ontology#Program" | |
* see https://github.com/DataONEorg/sem-prov-ontologies/blob/master/provenance/ProvONE/v1/provone.html | |
* @param {string} className | |
*/ | |
setProvClass: function (className) { | |
className = className.toLowerCase(); | |
className = className.charAt(0).toUpperCase() + className.slice(1); | |
/* This function is intended to be used for the ProvONE classes that are | |
* typically represented in DataONEObjects: "Data", "Program", and hopefully | |
* someday "Execution", as we don't allow the user to set the namespace | |
* e.g. to "PROV", so therefor we check for the currently known ProvONE classes. | |
*/ | |
if ( | |
_.contains( | |
[ | |
"Program", | |
"Data", | |
"Visualization", | |
"Document", | |
"Execution", | |
"User", | |
], | |
className, | |
) | |
) { | |
this.set("prov_instanceOfClass", [this.PROVONE + className]); | |
} else if ( | |
_.contains( | |
["Entity", "Usage", "Generation", "Association"], | |
className, | |
) | |
) { | |
this.set("prov_instanceOfClass", [this.PROV + className]); | |
} else { | |
message = | |
"The given class name: " + | |
className + | |
" is not in the known ProvONE or PROV classes."; | |
throw new Error(message); | |
} | |
}, | |
/** | |
* 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 | |
*/ | |
calculateChecksum: function (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; | |
// Do we have a file? | |
if (this.get("uploadFile") instanceof Blob) { | |
file = this.get("uploadFile"); | |
reader = new FileReader(); | |
/* Handle load errors */ | |
reader.onerror = function (event) { | |
console.log("Error reading: " + event); | |
}; | |
/* Show progress */ | |
reader.onprogress = function (event) {}; | |
/* Handle load finish */ | |
reader.onloadend = function (event) { | |
if (event.target.readyState == FileReader.DONE) { | |
hash.update(event.target.result); | |
} | |
offset += sliceSize; | |
if (_seek()) { | |
model.set("checksum", hash.hex()); | |
model.set("checksumAlgorithm", checksum.algorithm); | |
model.trigger("checksumCalculated", model.attributes); | |
} | |
}; | |
} else { | |
message = "The given object is not a blob or a file object."; | |
throw new Error(message); | |
} | |
switch (algorithm) { | |
case "MD5": | |
checksum.algorithm = algorithm; | |
hash = md5.create(); | |
_seek(); | |
break; | |
case "SHA-1": | |
// TODO: Support SHA-1 | |
// break; | |
default: | |
message = | |
"The given algorithm: " + algorithm + " is not supported."; | |
throw new Error(message); | |
} | |
/* | |
* 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; | |
// Digest the checksum when we're done calculating | |
if (offset >= file.size) { | |
hash.digest(); | |
calculated = true; | |
return calculated; | |
} | |
// slice the file and read the slice | |
slice = file.slice(offset, offset + sliceSize); | |
reader.readAsArrayBuffer(slice); | |
return calculated; | |
} | |
}, | |
/** | |
* 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) { | |
return ( | |
isDOI(customString) || | |
isDOI(this.get("id")) || | |
isDOI(this.get("seriesId")) | |
); | |
}, | |
/** | |
* 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. | |
if (MetacatUI.appModel.get("alternateRepositories").length) { | |
//Set the possibleAuthMNs attribute | |
var 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 (datasource) { | |
//Find the MN object that matches the datasource node ID | |
var 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); | |
possibleAuthMNs.push(clonedDatasourceMN); | |
} | |
} | |
//If there is an active alternate repo, move that to the top of the list of auth MNs | |
var activeAltRepo = | |
MetacatUI.appModel.get("activeAlternateRepositoryId") || ""; | |
if (activeAltRepo) { | |
var 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); | |
possibleAuthMNs.push(clonedActiveAltRepoMN); | |
} | |
} | |
//Add all the other alternate repositories to the list of auth MNs | |
var otherPossibleAuthMNs = _.reject( | |
MetacatUI.appModel.get("alternateRepositories"), | |
function (mn) { | |
return ( | |
mn.identifier == datasource || mn.identifier == activeAltRepo | |
); | |
}, | |
); | |
//Clone each MN object and add to the array | |
_.each(otherPossibleAuthMNs, function (mn) { | |
var clonedMN = Object.assign({}, mn); | |
possibleAuthMNs.push(clonedMN); | |
}); | |
//Update this model | |
this.set("possibleAuthMNs", possibleAuthMNs); | |
} | |
}, | |
/** | |
* Removes white space from string values returned by Solr when the white space causes issues. | |
* For now this only effects the `resourceMap` field, which will index new line characters and spaces | |
* when the RDF XML has those in the `identifier` XML element content. This was causing bugs where DataONEObject | |
* 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") { | |
json.resourceMap = json.resourceMap.trim(); | |
} else if (Array.isArray(json.resourceMap)) { | |
let newResourceMapIds = []; | |
_.each(json.resourceMap, function (rMapId) { | |
if (typeof rMapId == "string") { | |
newResourceMapIds.push(rMapId.trim()); | |
} | |
}); | |
json.resourceMap = newResourceMapIds; | |
} | |
}, | |
}, | |
/** @lends DataONEObject.prototype */ | |
{ | |
/** | |
* 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 = | |
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split( | |
"", | |
); | |
for (var i = 0; i < length; i++) { | |
idStr += chars[Math.floor(Math.random() * chars.length)]; | |
} | |
return idStr; | |
}, | |
}, | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/models/DataONEObject.js
Lines 133 to 175 in 554eadc
initialize: function (attrs, options) { | |
if (typeof attrs == "undefined") var attrs = {}; | |
this.set("accessPolicy", this.createAccessPolicy()); | |
var model = this; | |
this.on("change:size", function () { | |
var size = Utilities.bytesToSize(model.get("size")); | |
model.set("sizeStr", size); | |
}); | |
if (attrs.size) { | |
var 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") | |
this.set("originalAttrs", Object.keys(this.attributes)); | |
else | |
this.set( | |
"originalAttrs", | |
Object.keys(DataONEObject.prototype.defaults()), | |
); | |
this.on("successSaving", this.updateRelationships); | |
//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"); | |
if (entityMetadataModel) | |
entityMetadataModel.set("dataONEObject", this); | |
}); | |
this.on("sync", function () { | |
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 | |
this.setPossibleAuthMNs(); | |
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/models/DataONEObject.js
Lines 1928 to 1996 in 554eadc
formatXML: function (xml) { | |
var nodeNameMap = this.nodeNameMap(), | |
xmlString = ""; | |
//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 | |
else if (xml.nodeType == 1) xmlString = xml.outerHTML; | |
//Text node types are left as-is | |
else if (xml.nodeType == 3) return xml.nodeValue; | |
} | |
//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. `<name>` | |
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. `<name ` | |
regEx = new RegExp("<" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, "<" + nodeNameMap[name] + " "); | |
//Check for this node name when it's preceeded by a namespace, e.g. `:name ` | |
regEx = new RegExp(":" + name + " ", "g"); | |
xmlString = xmlString.replace(regEx, ":" + nodeNameMap[name] + " "); | |
//Check for this node name when it's a closing tag preceeded by a namespace, e.g. `:name>` | |
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. `</name>` | |
regEx = new RegExp("</" + name + ">", "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
"</" + nodeNameMap[name] + ">", | |
); | |
//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] + "=", | |
); | |
} | |
}, | |
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)); | |
}); | |
return xmlString; | |
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSDoc @returns declaration present but return expression not available in function.
metacatui/src/js/models/DataONEObject.js
Lines 1998 to 2003 in 554eadc
/** | |
* This method will download this object while | |
* sending the user's auth token in the request. | |
* @returns None | |
* @since: 2.28.0 | |
*/ |
src/js/models/PackageModel.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/models/PackageModel.js
Lines 9 to 1822 in 554eadc
], function ($, _, 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 () { | |
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 | |
totalSize: null, | |
formattedSize: "", | |
formatId: null, | |
obsoletedBy: null, | |
obsoletes: null, | |
read_count_i: null, | |
isPublic: true, | |
members: [], | |
memberIds: [], | |
sources: [], | |
derivations: [], | |
provenanceFlag: null, | |
sourcePackages: [], | |
derivationPackages: [], | |
sourceDocs: [], | |
derivationDocs: [], | |
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 | |
getArchivedMembers: false, | |
}; | |
}, | |
//Define the namespaces | |
namespaces: { | |
RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", | |
FOAF: "http://xmlns.com/foaf/0.1/", | |
OWL: "http://www.w3.org/2002/07/owl#", | |
DC: "http://purl.org/dc/elements/1.1/", | |
ORE: "http://www.openarchives.org/ore/terms/", | |
DCTERMS: "http://purl.org/dc/terms/", | |
CITO: "http://purl.org/spar/cito/", | |
XML: "http://www.w3.org/2001/XMLSchema#", | |
}, | |
sysMetaNodeMap: { | |
accesspolicy: "accessPolicy", | |
accessrule: "accessRule", | |
authoritativemembernode: "authoritativeMemberNode", | |
dateuploaded: "dateUploaded", | |
datesysmetadatamodified: "dateSysMetadataModified", | |
dateuploaded: "dateUploaded", | |
formatid: "formatId", | |
nodereference: "nodeReference", | |
obsoletedby: "obsoletedBy", | |
originmembernode: "originMemberNode", | |
replicamembernode: "replicaMemberNode", | |
replicapolicy: "replicaPolicy", | |
replicationstatus: "replicationStatus", | |
replicaverified: "replicaVerified", | |
rightsholder: "rightsHolder", | |
serialversion: "serialVersion", | |
}, | |
complete: false, | |
pending: false, | |
type: "Package", | |
// The RDF graph representing this data package | |
dataPackageGraph: null, | |
initialize: function (options) { | |
this.setURL(); | |
// Create an initial RDF graph | |
this.dataPackageGraph = rdf.graph(); | |
}, | |
setURL: function () { | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
}, | |
/* | |
* Set the URL for fetch | |
*/ | |
url: function () { | |
return ( | |
MetacatUI.appModel.get("objectServiceUrl") + | |
encodeURIComponent(this.get("id")) | |
); | |
}, | |
/* Retrieve the id of the resource map/package that this id belongs to */ | |
getMembersByMemberID: function (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 = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
success: function (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]; | |
//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.getMembers(); | |
} | |
//If there is no resource map, then this is the only document to in this package | |
else if ( | |
typeof doc.resourceMap === "undefined" || | |
!doc.resourceMap | |
) { | |
model.set("id", null); | |
model.set("memberIds", new Array(doc.id)); | |
model.set("members", [new SolrResult(doc)]); | |
model.trigger("change:members"); | |
model.flagComplete(); | |
} else { | |
model.set("id", doc.resourceMap[0]); | |
model.getMembers(); | |
} | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* 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) { | |
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"; | |
if (this.get("getArchivedMembers")) { | |
query += "&archived=archived:*"; | |
} | |
var 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) { | |
if (doc.id == model.get("id")) { | |
model.set("indexDoc", doc); | |
model.set(doc); | |
if ( | |
model.get("resourceMap") && | |
options && | |
options.getParentMetadata | |
) | |
model.getParentMetadata(); | |
} else { | |
pids.push(doc.id); | |
if (doc.formatType == "RESOURCE") { | |
var newPckg = new PackageModel(doc); | |
newPckg.set("parentPackage", model); | |
members.push(newPckg); | |
} else members.push(new SolrResult(doc)); | |
} | |
}); | |
model.set("memberIds", _.uniq(pids)); | |
model.set("members", members); | |
if (model.getNestedPackages().length > 0) | |
model.createNestedPackages(); | |
else model.flagComplete(); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return this; | |
}, | |
/* | |
* Send custom options to the Backbone.Model.fetch() function | |
*/ | |
fetch: function (options) { | |
if (!options) var options = {}; | |
var fetchOptions = _.extend({ dataType: "text" }, options); | |
//Add the authorization options | |
fetchOptions = _.extend( | |
fetchOptions, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
return Backbone.Model.prototype.fetch.call(this, fetchOptions); | |
}, | |
/* | |
* Deserialize a Package from OAI-ORE RDF XML | |
*/ | |
parse: function (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() | |
try { | |
rdf.parse( | |
response, | |
this.dataPackageGraph, | |
MetacatUI.appModel.get("objectServiceUrl") + | |
(encodeURIComponent(this.id) || | |
encodeURIComponent(this.seriesid)), | |
"application/rdf+xml", | |
); | |
// List the package members | |
memberStatements = this.dataPackageGraph.statementsMatching( | |
undefined, | |
ORE("aggregates"), | |
undefined, | |
undefined, | |
); | |
var memberPIDs = [], | |
members = [], | |
currentMembers = this.get("members"), | |
model = this; | |
// Get system metadata for each member to eval the formatId | |
_.each( | |
memberStatements, | |
function (memberStatement) { | |
memberURIParts = memberStatement.object.value.split("/"); | |
memberPIDStr = _.last(memberURIParts); | |
memberPID = decodeURIComponent(memberPIDStr); | |
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); | |
}); | |
//Add the existing model to the new member list | |
if (existingModel) { | |
members.push(existingModel); | |
} | |
//Or create a new SolrResult model | |
else { | |
members.push( | |
new SolrResult({ | |
id: decodeURIComponent(memberPID), | |
}), | |
); | |
} | |
} | |
}, | |
this, | |
); | |
//Get the documents relationships | |
var documentedByStatements = this.dataPackageGraph.statementsMatching( | |
undefined, | |
CITO("isDocumentedBy"), | |
undefined, | |
undefined, | |
), | |
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("/")); | |
//Save this as a metadata model | |
metadataPids.push(metadataPid); | |
//Set the isDocumentedBy field | |
var isDocBy = dataObj.get("isDocumentedBy"); | |
if (isDocBy && Array.isArray(isDocBy)) isDocBy.push(metadataPid); | |
else if (isDocBy && !Array.isArray(isDocBy)) | |
isDocBy = [isDocBy, metadataPid]; | |
else isDocBy = [metadataPid]; | |
dataObj.set("isDocumentedBy", isDocBy); | |
}, | |
this, | |
); | |
//Get the metadata models and mark them as metadata | |
var metadataModels = _.filter(members, function (m) { | |
return _.contains(metadataPids, m.get("id")); | |
}); | |
_.invoke(metadataModels, "set", "formatType", "METADATA"); | |
//Keep the pids in the collection for easy access later | |
this.set("memberIds", memberPIDs); | |
this.set("members", members); | |
} catch (error) { | |
console.log(error); | |
} | |
return models; | |
}, | |
/* | |
* Overwrite the Backbone.Model.save() function to set custom options | |
*/ | |
save: function (attrs, options) { | |
if (!options) var options = {}; | |
//Get the system metadata first | |
if (!this.get("hasSystemMetadata")) { | |
var model = this; | |
var requestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
success: function (response) { | |
model.parseSysMeta(response); | |
model.set("hasSystemMetadata", true); | |
model.save.call(model, null, options); | |
}, | |
dataType: "text", | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return; | |
} | |
//Create a new pid if we are updating the object | |
if (!options.sysMetaOnly) { | |
//Set a new id | |
var oldPid = this.get("id"); | |
this.set("oldPid", oldPid); | |
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(); | |
//Send the new pid, old pid, and system metadata | |
var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); | |
var formData = new FormData(); | |
formData.append("sysmeta", xmlBlob, "sysmeta"); | |
//Let's try updating the system metadata for now | |
if (options.sysMetaOnly) { | |
formData.append("pid", this.get("id")); | |
var requestSettings = { | |
url: MetacatUI.appModel.get("metaServiceUrl"), | |
type: "PUT", | |
cache: false, | |
contentType: false, | |
processData: false, | |
data: formData, | |
success: function (response) {}, | |
error: function (data) { | |
console.log("error updating system metadata"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} else { | |
//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" }); | |
formData.append("object", mapBlob); | |
//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); | |
this.set("checksum", checksum); | |
var requestSettings = { | |
url: MetacatUI.appModel.get("objectServiceUrl"), | |
type: "PUT", | |
cache: false, | |
contentType: false, | |
processData: false, | |
data: formData, | |
success: function (response) {}, | |
error: function (data) { | |
console.log("error udpating object"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
}, | |
parseSysMeta: function (response) { | |
this.set("sysMetaXML", $.parseHTML(response)); | |
var responseDoc = $.parseHTML(response), | |
systemMetadata, | |
prependXML = "", | |
appendXML = ""; | |
for (var i = 0; i < responseDoc.length; i++) { | |
if ( | |
responseDoc[i].nodeType == 1 && | |
responseDoc[i].localName.indexOf("systemmetadata") > -1 | |
) | |
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 | |
_.each( | |
Object.keys(sysMetaValues), | |
function (key) { | |
camelCasedValues[this.sysMetaNodeMap[key]] = sysMetaValues[key]; | |
}, | |
this, | |
); | |
//Set the values on the model | |
this.set(camelCasedValues); | |
}, | |
serialize: function () { | |
//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); | |
//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; | |
//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, | |
); | |
//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( | |
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 = [ | |
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"), | |
); | |
//Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph | |
var 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 | |
if ( | |
_.find(oldPidVariations, function (oldPidV) { | |
return oldPidV + "#aggregation" == statement.object.value; | |
}) | |
) { | |
var 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 | |
if (statementID.indexOf(cnResolveUrl) > -1) | |
idsFromXML.push( | |
statementID.substring(statementID.lastIndexOf("/") + 1), | |
); | |
else | |
idsFromXML.push( | |
cnResolveUrl + encodeURIComponent(statementID), | |
); | |
} | |
}, | |
this, | |
); | |
//Get all the ids from this model | |
var 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( | |
_.difference(idsFromModel, idsFromXML), | |
oldPidVariations, | |
); | |
//Create variations of all these ids too | |
var allMemberIds = idsFromModel; | |
_.each(idsFromModel, function (id) { | |
allMemberIds.push(cnResolveUrl + encodeURIComponent(id)); | |
}); | |
//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; | |
}) | |
) | |
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 = | |
this.dataPackageGraph.statementsMatching(aggregationNode); | |
_.each(aggregationSubjStatements, function (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 = | |
this.dataPackageGraph.statementsMatching( | |
undefined, | |
undefined, | |
aggregationNode, | |
); | |
_.each(aggregationObjStatements, function (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 = | |
this.dataPackageGraph.statementsMatching(rMapNode); | |
_.each(rMapStatements, function (statement) { | |
statement.subject.value = cnResolveUrl + encodeURIComponent(pid); | |
}); | |
//Change the idDescribedBy statement | |
var isDescribedByStatements = | |
this.dataPackageGraph.statementsMatching( | |
undefined, | |
ORE("isDescribedBy"), | |
rdf.sym(oldPid), | |
); | |
if (isDescribedByStatements[0]) | |
isDescribedByStatements[0].object.value = pid; | |
//Add nodes for new package members | |
_.each( | |
addedIds, | |
function (id) { | |
this.addToAggregation(id); | |
}, | |
this, | |
); | |
//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 | |
var serializer = rdf.Serializer(); | |
serializer.store = this.dataPackageGraph; | |
var 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")); | |
//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")); | |
xml.find("size").text(this.get("size")); | |
xml.find("checksum").text(this.get("checksum")); | |
xml | |
.find("submitter") | |
.text( | |
this.get("submitter") || MetacatUI.appUserModel.get("username"), | |
); | |
xml | |
.find("rightsholder") | |
.text( | |
this.get("rightsHolder") || MetacatUI.appUserModel.get("username"), | |
); | |
xml.find("archived").text(this.get("archived")); | |
xml | |
.find("dateuploaded") | |
.text(this.get("dateUploaded") || new Date().toISOString()); | |
xml | |
.find("datesysmetadatamodified") | |
.text( | |
this.get("dateSysMetadataModified") || new Date().toISOString(), | |
); | |
xml | |
.find("originmembernode") | |
.text( | |
this.get("originMemberNode") || | |
MetacatUI.nodeModel.get("currentMemberNode"), | |
); | |
xml | |
.find("authoritativemembernode") | |
.text( | |
this.get("authoritativeMemberNode") || | |
MetacatUI.nodeModel.get("currentMemberNode"), | |
); | |
if (this.get("obsoletes")) | |
xml.find("obsoletes").text(this.get("obsoletes")); | |
else xml.find("obsoletes").remove(); | |
if (this.get("obsoletedBy")) | |
xml.find("obsoletedby").text(this.get("obsoletedBy")); | |
else xml.find("obsoletedby").remove(); | |
//Write the access policy | |
var accessPolicyXML = "<accessPolicy>\n"; | |
_.each(this.get("accesspolicy"), function (policy, policyType, all) { | |
var fullPolicy = all[policyType]; | |
_.each(fullPolicy, function (policyPart) { | |
accessPolicyXML += "\t<" + policyType + ">\n"; | |
accessPolicyXML += | |
"\t\t<subject>" + policyPart.subject + "</subject>\n"; | |
var permissions = Array.isArray(policyPart.permission) | |
? policyPart.permission | |
: [policyPart.permission]; | |
_.each(permissions, function (perm) { | |
accessPolicyXML += "\t\t<permission>" + perm + "</permission>\n"; | |
}); | |
accessPolicyXML += "\t</" + policyType + ">\n"; | |
}); | |
}); | |
accessPolicyXML += "</accessPolicy>"; | |
//Replace the old access policy with the new one | |
xml.find("accesspolicy").replaceWith(accessPolicyXML); | |
var xmlString = $(document.createElement("div")) | |
.append(xml.clone()) | |
.html(); | |
//Now camel case the nodes | |
_.each( | |
Object.keys(this.sysMetaNodeMap), | |
function (name, i, allNodeNames) { | |
var regEx = new RegExp("<" + name, "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
"<" + this.sysMetaNodeMap[name], | |
); | |
var regEx = new RegExp(name + ">", "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
this.sysMetaNodeMap[name] + ">", | |
); | |
}, | |
this, | |
); | |
xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata"); | |
return xmlString; | |
}, | |
//Adds a new object to the resource map RDF graph | |
addToAggregation: function (id) { | |
if (id.indexOf(this.dataPackageGraph.cnResolveUrl) < 0) | |
var fullID = | |
this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id); | |
else { | |
var fullID = id; | |
id = id.substring( | |
this.dataPackageGraph.cnResolveUrl.lastIndexOf("/") + 1, | |
); | |
} | |
//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( | |
this.dataPackageGraph.cnResolveUrl + | |
encodeURIComponent(this.get("id")) + | |
"#aggregation", | |
), | |
idNode = rdf.literal(id, undefined, XML("string")); | |
//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 | |
this.dataPackageGraph.addStatement( | |
rdf.st(aggNode, ORE("aggregates"), objectNode), | |
); | |
//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"); | |
//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) { | |
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) | |
? _.intersection(metadataInPackage, isDocBy) | |
: _.intersection(metadataInPackage, [isDocBy]); | |
//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 | |
this.dataPackageGraph.addStatement(isDocByStatement); | |
this.dataPackageGraph.addStatement(documentsStatement); | |
}, | |
this, | |
); | |
} | |
}, | |
removeFromAggregation: function (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), | |
); | |
this.dataPackageGraph.removeStatements(statements); | |
}, | |
getParentMetadata: function () { | |
var 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 = ""; | |
if (Array.isArray(rMapIds) && rMapIds.length > 1) { | |
_.each(rMapIds, function (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"; | |
//At the end of the list of ids | |
if (i + 1 == ids.length) { | |
rMapQuery += ")"; | |
idQuery += ")"; | |
} | |
//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"; | |
} | |
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 = { | |
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) { | |
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 | |
if (_.intersection(parentIds, m.resourceMap).length) { | |
//Create a SolrResult model and add to an array | |
metadataModels.push(new SolrResult(m)); | |
} | |
} | |
}); | |
model.set("parentPackageMetadata", metadataModels); | |
model.trigger("change:parentPackageMetadata"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
//Create the URL string that is used to download this package | |
getURL: function () { | |
var 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 ( | |
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, | |
}); | |
//If this node has MNRead v2 services... | |
if (node && node.readv2) | |
url = | |
node.baseURL + | |
"/v2/packages/application%2Fbagit-097/" + | |
encodeURIComponent(this.get("id")); | |
} else if (MetacatUI.appModel.get("packageServiceUrl")) | |
url = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")); | |
this.set("url", url); | |
return url; | |
}, | |
createNestedPackages: function () { | |
var parentPackage = this, | |
nestedPackages = this.getNestedPackages(), | |
numNestedPackages = nestedPackages.length, | |
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 () { | |
numComplete++; | |
//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"); | |
}); | |
parentPackage.set("members", sorted); | |
parentPackage.flagComplete(); | |
} | |
}); | |
//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 | |
nestedPackage.getMembers(); | |
} | |
}); | |
}, | |
getNestedPackages: function () { | |
return _.where(this.get("members"), { type: "Package" }); | |
}, | |
getMemberNames: function () { | |
var metadata = this.getMetadata(); | |
if (!metadata) return false; | |
//Load the rendered metadata from the view service | |
var viewService = | |
MetacatUI.appModel.get("viewServiceUrl") + | |
encodeURIComponent(metadata.get("id")); | |
var requestSettings = { | |
url: viewService, | |
success: function (data, response, xhr) { | |
if (solrResult.get("formatType") == "METADATA") | |
entityName = solrResult.get("title"); | |
else { | |
var container = viewRef.findEntityDetailsContainer( | |
solrResult.get("id"), | |
); | |
if (container && container.length > 0) { | |
var entityName = $(container) | |
.find(".entityName") | |
.attr("data-entity-name"); | |
if (typeof entityName === "undefined" || !entityName) { | |
entityName = $(container) | |
.find( | |
".control-label:contains('Entity Name') + .controls-well", | |
) | |
.text(); | |
if (typeof entityName === "undefined" || !entityName) | |
entityName = null; | |
} | |
} else entityName = null; | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* | |
* 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; | |
//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(); | |
//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:*"; | |
var 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) { | |
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) { | |
if (member.type == "Package") return; | |
if (member.hasProvTrace()) { | |
sources = _.union(sources, member.getSources()); | |
derivations = _.union(derivations, member.getDerivations()); | |
} | |
}); | |
//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 | |
model.getExternalProvTrace(); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
getExternalProvTrace: function () { | |
var 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( | |
_.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 (!externalProvEntities.length) { | |
//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 | |
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", | |
); | |
} | |
//TODO: Find the products of programs/executions | |
//Make a comma-separated list of the provenance field names | |
var provFieldList = ""; | |
_.each( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (fieldName, i, list) { | |
provFieldList += fieldName; | |
if (i < list.length - 1) provFieldList += ","; | |
}, | |
); | |
//Combine the two queries with an OR operator | |
if (idQuery.length && metadataQuery.length) | |
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"; | |
//Send the query to the query service | |
var 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 ( | |
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 | |
if (typeof hasMultipleMaps !== "undefined") { | |
var allMapIDs = _.uniq( | |
_.flatten(_.pluck(hasMultipleMaps, "resourceMap")), | |
); | |
if (allMapIDs.length) { | |
var query = | |
"q=+-obsoletedBy:*+" + | |
MetacatUI.appSearchModel.getGroupedQuery( | |
"id", | |
allMapIDs, | |
"OR", | |
) + | |
"&fl=obsoletes,id" + | |
"&wt=json"; | |
var 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; | |
model.obsoletedResourceMaps = _.pluck( | |
resourceMaps, | |
"obsoletes", | |
); | |
model.latestResourceMaps = _.difference( | |
resourceMaps, | |
model.obsoletedResourceMaps, | |
); | |
model.sortProvTrace(data.response.docs); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} else model.sortProvTrace(data.response.docs); | |
} else model.sortProvTrace(data.response.docs); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return this; | |
}, | |
sortProvTrace: function (docs) { | |
var 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"); | |
//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"); | |
if ( | |
(typeof mapIds === "undefined" || !mapIds) && | |
docModel.get("formatType") == "DATA" && | |
(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 (_.contains(sourceIDs, doc.id)) sourceDocs.push(docModel); | |
if (_.contains(derivationIDs, doc.id)) derDocs.push(docModel); | |
} else if ( | |
(typeof mapIds === "undefined" || !mapIds) && | |
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({ | |
members: new Array(docModel), | |
}); | |
//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"); | |
//Some of these objects may have multiple resource maps | |
_.each(mapIds, function (mapId, i, list) { | |
if (!_.contains(model.obsoletedResourceMaps, mapId)) { | |
var documentsSource, documentsDerivation; | |
if (docModel.get("formatType") == "METADATA") { | |
if ( | |
_.intersection(docModel.get("documents"), sourceIDs).length | |
) | |
documentsSource = true; | |
if ( | |
_.intersection(docModel.get("documents"), derivationIDs) | |
.length | |
) | |
documentsDerivation = true; | |
} | |
//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? | |
if (!sourcePackages[mapId] && mapId != model.get("id")) { | |
//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 | |
sourcePackages[mapId] = p; | |
} | |
//If so, add this member to its package model | |
else if (mapId != model.get("id")) { | |
var memberList = sourcePackages[mapId].get("members"); | |
memberList.push(docModel); | |
sourcePackages[mapId].set("members", memberList); | |
} | |
} | |
//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? | |
if (!derPackages[mapId] && mapId != model.get("id")) { | |
//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 | |
derPackages[mapId] = p; | |
} | |
//If so, add this member to its package model | |
else if (mapId != model.get("id")) { | |
var memberList = derPackages[mapId].get("members"); | |
memberList.push(docModel); | |
derPackages[mapId].set("members", memberList); | |
} | |
} | |
} | |
}); | |
} | |
}); | |
//Transform our associative array (Object) of packages into an array | |
var newArrays = new Array(); | |
_.each( | |
new Array(sourcePackages, derPackages, sourceDocs, derDocs), | |
function (provObject) { | |
var newArray = new Array(), | |
key; | |
for (key in provObject) { | |
newArray.push(provObject[key]); | |
} | |
newArrays.push(newArray); | |
}, | |
); | |
//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. | |
model.setMemberProvTrace(); | |
//Flag that the provenance trace is complete | |
model.set("provenanceFlag", "complete"); | |
}, | |
setMemberProvTrace: function () { | |
var model = this, | |
relatedModels = this.get("relatedModels"), | |
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) { | |
if (member.type == "Package") return; | |
//Get the sources and derivations of this member | |
var memberSourceIDs = member.getSources(); | |
var 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? | |
if (_.contains(memberSourceIDs, sourcePkgMember.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, derPkgMember.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberSourceIDs, doc.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, doc.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, otherMember.get("id"))) | |
//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? | |
if (_.contains(memberSourceIDs, otherMember.get("id"))) | |
//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? | |
if ( | |
otherMember.get("type") == "program" && | |
_.contains( | |
member.get("prov_generatedByProgram"), | |
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"); | |
}); | |
member.set( | |
"prov_wasDerivedFrom", | |
_.union(member.get("prov_wasDerivedFrom"), indirectSourcesIds), | |
); | |
//otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")])); | |
member.set( | |
"provSources", | |
_.union(member.get("provSources"), indirectSources), | |
); | |
} | |
if ( | |
otherMember.get("type") == "program" && | |
_.contains( | |
member.get("prov_usedByProgram"), | |
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"); | |
}, | |
); | |
member.set( | |
"prov_hasDerivations", | |
_.union( | |
member.get("prov_hasDerivations"), | |
indirectDerivationsIds, | |
), | |
); | |
//otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")])); | |
member.set( | |
"provDerivations", | |
_.union(member.get("provDerivations"), indirectDerivationsIds), | |
); | |
} | |
}); | |
//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 | |
member.set("provSources", _.uniq(member.get("provSources"))); | |
member.set("provDerivations", _.uniq(member.get("provDerivations"))); | |
}); | |
//Update the list of related models | |
this.set("relatedModels", relatedModels); | |
}, | |
downloadWithCredentials: function () { | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var 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 | |
xhr.onload = function () { | |
//Get the file name from the Content-Disposition header | |
var filename = xhr.getResponseHeader("Content-Disposition"); | |
//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 | |
if ( | |
filename.indexOf(".zip") < 0 || | |
filename.indexOf(".zip") != filename.length - 4 | |
) { | |
filename += ".zip"; | |
} | |
//For IE, we need to use the navigator API | |
if (navigator && navigator.msSaveOrOpenBlob) { | |
navigator.msSaveOrOpenBlob(xhr.response, filename); | |
} else { | |
var 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"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download Package", | |
model.get("id"), | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download Package", | |
model.get("id"), | |
); | |
}; | |
//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"), | |
); | |
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++) { | |
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 (!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"); | |
if (!authServiceUrl) return false; | |
var model = this; | |
var requestSettings = { | |
url: | |
authServiceUrl + | |
encodeURIComponent(this.get("id")) + | |
"?action=write", | |
type: "GET", | |
success: function (data, textStatus, xhr) { | |
model.set("isAuthorized", true); | |
model.trigger("change:isAuthorized"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("isAuthorized", false); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
flagComplete: function () { | |
this.complete = true; | |
this.pending = false; | |
this.trigger("complete", this); | |
}, | |
/* | |
* function xmlToJson - A utility function for converting XML to JSON | |
* | |
* @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) { | |
// Create the return object | |
var obj = {}; | |
// do children | |
if (xml.hasChildNodes()) { | |
for (var i = 0; i < xml.childNodes.length; i++) { | |
var item = xml.childNodes.item(i); | |
//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; | |
//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 | |
else if ( | |
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 (!Array.isArray(old)) old = [old]; | |
//Create a new object to store this node info | |
var newNode = {}; | |
//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); | |
} else if (item.nodeType == 3) { | |
newNode = item.nodeValue; | |
var newArray = old.concat(newNode); | |
} | |
//Store the attributes for this node | |
_.each(item.attributes, function (attr) { | |
newNode[attr.localName] = attr.nodeValue; | |
}); | |
//Replace the old array with the updated one | |
obj[nodeName] = newArray; | |
//Exit | |
continue; | |
} | |
//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 () { | |
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"); | |
return sum + member.get("size"); | |
}); | |
} | |
this.set("totalSize", totalSize); | |
return totalSize; | |
}, | |
}, | |
); | |
return PackageModel; | |
}); |
src/js/models/PackageModel.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/models/PackageModel.js
Lines 12 to 1820 in 554eadc
var PackageModel = Backbone.Model.extend( | |
/** @lends PackageModel.prototype */ { | |
// This model contains information about a package/resource map | |
defaults: function () { | |
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 | |
totalSize: null, | |
formattedSize: "", | |
formatId: null, | |
obsoletedBy: null, | |
obsoletes: null, | |
read_count_i: null, | |
isPublic: true, | |
members: [], | |
memberIds: [], | |
sources: [], | |
derivations: [], | |
provenanceFlag: null, | |
sourcePackages: [], | |
derivationPackages: [], | |
sourceDocs: [], | |
derivationDocs: [], | |
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 | |
getArchivedMembers: false, | |
}; | |
}, | |
//Define the namespaces | |
namespaces: { | |
RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", | |
FOAF: "http://xmlns.com/foaf/0.1/", | |
OWL: "http://www.w3.org/2002/07/owl#", | |
DC: "http://purl.org/dc/elements/1.1/", | |
ORE: "http://www.openarchives.org/ore/terms/", | |
DCTERMS: "http://purl.org/dc/terms/", | |
CITO: "http://purl.org/spar/cito/", | |
XML: "http://www.w3.org/2001/XMLSchema#", | |
}, | |
sysMetaNodeMap: { | |
accesspolicy: "accessPolicy", | |
accessrule: "accessRule", | |
authoritativemembernode: "authoritativeMemberNode", | |
dateuploaded: "dateUploaded", | |
datesysmetadatamodified: "dateSysMetadataModified", | |
dateuploaded: "dateUploaded", | |
formatid: "formatId", | |
nodereference: "nodeReference", | |
obsoletedby: "obsoletedBy", | |
originmembernode: "originMemberNode", | |
replicamembernode: "replicaMemberNode", | |
replicapolicy: "replicaPolicy", | |
replicationstatus: "replicationStatus", | |
replicaverified: "replicaVerified", | |
rightsholder: "rightsHolder", | |
serialversion: "serialVersion", | |
}, | |
complete: false, | |
pending: false, | |
type: "Package", | |
// The RDF graph representing this data package | |
dataPackageGraph: null, | |
initialize: function (options) { | |
this.setURL(); | |
// Create an initial RDF graph | |
this.dataPackageGraph = rdf.graph(); | |
}, | |
setURL: function () { | |
if (MetacatUI.appModel.get("packageServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
}, | |
/* | |
* Set the URL for fetch | |
*/ | |
url: function () { | |
return ( | |
MetacatUI.appModel.get("objectServiceUrl") + | |
encodeURIComponent(this.get("id")) | |
); | |
}, | |
/* Retrieve the id of the resource map/package that this id belongs to */ | |
getMembersByMemberID: function (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 = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
success: function (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]; | |
//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.getMembers(); | |
} | |
//If there is no resource map, then this is the only document to in this package | |
else if ( | |
typeof doc.resourceMap === "undefined" || | |
!doc.resourceMap | |
) { | |
model.set("id", null); | |
model.set("memberIds", new Array(doc.id)); | |
model.set("members", [new SolrResult(doc)]); | |
model.trigger("change:members"); | |
model.flagComplete(); | |
} else { | |
model.set("id", doc.resourceMap[0]); | |
model.getMembers(); | |
} | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* 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) { | |
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"; | |
if (this.get("getArchivedMembers")) { | |
query += "&archived=archived:*"; | |
} | |
var 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) { | |
if (doc.id == model.get("id")) { | |
model.set("indexDoc", doc); | |
model.set(doc); | |
if ( | |
model.get("resourceMap") && | |
options && | |
options.getParentMetadata | |
) | |
model.getParentMetadata(); | |
} else { | |
pids.push(doc.id); | |
if (doc.formatType == "RESOURCE") { | |
var newPckg = new PackageModel(doc); | |
newPckg.set("parentPackage", model); | |
members.push(newPckg); | |
} else members.push(new SolrResult(doc)); | |
} | |
}); | |
model.set("memberIds", _.uniq(pids)); | |
model.set("members", members); | |
if (model.getNestedPackages().length > 0) | |
model.createNestedPackages(); | |
else model.flagComplete(); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return this; | |
}, | |
/* | |
* Send custom options to the Backbone.Model.fetch() function | |
*/ | |
fetch: function (options) { | |
if (!options) var options = {}; | |
var fetchOptions = _.extend({ dataType: "text" }, options); | |
//Add the authorization options | |
fetchOptions = _.extend( | |
fetchOptions, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
); | |
return Backbone.Model.prototype.fetch.call(this, fetchOptions); | |
}, | |
/* | |
* Deserialize a Package from OAI-ORE RDF XML | |
*/ | |
parse: function (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() | |
try { | |
rdf.parse( | |
response, | |
this.dataPackageGraph, | |
MetacatUI.appModel.get("objectServiceUrl") + | |
(encodeURIComponent(this.id) || | |
encodeURIComponent(this.seriesid)), | |
"application/rdf+xml", | |
); | |
// List the package members | |
memberStatements = this.dataPackageGraph.statementsMatching( | |
undefined, | |
ORE("aggregates"), | |
undefined, | |
undefined, | |
); | |
var memberPIDs = [], | |
members = [], | |
currentMembers = this.get("members"), | |
model = this; | |
// Get system metadata for each member to eval the formatId | |
_.each( | |
memberStatements, | |
function (memberStatement) { | |
memberURIParts = memberStatement.object.value.split("/"); | |
memberPIDStr = _.last(memberURIParts); | |
memberPID = decodeURIComponent(memberPIDStr); | |
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); | |
}); | |
//Add the existing model to the new member list | |
if (existingModel) { | |
members.push(existingModel); | |
} | |
//Or create a new SolrResult model | |
else { | |
members.push( | |
new SolrResult({ | |
id: decodeURIComponent(memberPID), | |
}), | |
); | |
} | |
} | |
}, | |
this, | |
); | |
//Get the documents relationships | |
var documentedByStatements = this.dataPackageGraph.statementsMatching( | |
undefined, | |
CITO("isDocumentedBy"), | |
undefined, | |
undefined, | |
), | |
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("/")); | |
//Save this as a metadata model | |
metadataPids.push(metadataPid); | |
//Set the isDocumentedBy field | |
var isDocBy = dataObj.get("isDocumentedBy"); | |
if (isDocBy && Array.isArray(isDocBy)) isDocBy.push(metadataPid); | |
else if (isDocBy && !Array.isArray(isDocBy)) | |
isDocBy = [isDocBy, metadataPid]; | |
else isDocBy = [metadataPid]; | |
dataObj.set("isDocumentedBy", isDocBy); | |
}, | |
this, | |
); | |
//Get the metadata models and mark them as metadata | |
var metadataModels = _.filter(members, function (m) { | |
return _.contains(metadataPids, m.get("id")); | |
}); | |
_.invoke(metadataModels, "set", "formatType", "METADATA"); | |
//Keep the pids in the collection for easy access later | |
this.set("memberIds", memberPIDs); | |
this.set("members", members); | |
} catch (error) { | |
console.log(error); | |
} | |
return models; | |
}, | |
/* | |
* Overwrite the Backbone.Model.save() function to set custom options | |
*/ | |
save: function (attrs, options) { | |
if (!options) var options = {}; | |
//Get the system metadata first | |
if (!this.get("hasSystemMetadata")) { | |
var model = this; | |
var requestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
success: function (response) { | |
model.parseSysMeta(response); | |
model.set("hasSystemMetadata", true); | |
model.save.call(model, null, options); | |
}, | |
dataType: "text", | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return; | |
} | |
//Create a new pid if we are updating the object | |
if (!options.sysMetaOnly) { | |
//Set a new id | |
var oldPid = this.get("id"); | |
this.set("oldPid", oldPid); | |
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(); | |
//Send the new pid, old pid, and system metadata | |
var xmlBlob = new Blob([sysMetaXML], { type: "application/xml" }); | |
var formData = new FormData(); | |
formData.append("sysmeta", xmlBlob, "sysmeta"); | |
//Let's try updating the system metadata for now | |
if (options.sysMetaOnly) { | |
formData.append("pid", this.get("id")); | |
var requestSettings = { | |
url: MetacatUI.appModel.get("metaServiceUrl"), | |
type: "PUT", | |
cache: false, | |
contentType: false, | |
processData: false, | |
data: formData, | |
success: function (response) {}, | |
error: function (data) { | |
console.log("error updating system metadata"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} else { | |
//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" }); | |
formData.append("object", mapBlob); | |
//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); | |
this.set("checksum", checksum); | |
var requestSettings = { | |
url: MetacatUI.appModel.get("objectServiceUrl"), | |
type: "PUT", | |
cache: false, | |
contentType: false, | |
processData: false, | |
data: formData, | |
success: function (response) {}, | |
error: function (data) { | |
console.log("error udpating object"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
}, | |
parseSysMeta: function (response) { | |
this.set("sysMetaXML", $.parseHTML(response)); | |
var responseDoc = $.parseHTML(response), | |
systemMetadata, | |
prependXML = "", | |
appendXML = ""; | |
for (var i = 0; i < responseDoc.length; i++) { | |
if ( | |
responseDoc[i].nodeType == 1 && | |
responseDoc[i].localName.indexOf("systemmetadata") > -1 | |
) | |
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 | |
_.each( | |
Object.keys(sysMetaValues), | |
function (key) { | |
camelCasedValues[this.sysMetaNodeMap[key]] = sysMetaValues[key]; | |
}, | |
this, | |
); | |
//Set the values on the model | |
this.set(camelCasedValues); | |
}, | |
serialize: function () { | |
//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); | |
//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; | |
//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, | |
); | |
//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( | |
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 = [ | |
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"), | |
); | |
//Using the isAggregatedBy statements, find all the DataONE object ids in the RDF graph | |
var 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 | |
if ( | |
_.find(oldPidVariations, function (oldPidV) { | |
return oldPidV + "#aggregation" == statement.object.value; | |
}) | |
) { | |
var 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 | |
if (statementID.indexOf(cnResolveUrl) > -1) | |
idsFromXML.push( | |
statementID.substring(statementID.lastIndexOf("/") + 1), | |
); | |
else | |
idsFromXML.push( | |
cnResolveUrl + encodeURIComponent(statementID), | |
); | |
} | |
}, | |
this, | |
); | |
//Get all the ids from this model | |
var 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( | |
_.difference(idsFromModel, idsFromXML), | |
oldPidVariations, | |
); | |
//Create variations of all these ids too | |
var allMemberIds = idsFromModel; | |
_.each(idsFromModel, function (id) { | |
allMemberIds.push(cnResolveUrl + encodeURIComponent(id)); | |
}); | |
//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; | |
}) | |
) | |
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 = | |
this.dataPackageGraph.statementsMatching(aggregationNode); | |
_.each(aggregationSubjStatements, function (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 = | |
this.dataPackageGraph.statementsMatching( | |
undefined, | |
undefined, | |
aggregationNode, | |
); | |
_.each(aggregationObjStatements, function (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 = | |
this.dataPackageGraph.statementsMatching(rMapNode); | |
_.each(rMapStatements, function (statement) { | |
statement.subject.value = cnResolveUrl + encodeURIComponent(pid); | |
}); | |
//Change the idDescribedBy statement | |
var isDescribedByStatements = | |
this.dataPackageGraph.statementsMatching( | |
undefined, | |
ORE("isDescribedBy"), | |
rdf.sym(oldPid), | |
); | |
if (isDescribedByStatements[0]) | |
isDescribedByStatements[0].object.value = pid; | |
//Add nodes for new package members | |
_.each( | |
addedIds, | |
function (id) { | |
this.addToAggregation(id); | |
}, | |
this, | |
); | |
//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 | |
var serializer = rdf.Serializer(); | |
serializer.store = this.dataPackageGraph; | |
var 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")); | |
//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")); | |
xml.find("size").text(this.get("size")); | |
xml.find("checksum").text(this.get("checksum")); | |
xml | |
.find("submitter") | |
.text( | |
this.get("submitter") || MetacatUI.appUserModel.get("username"), | |
); | |
xml | |
.find("rightsholder") | |
.text( | |
this.get("rightsHolder") || MetacatUI.appUserModel.get("username"), | |
); | |
xml.find("archived").text(this.get("archived")); | |
xml | |
.find("dateuploaded") | |
.text(this.get("dateUploaded") || new Date().toISOString()); | |
xml | |
.find("datesysmetadatamodified") | |
.text( | |
this.get("dateSysMetadataModified") || new Date().toISOString(), | |
); | |
xml | |
.find("originmembernode") | |
.text( | |
this.get("originMemberNode") || | |
MetacatUI.nodeModel.get("currentMemberNode"), | |
); | |
xml | |
.find("authoritativemembernode") | |
.text( | |
this.get("authoritativeMemberNode") || | |
MetacatUI.nodeModel.get("currentMemberNode"), | |
); | |
if (this.get("obsoletes")) | |
xml.find("obsoletes").text(this.get("obsoletes")); | |
else xml.find("obsoletes").remove(); | |
if (this.get("obsoletedBy")) | |
xml.find("obsoletedby").text(this.get("obsoletedBy")); | |
else xml.find("obsoletedby").remove(); | |
//Write the access policy | |
var accessPolicyXML = "<accessPolicy>\n"; | |
_.each(this.get("accesspolicy"), function (policy, policyType, all) { | |
var fullPolicy = all[policyType]; | |
_.each(fullPolicy, function (policyPart) { | |
accessPolicyXML += "\t<" + policyType + ">\n"; | |
accessPolicyXML += | |
"\t\t<subject>" + policyPart.subject + "</subject>\n"; | |
var permissions = Array.isArray(policyPart.permission) | |
? policyPart.permission | |
: [policyPart.permission]; | |
_.each(permissions, function (perm) { | |
accessPolicyXML += "\t\t<permission>" + perm + "</permission>\n"; | |
}); | |
accessPolicyXML += "\t</" + policyType + ">\n"; | |
}); | |
}); | |
accessPolicyXML += "</accessPolicy>"; | |
//Replace the old access policy with the new one | |
xml.find("accesspolicy").replaceWith(accessPolicyXML); | |
var xmlString = $(document.createElement("div")) | |
.append(xml.clone()) | |
.html(); | |
//Now camel case the nodes | |
_.each( | |
Object.keys(this.sysMetaNodeMap), | |
function (name, i, allNodeNames) { | |
var regEx = new RegExp("<" + name, "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
"<" + this.sysMetaNodeMap[name], | |
); | |
var regEx = new RegExp(name + ">", "g"); | |
xmlString = xmlString.replace( | |
regEx, | |
this.sysMetaNodeMap[name] + ">", | |
); | |
}, | |
this, | |
); | |
xmlString = xmlString.replace(/systemmetadata/g, "systemMetadata"); | |
return xmlString; | |
}, | |
//Adds a new object to the resource map RDF graph | |
addToAggregation: function (id) { | |
if (id.indexOf(this.dataPackageGraph.cnResolveUrl) < 0) | |
var fullID = | |
this.dataPackageGraph.cnResolveUrl + encodeURIComponent(id); | |
else { | |
var fullID = id; | |
id = id.substring( | |
this.dataPackageGraph.cnResolveUrl.lastIndexOf("/") + 1, | |
); | |
} | |
//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( | |
this.dataPackageGraph.cnResolveUrl + | |
encodeURIComponent(this.get("id")) + | |
"#aggregation", | |
), | |
idNode = rdf.literal(id, undefined, XML("string")); | |
//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 | |
this.dataPackageGraph.addStatement( | |
rdf.st(aggNode, ORE("aggregates"), objectNode), | |
); | |
//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"); | |
//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) { | |
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) | |
? _.intersection(metadataInPackage, isDocBy) | |
: _.intersection(metadataInPackage, [isDocBy]); | |
//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 | |
this.dataPackageGraph.addStatement(isDocByStatement); | |
this.dataPackageGraph.addStatement(documentsStatement); | |
}, | |
this, | |
); | |
} | |
}, | |
removeFromAggregation: function (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), | |
); | |
this.dataPackageGraph.removeStatements(statements); | |
}, | |
getParentMetadata: function () { | |
var 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 = ""; | |
if (Array.isArray(rMapIds) && rMapIds.length > 1) { | |
_.each(rMapIds, function (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"; | |
//At the end of the list of ids | |
if (i + 1 == ids.length) { | |
rMapQuery += ")"; | |
idQuery += ")"; | |
} | |
//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"; | |
} | |
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 = { | |
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) { | |
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 | |
if (_.intersection(parentIds, m.resourceMap).length) { | |
//Create a SolrResult model and add to an array | |
metadataModels.push(new SolrResult(m)); | |
} | |
} | |
}); | |
model.set("parentPackageMetadata", metadataModels); | |
model.trigger("change:parentPackageMetadata"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
//Create the URL string that is used to download this package | |
getURL: function () { | |
var 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 ( | |
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, | |
}); | |
//If this node has MNRead v2 services... | |
if (node && node.readv2) | |
url = | |
node.baseURL + | |
"/v2/packages/application%2Fbagit-097/" + | |
encodeURIComponent(this.get("id")); | |
} else if (MetacatUI.appModel.get("packageServiceUrl")) | |
url = | |
MetacatUI.appModel.get("packageServiceUrl") + | |
encodeURIComponent(this.get("id")); | |
this.set("url", url); | |
return url; | |
}, | |
createNestedPackages: function () { | |
var parentPackage = this, | |
nestedPackages = this.getNestedPackages(), | |
numNestedPackages = nestedPackages.length, | |
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 () { | |
numComplete++; | |
//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"); | |
}); | |
parentPackage.set("members", sorted); | |
parentPackage.flagComplete(); | |
} | |
}); | |
//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 | |
nestedPackage.getMembers(); | |
} | |
}); | |
}, | |
getNestedPackages: function () { | |
return _.where(this.get("members"), { type: "Package" }); | |
}, | |
getMemberNames: function () { | |
var metadata = this.getMetadata(); | |
if (!metadata) return false; | |
//Load the rendered metadata from the view service | |
var viewService = | |
MetacatUI.appModel.get("viewServiceUrl") + | |
encodeURIComponent(metadata.get("id")); | |
var requestSettings = { | |
url: viewService, | |
success: function (data, response, xhr) { | |
if (solrResult.get("formatType") == "METADATA") | |
entityName = solrResult.get("title"); | |
else { | |
var container = viewRef.findEntityDetailsContainer( | |
solrResult.get("id"), | |
); | |
if (container && container.length > 0) { | |
var entityName = $(container) | |
.find(".entityName") | |
.attr("data-entity-name"); | |
if (typeof entityName === "undefined" || !entityName) { | |
entityName = $(container) | |
.find( | |
".control-label:contains('Entity Name') + .controls-well", | |
) | |
.text(); | |
if (typeof entityName === "undefined" || !entityName) | |
entityName = null; | |
} | |
} else entityName = null; | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* | |
* 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; | |
//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(); | |
//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:*"; | |
var 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) { | |
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) { | |
if (member.type == "Package") return; | |
if (member.hasProvTrace()) { | |
sources = _.union(sources, member.getSources()); | |
derivations = _.union(derivations, member.getDerivations()); | |
} | |
}); | |
//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 | |
model.getExternalProvTrace(); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
getExternalProvTrace: function () { | |
var 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( | |
_.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 (!externalProvEntities.length) { | |
//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 | |
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", | |
); | |
} | |
//TODO: Find the products of programs/executions | |
//Make a comma-separated list of the provenance field names | |
var provFieldList = ""; | |
_.each( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (fieldName, i, list) { | |
provFieldList += fieldName; | |
if (i < list.length - 1) provFieldList += ","; | |
}, | |
); | |
//Combine the two queries with an OR operator | |
if (idQuery.length && metadataQuery.length) | |
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"; | |
//Send the query to the query service | |
var 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 ( | |
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 | |
if (typeof hasMultipleMaps !== "undefined") { | |
var allMapIDs = _.uniq( | |
_.flatten(_.pluck(hasMultipleMaps, "resourceMap")), | |
); | |
if (allMapIDs.length) { | |
var query = | |
"q=+-obsoletedBy:*+" + | |
MetacatUI.appSearchModel.getGroupedQuery( | |
"id", | |
allMapIDs, | |
"OR", | |
) + | |
"&fl=obsoletes,id" + | |
"&wt=json"; | |
var 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; | |
model.obsoletedResourceMaps = _.pluck( | |
resourceMaps, | |
"obsoletes", | |
); | |
model.latestResourceMaps = _.difference( | |
resourceMaps, | |
model.obsoletedResourceMaps, | |
); | |
model.sortProvTrace(data.response.docs); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} else model.sortProvTrace(data.response.docs); | |
} else model.sortProvTrace(data.response.docs); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
return this; | |
}, | |
sortProvTrace: function (docs) { | |
var 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"); | |
//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"); | |
if ( | |
(typeof mapIds === "undefined" || !mapIds) && | |
docModel.get("formatType") == "DATA" && | |
(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 (_.contains(sourceIDs, doc.id)) sourceDocs.push(docModel); | |
if (_.contains(derivationIDs, doc.id)) derDocs.push(docModel); | |
} else if ( | |
(typeof mapIds === "undefined" || !mapIds) && | |
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({ | |
members: new Array(docModel), | |
}); | |
//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"); | |
//Some of these objects may have multiple resource maps | |
_.each(mapIds, function (mapId, i, list) { | |
if (!_.contains(model.obsoletedResourceMaps, mapId)) { | |
var documentsSource, documentsDerivation; | |
if (docModel.get("formatType") == "METADATA") { | |
if ( | |
_.intersection(docModel.get("documents"), sourceIDs).length | |
) | |
documentsSource = true; | |
if ( | |
_.intersection(docModel.get("documents"), derivationIDs) | |
.length | |
) | |
documentsDerivation = true; | |
} | |
//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? | |
if (!sourcePackages[mapId] && mapId != model.get("id")) { | |
//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 | |
sourcePackages[mapId] = p; | |
} | |
//If so, add this member to its package model | |
else if (mapId != model.get("id")) { | |
var memberList = sourcePackages[mapId].get("members"); | |
memberList.push(docModel); | |
sourcePackages[mapId].set("members", memberList); | |
} | |
} | |
//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? | |
if (!derPackages[mapId] && mapId != model.get("id")) { | |
//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 | |
derPackages[mapId] = p; | |
} | |
//If so, add this member to its package model | |
else if (mapId != model.get("id")) { | |
var memberList = derPackages[mapId].get("members"); | |
memberList.push(docModel); | |
derPackages[mapId].set("members", memberList); | |
} | |
} | |
} | |
}); | |
} | |
}); | |
//Transform our associative array (Object) of packages into an array | |
var newArrays = new Array(); | |
_.each( | |
new Array(sourcePackages, derPackages, sourceDocs, derDocs), | |
function (provObject) { | |
var newArray = new Array(), | |
key; | |
for (key in provObject) { | |
newArray.push(provObject[key]); | |
} | |
newArrays.push(newArray); | |
}, | |
); | |
//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. | |
model.setMemberProvTrace(); | |
//Flag that the provenance trace is complete | |
model.set("provenanceFlag", "complete"); | |
}, | |
setMemberProvTrace: function () { | |
var model = this, | |
relatedModels = this.get("relatedModels"), | |
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) { | |
if (member.type == "Package") return; | |
//Get the sources and derivations of this member | |
var memberSourceIDs = member.getSources(); | |
var 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? | |
if (_.contains(memberSourceIDs, sourcePkgMember.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, derPkgMember.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberSourceIDs, doc.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, doc.get("id"))) | |
//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 | |
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? | |
if (_.contains(memberDerIDs, otherMember.get("id"))) | |
//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? | |
if (_.contains(memberSourceIDs, otherMember.get("id"))) | |
//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? | |
if ( | |
otherMember.get("type") == "program" && | |
_.contains( | |
member.get("prov_generatedByProgram"), | |
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"); | |
}); | |
member.set( | |
"prov_wasDerivedFrom", | |
_.union(member.get("prov_wasDerivedFrom"), indirectSourcesIds), | |
); | |
//otherMember.set("prov_hasDerivations", _.union(otherMember.get("prov_hasDerivations"), [member.get("id")])); | |
member.set( | |
"provSources", | |
_.union(member.get("provSources"), indirectSources), | |
); | |
} | |
if ( | |
otherMember.get("type") == "program" && | |
_.contains( | |
member.get("prov_usedByProgram"), | |
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"); | |
}, | |
); | |
member.set( | |
"prov_hasDerivations", | |
_.union( | |
member.get("prov_hasDerivations"), | |
indirectDerivationsIds, | |
), | |
); | |
//otherMember.set("prov_wasDerivedFrom", _.union(otherMember.get("prov_wasDerivedFrom"), [member.get("id")])); | |
member.set( | |
"provDerivations", | |
_.union(member.get("provDerivations"), indirectDerivationsIds), | |
); | |
} | |
}); | |
//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 | |
member.set("provSources", _.uniq(member.get("provSources"))); | |
member.set("provDerivations", _.uniq(member.get("provDerivations"))); | |
}); | |
//Update the list of related models | |
this.set("relatedModels", relatedModels); | |
}, | |
downloadWithCredentials: function () { | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var 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 | |
xhr.onload = function () { | |
//Get the file name from the Content-Disposition header | |
var filename = xhr.getResponseHeader("Content-Disposition"); | |
//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 | |
if ( | |
filename.indexOf(".zip") < 0 || | |
filename.indexOf(".zip") != filename.length - 4 | |
) { | |
filename += ".zip"; | |
} | |
//For IE, we need to use the navigator API | |
if (navigator && navigator.msSaveOrOpenBlob) { | |
navigator.msSaveOrOpenBlob(xhr.response, filename); | |
} else { | |
var 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"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download Package", | |
model.get("id"), | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download Package", | |
model.get("id"), | |
); | |
}; | |
//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"), | |
); | |
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++) { | |
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 (!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"); | |
if (!authServiceUrl) return false; | |
var model = this; | |
var requestSettings = { | |
url: | |
authServiceUrl + | |
encodeURIComponent(this.get("id")) + | |
"?action=write", | |
type: "GET", | |
success: function (data, textStatus, xhr) { | |
model.set("isAuthorized", true); | |
model.trigger("change:isAuthorized"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("isAuthorized", false); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
flagComplete: function () { | |
this.complete = true; | |
this.pending = false; | |
this.trigger("complete", this); | |
}, | |
/* | |
* function xmlToJson - A utility function for converting XML to JSON | |
* | |
* @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) { | |
// Create the return object | |
var obj = {}; | |
// do children | |
if (xml.hasChildNodes()) { | |
for (var i = 0; i < xml.childNodes.length; i++) { | |
var item = xml.childNodes.item(i); | |
//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; | |
//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 | |
else if ( | |
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 (!Array.isArray(old)) old = [old]; | |
//Create a new object to store this node info | |
var newNode = {}; | |
//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); | |
} else if (item.nodeType == 3) { | |
newNode = item.nodeValue; | |
var newArray = old.concat(newNode); | |
} | |
//Store the attributes for this node | |
_.each(item.attributes, function (attr) { | |
newNode[attr.localName] = attr.nodeValue; | |
}); | |
//Replace the old array with the updated one | |
obj[nodeName] = newArray; | |
//Exit | |
continue; | |
} | |
//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 () { | |
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"); | |
return sum + member.get("size"); | |
}); | |
} | |
this.set("totalSize", totalSize); | |
return totalSize; | |
}, | |
}, | |
); |
src/js/models/PackageModel.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <object-shorthand> reported by reviewdog 🐶
Expected method shorthand.
metacatui/src/js/models/PackageModel.js
Lines 1803 to 1818 in 554eadc
getTotalSize: function () { | |
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"); | |
return sum + member.get("size"); | |
}); | |
} | |
this.set("totalSize", totalSize); | |
return totalSize; | |
}, |
src/js/models/SolrResult.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <prefer-arrow-callback> reported by reviewdog 🐶
Unexpected function expression.
metacatui/src/js/models/SolrResult.js
Lines 1 to 865 in 554eadc
define(["jquery", "underscore", "backbone"], function ($, _, Backbone) { | |
/** | |
* @class SolrResult | |
* @classdesc A single result from the Solr search service | |
* @classcategory Models | |
* @extends Backbone.Model | |
*/ | |
var SolrResult = Backbone.Model.extend( | |
/** @lends SolrResult.prototype */ { | |
// This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element | |
defaults: { | |
abstract: null, | |
entityName: null, | |
indexed: true, | |
archived: false, | |
origin: "", | |
keywords: "", | |
title: "", | |
pubDate: "", | |
eastBoundCoord: "", | |
westBoundCoord: "", | |
northBoundCoord: "", | |
southBoundCoord: "", | |
attributeName: "", | |
beginDate: "", | |
endDate: "", | |
pubDate: "", | |
id: "", | |
seriesId: null, | |
resourceMap: null, | |
downloads: null, | |
citations: 0, | |
selected: false, | |
formatId: null, | |
formatType: null, | |
fileName: null, | |
datasource: null, | |
rightsHolder: null, | |
size: 0, | |
type: "", | |
url: null, | |
obsoletedBy: null, | |
geohash_9: null, | |
read_count_i: 0, | |
reads: 0, | |
isDocumentedBy: null, | |
isPublic: null, | |
isService: false, | |
serviceDescription: null, | |
serviceTitle: null, | |
serviceEndpoint: null, | |
serviceOutput: null, | |
notFound: false, | |
newestVersion: null, | |
//@type {string} - The system metadata XML as a string | |
systemMetadata: null, | |
provSources: [], | |
provDerivations: [], | |
//Provenance index fields | |
prov_generated: null, | |
prov_generatedByDataONEDN: null, | |
prov_generatedByExecution: null, | |
prov_generatedByFoafName: null, | |
prov_generatedByOrcid: null, | |
prov_generatedByProgram: null, | |
prov_generatedByUser: null, | |
prov_hasDerivations: null, | |
prov_hasSources: null, | |
prov_instanceOfClass: null, | |
prov_used: null, | |
prov_usedByDataONEDN: null, | |
prov_usedByExecution: null, | |
prov_usedByFoafName: null, | |
prov_usedByOrcid: null, | |
prov_usedByProgram: null, | |
prov_usedByUser: null, | |
prov_wasDerivedFrom: null, | |
prov_wasExecutedByExecution: null, | |
prov_wasExecutedByUser: null, | |
prov_wasGeneratedBy: null, | |
prov_wasInformedBy: null, | |
}, | |
initialize: function () { | |
this.setURL(); | |
this.on("change:id", this.setURL); | |
this.set("type", this.getType()); | |
this.on("change:read_count_i", function () { | |
this.set("reads", this.get("read_count_i")); | |
}); | |
}, | |
type: "SolrResult", | |
// Toggle the `selected` state of the result | |
toggle: function () { | |
this.selected = !this.get("selected"); | |
}, | |
/** | |
* Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data | |
* @return {string} | |
*/ | |
getType: function () { | |
//The list of formatIds that are images | |
var imageIds = [ | |
"image/gif", | |
"image/jp2", | |
"image/jpeg", | |
"image/png", | |
"image/svg xml", | |
"image/svg+xml", | |
"image/bmp", | |
]; | |
//The list of formatIds that are images | |
var pdfIds = ["application/pdf"]; | |
var annotationIds = [ | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html", | |
]; | |
var collectionIds = [ | |
"https://purl.dataone.org/collections-1.0.0", | |
"https://purl.dataone.org/collections-1.1.0", | |
]; | |
var portalIds = [ | |
"https://purl.dataone.org/portals-1.0.0", | |
"https://purl.dataone.org/portals-1.1.0", | |
]; | |
//Determine the type via provONE | |
var instanceOfClass = this.get("prov_instanceOfClass"); | |
if (typeof instanceOfClass !== "undefined") { | |
var programClass = _.filter(instanceOfClass, function (className) { | |
return className.indexOf("#Program") > -1; | |
}); | |
if (typeof programClass !== "undefined" && programClass.length) | |
return "program"; | |
} else { | |
if (this.get("prov_generated") || this.get("prov_used")) | |
return "program"; | |
} | |
//Determine the type via file format | |
if (_.contains(collectionIds, this.get("formatId"))) | |
return "collection"; | |
if (_.contains(portalIds, this.get("formatId"))) return "portal"; | |
if (this.get("formatType") == "METADATA") return "metadata"; | |
if (_.contains(imageIds, this.get("formatId"))) return "image"; | |
if (_.contains(pdfIds, this.get("formatId"))) return "PDF"; | |
if (_.contains(annotationIds, this.get("formatId"))) | |
return "annotation"; | |
else return "data"; | |
}, | |
//Returns a plain-english version of the specific format ID (for selected ids) | |
getFormat: function () { | |
var formatMap = { | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": | |
"Microsoft Excel OpenXML", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": | |
"Microsoft Word OpenXML", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12": | |
"Microsoft Office Excel 2007 binary workbooks", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation": | |
"Microsoft Office OpenXML Presentation", | |
"application/vnd.ms-excel": "Microsoft Excel", | |
"application/msword": "Microsoft Word", | |
"application/vnd.ms-powerpoint": "Microsoft Powerpoint", | |
"text/html": "HTML", | |
"text/plain": "plain text (.txt)", | |
"video/avi": "Microsoft AVI file", | |
"video/x-ms-wmv": "Windows Media Video (.wmv)", | |
"audio/x-ms-wma": "Windows Media Audio (.wma)", | |
"application/vnd.google-earth.kml xml": | |
"Google Earth Keyhole Markup Language (KML)", | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html": | |
"annotation", | |
"application/mathematica": "Mathematica Notebook", | |
"application/postscript": "Postscript", | |
"application/rtf": "Rich Text Format (RTF)", | |
"application/xml": "XML Application", | |
"text/xml": "XML", | |
"application/x-fasta": "FASTA sequence file", | |
"nexus/1997": "NEXUS File Format for Systematic Information", | |
"anvl/erc-v02": | |
"Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13", | |
"http://purl.org/dryad/terms/": | |
"Dryad Metadata Application Profile Version 3.0", | |
"http://datadryad.org/profile/v3.1": | |
"Dryad Metadata Application Profile Version 3.1", | |
"application/pdf": "PDF", | |
"application/zip": "ZIP file", | |
"http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML", | |
"http://www.w3.org/TR/rdfa-syntax": "RDFa", | |
"application/rdf xml": "RDF", | |
"text/turtle": "TURTLE", | |
"text/n3": "N3", | |
"application/x-gzip": "GZIP Format", | |
"application/x-python": "Python script", | |
"http://www.w3.org/2005/Atom": "ATOM-1.0", | |
"application/octet-stream": "octet stream (application file)", | |
"http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd": | |
"Darwin Core, v2.0", | |
"http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core", | |
"eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0", | |
"eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1", | |
"eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1", | |
"eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0", | |
"https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0", | |
}; | |
return formatMap[this.get("formatId")] || this.get("formatId"); | |
}, | |
setURL: function () { | |
if (MetacatUI.appModel.get("objectServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("objectServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
else if (MetacatUI.appModel.get("resolveServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("resolveServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
}, | |
/** | |
* 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) { | |
return ( | |
MetacatUI.appModel.isDOI(customString) || | |
MetacatUI.appModel.isDOI(this.get("id")) || | |
MetacatUI.appModel.isDOI(this.get("seriesId")) | |
); | |
}, | |
/* | |
* Checks if the currently-logged-in user is authorized to change | |
* permissions (or other action if set as parameter) on this doc | |
* @param {string} [action=changePermission] - 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. | |
*/ | |
checkAuthority: function (action = "changePermission") { | |
var authServiceUrl = MetacatUI.appModel.get("authServiceUrl"); | |
if (!authServiceUrl) return false; | |
var model = this; | |
var requestSettings = { | |
url: | |
authServiceUrl + | |
encodeURIComponent(this.get("id")) + | |
"?action=" + | |
action, | |
type: "GET", | |
success: function (data, textStatus, xhr) { | |
model.set("isAuthorized_" + action, true); | |
model.set("isAuthorized", true); | |
model.trigger("change:isAuthorized"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("isAuthorized_" + action, false); | |
model.set("isAuthorized", false); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* | |
* This method will download this object while sending the user's auth token in the request. | |
*/ | |
downloadWithCredentials: function () { | |
//if(this.get("isPublic")) return; | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var xhr = new XMLHttpRequest(); | |
//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 | |
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"); | |
if (!filename) { | |
filename = | |
model.get("fileName") || | |
model.get("title") || | |
model.get("id") || | |
"download"; | |
} else | |
filename = filename | |
.substring(filename.indexOf("filename=") + 9) | |
.replace(/"/g, ""); | |
//Replace any whitespaces | |
filename = filename.trim().replace(/ /g, "_"); | |
//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 | |
else { | |
var a = document.createElement("a"); | |
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob | |
// Set the file name. | |
a.download = filename; | |
a.style.display = "none"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download DataONEObject", | |
model.get("id"), | |
); | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track the error | |
MetacatUI.analytics?.trackException( | |
`Download DataONEObject error: ${e || ""}`, | |
model.get("id"), | |
true, | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.responseType = "blob"; | |
if (MetacatUI.appUserModel.get("loggedIn")) | |
xhr.setRequestHeader( | |
"Authorization", | |
"Bearer " + MetacatUI.appUserModel.get("token"), | |
); | |
xhr.send(); | |
}, | |
getInfo: function (fields) { | |
var model = this; | |
if (!fields) | |
var fields = | |
"abstract,id,seriesId,fileName,resourceMap,formatType,formatId,obsoletedBy,isDocumentedBy,documents,title,origin,keywords,attributeName,pubDate,eastBoundCoord,westBoundCoord,northBoundCoord,southBoundCoord,beginDate,endDate,dateUploaded,archived,datasource,replicaMN,isAuthorized,isPublic,size,read_count_i,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,serviceType,project,dateModified"; | |
var escapeSpecialChar = MetacatUI.appSearchModel.escapeSpecialChar; | |
var query = "q="; | |
//If there is no seriesId set, then search for pid or sid | |
if (!this.get("seriesId")) | |
query += | |
'(id:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("id"))) + | |
'" OR seriesId:"' + | |
escapeSpecialChar(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:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("seriesId"))) + | |
'" AND id:"' + | |
escapeSpecialChar(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:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("id"))) + | |
'" -obsoletedBy:*'; | |
query += | |
"&fl=" + | |
fields + //Specify the fields to return | |
"&wt=json&rows=1000" + //Get the results in JSON format and get 1000 rows | |
"&archived=archived:*"; //Get archived or unarchived content | |
var requestSettings = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
type: "GET", | |
success: function (data, response, xhr) { | |
//If the Solr response was not as expected, trigger and error and exit | |
if (!data || typeof data.response == "undefined") { | |
model.set("indexed", false); | |
model.trigger("getInfoError"); | |
return; | |
} | |
var docs = data.response.docs; | |
if (docs.length == 1) { | |
docs[0].resourceMap = model.parseResourceMapField(docs[0]); | |
model.set(docs[0]); | |
model.trigger("sync"); | |
} | |
//If we searched by seriesId, then let's find the most recent version in the series | |
else if (docs.length > 1) { | |
//Filter out docs that are obsoleted | |
var mostRecent = _.reject(docs, function (doc) { | |
return typeof doc.obsoletedBy !== "undefined"; | |
}); | |
//If there is only one doc that is not obsoleted (the most recent), then | |
// set this doc's values on this model | |
if (mostRecent.length == 1) { | |
mostRecent[0].resourceMap = model.parseResourceMapField( | |
mostRecent[0], | |
); | |
model.set(mostRecent[0]); | |
model.trigger("sync"); | |
} else { | |
//If there are multiple docs without an obsoletedBy statement, then | |
// retreive the head of the series via the system metadata | |
var sysMetaRequestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(docs[0].seriesId), | |
type: "GET", | |
success: function (sysMetaData) { | |
//Get the identifier node from the system metadata | |
var seriesHeadID = $(sysMetaData).find("identifier").text(); | |
//Get the doc from the Solr results with that identifier | |
var seriesHead = _.findWhere(docs, { id: seriesHeadID }); | |
//If there is a doc in the Solr results list that matches the series head id | |
if (seriesHead) { | |
seriesHead.resourceMap = | |
model.parseResourceMapField(seriesHead); | |
//Set those values on this model | |
model.set(seriesHead); | |
} | |
//Otherwise, just fall back on the first doc in the list | |
else if (mostRecent.length) { | |
mostRecent[0].resourceMap = model.parseResourceMapField( | |
mostRecent[0], | |
); | |
model.set(mostRecent[0]); | |
} else { | |
docs[0].resourceMap = model.parseResourceMapField( | |
docs[0], | |
); | |
model.set(docs[0]); | |
} | |
model.trigger("sync"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
// Fall back on the first doc in the list | |
if (mostRecent.length) { | |
model.set(mostRecent[0]); | |
} else { | |
model.set(docs[0]); | |
} | |
model.trigger("sync"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
sysMetaRequestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
} else { | |
model.set("indexed", false); | |
//Try getting the system metadata as a backup | |
model.getSysMeta(); | |
} | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("indexed", false); | |
model.trigger("getInfoError"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
getCitationInfo: function () { | |
this.getInfo( | |
"id,seriesId,origin,pubDate,dateUploaded,title,datasource,project", | |
); | |
}, | |
/* | |
* Get the system metadata for this object | |
*/ | |
getSysMeta: function () { | |
var url = | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
model = this; | |
var requestSettings = { | |
url: url, | |
type: "GET", | |
dataType: "text", | |
success: function (data, response, xhr) { | |
if (data && data.length) { | |
model.set("systemMetadata", data); | |
} | |
//Check if this is archvied | |
var archived = $(data).find("archived").text() == "true"; | |
model.set("archived", archived); | |
//Get the file size | |
model.set("size", $(data).find("size").text() || ""); | |
//Get the entity name | |
model.set("filename", $(data).find("filename").text() || ""); | |
//Check if this is a metadata doc | |
var formatId = $(data).find("formatid").text() || "", | |
formatType; | |
model.set("formatId", formatId); | |
if ( | |
formatId.indexOf("ecoinformatics.org") > -1 || | |
formatId.indexOf("FGDC") > -1 || | |
formatId.indexOf("INCITS") > -1 || | |
formatId.indexOf("namespaces/netcdf") > -1 || | |
formatId.indexOf("waterML") > -1 || | |
formatId.indexOf("darwin") > -1 || | |
formatId.indexOf("dryad") > -1 || | |
formatId.indexOf("http://www.loc.gov/METS") > -1 || | |
formatId.indexOf("ddi:codebook:2_5") > -1 || | |
formatId.indexOf("http://www.icpsr.umich.edu/DDI") > -1 || | |
formatId.indexOf( | |
"http://purl.org/ornl/schema/mercury/terms/v1.0", | |
) > -1 || | |
formatId.indexOf("datacite") > -1 || | |
formatId.indexOf("isotc211") > -1 || | |
formatId.indexOf("metadata") > -1 | |
) | |
model.set("formatType", "METADATA"); | |
//Trigger the sync event so the app knows we found the model info | |
model.trigger("sync"); | |
}, | |
error: function (response) { | |
//When the user is unauthorized to access this object, trigger a 401 error | |
if (response.status == 401) { | |
model.set("notFound", true); | |
model.trigger("401"); | |
} | |
//When the object doesn't exist, trigger a 404 error | |
else if (response.status == 404) { | |
model.set("notFound", true); | |
model.trigger("404"); | |
} | |
//Other error codes trigger a generic error | |
else { | |
model.trigger("error"); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
//Transgresses the obsolence chain until it finds the newest version that this user is authorized to read | |
findLatestVersion: function (newestVersion, possiblyNewer) { | |
// Make sure we have the /meta service configured | |
if (!MetacatUI.appModel.get("metaServiceUrl")) return; | |
//If no pid was supplied, use this model's id | |
if (!newestVersion) { | |
var newestVersion = this.get("id"); | |
var possiblyNewer = this.get("obsoletedBy"); | |
} | |
//If this isn't obsoleted by anything, then there is no newer version | |
if (!possiblyNewer) { | |
this.set("newestVersion", newestVersion); | |
return; | |
} | |
var model = this; | |
//Get the system metadata for the possibly newer version | |
var requestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(possiblyNewer), | |
type: "GET", | |
success: function (data) { | |
// the response may have an obsoletedBy element | |
var obsoletedBy = $(data).find("obsoletedBy").text(); | |
//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 | |
else model.set("newestVersion", possiblyNewer); | |
}, | |
error: function (xhr) { | |
//If this newer version isn't found or accessible, then save the last | |
// accessible id as the newest version | |
if ( | |
xhr.status == 401 || | |
xhr.status == 404 || | |
xhr.status == "401" || | |
xhr.status == "404" | |
) { | |
model.set("newestVersion", newestVersion); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/**** Provenance-related functions ****/ | |
/* | |
* Returns true if this provenance field points to a source of this data or metadata object | |
*/ | |
isSourceField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_generatedByExecution" || | |
field == "prov_generatedByProgram" || | |
field == "prov_used" || | |
field == "prov_wasDerivedFrom" || | |
field == "prov_wasInformedBy" | |
) | |
return true; | |
else return false; | |
}, | |
/* | |
* Returns true if this provenance field points to a derivation of this data or metadata object | |
*/ | |
isDerivationField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_usedByExecution" || | |
field == "prov_usedByProgram" || | |
field == "prov_hasDerivations" || | |
field == "prov_generated" | |
) | |
return true; | |
else return false; | |
}, | |
/* | |
* Returns true if this SolrResult has a provenance trace (i.e. has either sources or derivations) | |
*/ | |
hasProvTrace: function () { | |
if (this.get("formatType") == "METADATA") { | |
if (this.get("prov_hasSources") || this.get("prov_hasDerivations")) | |
return true; | |
} | |
var fieldNames = MetacatUI.appSearchModel.getProvFields(), | |
currentField = ""; | |
for (var i = 0; i < fieldNames.length; i++) { | |
currentField = fieldNames[i]; | |
if (this.has(currentField)) return true; | |
} | |
return false; | |
}, | |
/* | |
* Returns an array of all the IDs of objects that are sources of this object | |
*/ | |
getSources: function () { | |
var sources = new Array(), | |
model = this, | |
//Get the prov fields but leave out references to executions which are not used in the UI yet | |
fields = _.reject( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (f) { | |
return f.indexOf("xecution") > -1; | |
}, | |
); //Leave out the first e in execution so we don't have to worry about case sensitivity | |
_.each(fields, function (provField, i) { | |
if (model.isSourceField(provField) && model.has(provField)) | |
sources.push(model.get(provField)); | |
}); | |
return _.uniq(_.flatten(sources)); | |
}, | |
/* | |
* Returns an array of all the IDs of objects that are derivations of this object | |
*/ | |
getDerivations: function () { | |
var derivations = new Array(), | |
model = this, | |
//Get the prov fields but leave out references to executions which are not used in the UI yet | |
fields = _.reject( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (f) { | |
return f.indexOf("xecution") > -1; | |
}, | |
); //Leave out the first e in execution so we don't have to worry about case sensitivity | |
_.each(fields, function (provField, i) { | |
if (model.isDerivationField(provField) && model.has(provField)) | |
derivations.push(model.get(provField)); | |
}); | |
return _.uniq(_.flatten(derivations)); | |
}, | |
getInputs: function () { | |
return this.get("prov_used"); | |
}, | |
getOutputs: function () { | |
return this.get("prov_generated"); | |
}, | |
/* | |
* Uses the app configuration to check if this model's metrics should be hidden in the display | |
* | |
* @return {boolean} | |
*/ | |
hideMetrics: function () { | |
//If the AppModel is configured with cases of where to hide metrics, | |
if ( | |
typeof MetacatUI.appModel.get("hideMetricsWhen") == "object" && | |
MetacatUI.appModel.get("hideMetricsWhen") | |
) { | |
//Check for at least one match | |
return _.some( | |
MetacatUI.appModel.get("hideMetricsWhen"), | |
function (value, modelProperty) { | |
//Get the value of this property from this model | |
var modelValue = this.get(modelProperty); | |
//Check for the presence of this model's value in the AppModel value | |
if (Array.isArray(value) && typeof modelValue == "string") { | |
return _.contains(value, modelValue); | |
} | |
//Check for the presence of the AppModel's value in this model's value | |
else if (typeof value == "string" && Array.isArray(modelValue)) { | |
return _.contains(modelValue, value); | |
} | |
//Check for overlap of two arrays | |
else if (Array.isArray(value) && Array.isArray(modelValue)) { | |
return _.intersection(value, modelValue).length > 0; | |
} | |
//If the AppModel value is a function, execute it | |
else if (typeof value == "function") { | |
return value(modelValue); | |
} | |
//Otherwise, just check for equality | |
else { | |
return value === modelValue; | |
} | |
}, | |
this, | |
); | |
} else { | |
return false; | |
} | |
}, | |
/** | |
* Creates a URL for viewing more information about this metadata | |
* @return {string} | |
*/ | |
createViewURL: function () { | |
return this.getType() == "portal" || this.getType() == "collection" | |
? MetacatUI.root + | |
"/" + | |
MetacatUI.appModel.get("portalTermPlural") + | |
"/" + | |
encodeURIComponent( | |
this.get("label") || this.get("seriesId") || this.get("id"), | |
) | |
: MetacatUI.root + | |
"/view/" + | |
encodeURIComponent(this.get("seriesId") || this.get("id")); | |
}, | |
parseResourceMapField: function (json) { | |
if (typeof json.resourceMap == "string") { | |
return json.resourceMap.trim(); | |
} else if (Array.isArray(json.resourceMap)) { | |
let newResourceMapIds = []; | |
_.each(json.resourceMap, function (rMapId) { | |
if (typeof rMapId == "string") { | |
newResourceMapIds.push(rMapId.trim()); | |
} | |
}); | |
return newResourceMapIds; | |
} | |
//If nothing works so far, return an empty array | |
return []; | |
}, | |
}, | |
); | |
return SolrResult; | |
}); |
src/js/models/SolrResult.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 [eslint] <no-var> reported by reviewdog 🐶
Unexpected var, use let or const instead.
metacatui/src/js/models/SolrResult.js
Lines 8 to 863 in 554eadc
var SolrResult = Backbone.Model.extend( | |
/** @lends SolrResult.prototype */ { | |
// This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element | |
defaults: { | |
abstract: null, | |
entityName: null, | |
indexed: true, | |
archived: false, | |
origin: "", | |
keywords: "", | |
title: "", | |
pubDate: "", | |
eastBoundCoord: "", | |
westBoundCoord: "", | |
northBoundCoord: "", | |
southBoundCoord: "", | |
attributeName: "", | |
beginDate: "", | |
endDate: "", | |
pubDate: "", | |
id: "", | |
seriesId: null, | |
resourceMap: null, | |
downloads: null, | |
citations: 0, | |
selected: false, | |
formatId: null, | |
formatType: null, | |
fileName: null, | |
datasource: null, | |
rightsHolder: null, | |
size: 0, | |
type: "", | |
url: null, | |
obsoletedBy: null, | |
geohash_9: null, | |
read_count_i: 0, | |
reads: 0, | |
isDocumentedBy: null, | |
isPublic: null, | |
isService: false, | |
serviceDescription: null, | |
serviceTitle: null, | |
serviceEndpoint: null, | |
serviceOutput: null, | |
notFound: false, | |
newestVersion: null, | |
//@type {string} - The system metadata XML as a string | |
systemMetadata: null, | |
provSources: [], | |
provDerivations: [], | |
//Provenance index fields | |
prov_generated: null, | |
prov_generatedByDataONEDN: null, | |
prov_generatedByExecution: null, | |
prov_generatedByFoafName: null, | |
prov_generatedByOrcid: null, | |
prov_generatedByProgram: null, | |
prov_generatedByUser: null, | |
prov_hasDerivations: null, | |
prov_hasSources: null, | |
prov_instanceOfClass: null, | |
prov_used: null, | |
prov_usedByDataONEDN: null, | |
prov_usedByExecution: null, | |
prov_usedByFoafName: null, | |
prov_usedByOrcid: null, | |
prov_usedByProgram: null, | |
prov_usedByUser: null, | |
prov_wasDerivedFrom: null, | |
prov_wasExecutedByExecution: null, | |
prov_wasExecutedByUser: null, | |
prov_wasGeneratedBy: null, | |
prov_wasInformedBy: null, | |
}, | |
initialize: function () { | |
this.setURL(); | |
this.on("change:id", this.setURL); | |
this.set("type", this.getType()); | |
this.on("change:read_count_i", function () { | |
this.set("reads", this.get("read_count_i")); | |
}); | |
}, | |
type: "SolrResult", | |
// Toggle the `selected` state of the result | |
toggle: function () { | |
this.selected = !this.get("selected"); | |
}, | |
/** | |
* Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data | |
* @return {string} | |
*/ | |
getType: function () { | |
//The list of formatIds that are images | |
var imageIds = [ | |
"image/gif", | |
"image/jp2", | |
"image/jpeg", | |
"image/png", | |
"image/svg xml", | |
"image/svg+xml", | |
"image/bmp", | |
]; | |
//The list of formatIds that are images | |
var pdfIds = ["application/pdf"]; | |
var annotationIds = [ | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html", | |
]; | |
var collectionIds = [ | |
"https://purl.dataone.org/collections-1.0.0", | |
"https://purl.dataone.org/collections-1.1.0", | |
]; | |
var portalIds = [ | |
"https://purl.dataone.org/portals-1.0.0", | |
"https://purl.dataone.org/portals-1.1.0", | |
]; | |
//Determine the type via provONE | |
var instanceOfClass = this.get("prov_instanceOfClass"); | |
if (typeof instanceOfClass !== "undefined") { | |
var programClass = _.filter(instanceOfClass, function (className) { | |
return className.indexOf("#Program") > -1; | |
}); | |
if (typeof programClass !== "undefined" && programClass.length) | |
return "program"; | |
} else { | |
if (this.get("prov_generated") || this.get("prov_used")) | |
return "program"; | |
} | |
//Determine the type via file format | |
if (_.contains(collectionIds, this.get("formatId"))) | |
return "collection"; | |
if (_.contains(portalIds, this.get("formatId"))) return "portal"; | |
if (this.get("formatType") == "METADATA") return "metadata"; | |
if (_.contains(imageIds, this.get("formatId"))) return "image"; | |
if (_.contains(pdfIds, this.get("formatId"))) return "PDF"; | |
if (_.contains(annotationIds, this.get("formatId"))) | |
return "annotation"; | |
else return "data"; | |
}, | |
//Returns a plain-english version of the specific format ID (for selected ids) | |
getFormat: function () { | |
var formatMap = { | |
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": | |
"Microsoft Excel OpenXML", | |
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": | |
"Microsoft Word OpenXML", | |
"application/vnd.ms-excel.sheet.binary.macroEnabled.12": | |
"Microsoft Office Excel 2007 binary workbooks", | |
"application/vnd.openxmlformats-officedocument.presentationml.presentation": | |
"Microsoft Office OpenXML Presentation", | |
"application/vnd.ms-excel": "Microsoft Excel", | |
"application/msword": "Microsoft Word", | |
"application/vnd.ms-powerpoint": "Microsoft Powerpoint", | |
"text/html": "HTML", | |
"text/plain": "plain text (.txt)", | |
"video/avi": "Microsoft AVI file", | |
"video/x-ms-wmv": "Windows Media Video (.wmv)", | |
"audio/x-ms-wma": "Windows Media Audio (.wma)", | |
"application/vnd.google-earth.kml xml": | |
"Google Earth Keyhole Markup Language (KML)", | |
"http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html": | |
"annotation", | |
"application/mathematica": "Mathematica Notebook", | |
"application/postscript": "Postscript", | |
"application/rtf": "Rich Text Format (RTF)", | |
"application/xml": "XML Application", | |
"text/xml": "XML", | |
"application/x-fasta": "FASTA sequence file", | |
"nexus/1997": "NEXUS File Format for Systematic Information", | |
"anvl/erc-v02": | |
"Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13", | |
"http://purl.org/dryad/terms/": | |
"Dryad Metadata Application Profile Version 3.0", | |
"http://datadryad.org/profile/v3.1": | |
"Dryad Metadata Application Profile Version 3.1", | |
"application/pdf": "PDF", | |
"application/zip": "ZIP file", | |
"http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML", | |
"http://www.w3.org/TR/rdfa-syntax": "RDFa", | |
"application/rdf xml": "RDF", | |
"text/turtle": "TURTLE", | |
"text/n3": "N3", | |
"application/x-gzip": "GZIP Format", | |
"application/x-python": "Python script", | |
"http://www.w3.org/2005/Atom": "ATOM-1.0", | |
"application/octet-stream": "octet stream (application file)", | |
"http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd": | |
"Darwin Core, v2.0", | |
"http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core", | |
"eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0", | |
"eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1", | |
"eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1", | |
"eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0", | |
"https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0", | |
}; | |
return formatMap[this.get("formatId")] || this.get("formatId"); | |
}, | |
setURL: function () { | |
if (MetacatUI.appModel.get("objectServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("objectServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
else if (MetacatUI.appModel.get("resolveServiceUrl")) | |
this.set( | |
"url", | |
MetacatUI.appModel.get("resolveServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
); | |
}, | |
/** | |
* 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) { | |
return ( | |
MetacatUI.appModel.isDOI(customString) || | |
MetacatUI.appModel.isDOI(this.get("id")) || | |
MetacatUI.appModel.isDOI(this.get("seriesId")) | |
); | |
}, | |
/* | |
* Checks if the currently-logged-in user is authorized to change | |
* permissions (or other action if set as parameter) on this doc | |
* @param {string} [action=changePermission] - 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. | |
*/ | |
checkAuthority: function (action = "changePermission") { | |
var authServiceUrl = MetacatUI.appModel.get("authServiceUrl"); | |
if (!authServiceUrl) return false; | |
var model = this; | |
var requestSettings = { | |
url: | |
authServiceUrl + | |
encodeURIComponent(this.get("id")) + | |
"?action=" + | |
action, | |
type: "GET", | |
success: function (data, textStatus, xhr) { | |
model.set("isAuthorized_" + action, true); | |
model.set("isAuthorized", true); | |
model.trigger("change:isAuthorized"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("isAuthorized_" + action, false); | |
model.set("isAuthorized", false); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/* | |
* This method will download this object while sending the user's auth token in the request. | |
*/ | |
downloadWithCredentials: function () { | |
//if(this.get("isPublic")) return; | |
//Get info about this object | |
var url = this.get("url"), | |
model = this; | |
//Create an XHR | |
var xhr = new XMLHttpRequest(); | |
//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 | |
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"); | |
if (!filename) { | |
filename = | |
model.get("fileName") || | |
model.get("title") || | |
model.get("id") || | |
"download"; | |
} else | |
filename = filename | |
.substring(filename.indexOf("filename=") + 9) | |
.replace(/"/g, ""); | |
//Replace any whitespaces | |
filename = filename.trim().replace(/ /g, "_"); | |
//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 | |
else { | |
var a = document.createElement("a"); | |
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob | |
// Set the file name. | |
a.download = filename; | |
a.style.display = "none"; | |
document.body.appendChild(a); | |
a.click(); | |
a.remove(); | |
} | |
model.trigger("downloadComplete"); | |
// Track this event | |
MetacatUI.analytics?.trackEvent( | |
"download", | |
"Download DataONEObject", | |
model.get("id"), | |
); | |
}; | |
xhr.onerror = function (e) { | |
model.trigger("downloadError"); | |
// Track the error | |
MetacatUI.analytics?.trackException( | |
`Download DataONEObject error: ${e || ""}`, | |
model.get("id"), | |
true, | |
); | |
}; | |
xhr.onprogress = function (e) { | |
if (e.lengthComputable) { | |
var percent = (e.loaded / e.total) * 100; | |
model.set("downloadPercent", percent); | |
} | |
}; | |
xhr.responseType = "blob"; | |
if (MetacatUI.appUserModel.get("loggedIn")) | |
xhr.setRequestHeader( | |
"Authorization", | |
"Bearer " + MetacatUI.appUserModel.get("token"), | |
); | |
xhr.send(); | |
}, | |
getInfo: function (fields) { | |
var model = this; | |
if (!fields) | |
var fields = | |
"abstract,id,seriesId,fileName,resourceMap,formatType,formatId,obsoletedBy,isDocumentedBy,documents,title,origin,keywords,attributeName,pubDate,eastBoundCoord,westBoundCoord,northBoundCoord,southBoundCoord,beginDate,endDate,dateUploaded,archived,datasource,replicaMN,isAuthorized,isPublic,size,read_count_i,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,serviceType,project,dateModified"; | |
var escapeSpecialChar = MetacatUI.appSearchModel.escapeSpecialChar; | |
var query = "q="; | |
//If there is no seriesId set, then search for pid or sid | |
if (!this.get("seriesId")) | |
query += | |
'(id:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("id"))) + | |
'" OR seriesId:"' + | |
escapeSpecialChar(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:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("seriesId"))) + | |
'" AND id:"' + | |
escapeSpecialChar(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:"' + | |
escapeSpecialChar(encodeURIComponent(this.get("id"))) + | |
'" -obsoletedBy:*'; | |
query += | |
"&fl=" + | |
fields + //Specify the fields to return | |
"&wt=json&rows=1000" + //Get the results in JSON format and get 1000 rows | |
"&archived=archived:*"; //Get archived or unarchived content | |
var requestSettings = { | |
url: MetacatUI.appModel.get("queryServiceUrl") + query, | |
type: "GET", | |
success: function (data, response, xhr) { | |
//If the Solr response was not as expected, trigger and error and exit | |
if (!data || typeof data.response == "undefined") { | |
model.set("indexed", false); | |
model.trigger("getInfoError"); | |
return; | |
} | |
var docs = data.response.docs; | |
if (docs.length == 1) { | |
docs[0].resourceMap = model.parseResourceMapField(docs[0]); | |
model.set(docs[0]); | |
model.trigger("sync"); | |
} | |
//If we searched by seriesId, then let's find the most recent version in the series | |
else if (docs.length > 1) { | |
//Filter out docs that are obsoleted | |
var mostRecent = _.reject(docs, function (doc) { | |
return typeof doc.obsoletedBy !== "undefined"; | |
}); | |
//If there is only one doc that is not obsoleted (the most recent), then | |
// set this doc's values on this model | |
if (mostRecent.length == 1) { | |
mostRecent[0].resourceMap = model.parseResourceMapField( | |
mostRecent[0], | |
); | |
model.set(mostRecent[0]); | |
model.trigger("sync"); | |
} else { | |
//If there are multiple docs without an obsoletedBy statement, then | |
// retreive the head of the series via the system metadata | |
var sysMetaRequestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(docs[0].seriesId), | |
type: "GET", | |
success: function (sysMetaData) { | |
//Get the identifier node from the system metadata | |
var seriesHeadID = $(sysMetaData).find("identifier").text(); | |
//Get the doc from the Solr results with that identifier | |
var seriesHead = _.findWhere(docs, { id: seriesHeadID }); | |
//If there is a doc in the Solr results list that matches the series head id | |
if (seriesHead) { | |
seriesHead.resourceMap = | |
model.parseResourceMapField(seriesHead); | |
//Set those values on this model | |
model.set(seriesHead); | |
} | |
//Otherwise, just fall back on the first doc in the list | |
else if (mostRecent.length) { | |
mostRecent[0].resourceMap = model.parseResourceMapField( | |
mostRecent[0], | |
); | |
model.set(mostRecent[0]); | |
} else { | |
docs[0].resourceMap = model.parseResourceMapField( | |
docs[0], | |
); | |
model.set(docs[0]); | |
} | |
model.trigger("sync"); | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
// Fall back on the first doc in the list | |
if (mostRecent.length) { | |
model.set(mostRecent[0]); | |
} else { | |
model.set(docs[0]); | |
} | |
model.trigger("sync"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
sysMetaRequestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
} | |
} else { | |
model.set("indexed", false); | |
//Try getting the system metadata as a backup | |
model.getSysMeta(); | |
} | |
}, | |
error: function (xhr, textStatus, errorThrown) { | |
model.set("indexed", false); | |
model.trigger("getInfoError"); | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
getCitationInfo: function () { | |
this.getInfo( | |
"id,seriesId,origin,pubDate,dateUploaded,title,datasource,project", | |
); | |
}, | |
/* | |
* Get the system metadata for this object | |
*/ | |
getSysMeta: function () { | |
var url = | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(this.get("id")), | |
model = this; | |
var requestSettings = { | |
url: url, | |
type: "GET", | |
dataType: "text", | |
success: function (data, response, xhr) { | |
if (data && data.length) { | |
model.set("systemMetadata", data); | |
} | |
//Check if this is archvied | |
var archived = $(data).find("archived").text() == "true"; | |
model.set("archived", archived); | |
//Get the file size | |
model.set("size", $(data).find("size").text() || ""); | |
//Get the entity name | |
model.set("filename", $(data).find("filename").text() || ""); | |
//Check if this is a metadata doc | |
var formatId = $(data).find("formatid").text() || "", | |
formatType; | |
model.set("formatId", formatId); | |
if ( | |
formatId.indexOf("ecoinformatics.org") > -1 || | |
formatId.indexOf("FGDC") > -1 || | |
formatId.indexOf("INCITS") > -1 || | |
formatId.indexOf("namespaces/netcdf") > -1 || | |
formatId.indexOf("waterML") > -1 || | |
formatId.indexOf("darwin") > -1 || | |
formatId.indexOf("dryad") > -1 || | |
formatId.indexOf("http://www.loc.gov/METS") > -1 || | |
formatId.indexOf("ddi:codebook:2_5") > -1 || | |
formatId.indexOf("http://www.icpsr.umich.edu/DDI") > -1 || | |
formatId.indexOf( | |
"http://purl.org/ornl/schema/mercury/terms/v1.0", | |
) > -1 || | |
formatId.indexOf("datacite") > -1 || | |
formatId.indexOf("isotc211") > -1 || | |
formatId.indexOf("metadata") > -1 | |
) | |
model.set("formatType", "METADATA"); | |
//Trigger the sync event so the app knows we found the model info | |
model.trigger("sync"); | |
}, | |
error: function (response) { | |
//When the user is unauthorized to access this object, trigger a 401 error | |
if (response.status == 401) { | |
model.set("notFound", true); | |
model.trigger("401"); | |
} | |
//When the object doesn't exist, trigger a 404 error | |
else if (response.status == 404) { | |
model.set("notFound", true); | |
model.trigger("404"); | |
} | |
//Other error codes trigger a generic error | |
else { | |
model.trigger("error"); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
//Transgresses the obsolence chain until it finds the newest version that this user is authorized to read | |
findLatestVersion: function (newestVersion, possiblyNewer) { | |
// Make sure we have the /meta service configured | |
if (!MetacatUI.appModel.get("metaServiceUrl")) return; | |
//If no pid was supplied, use this model's id | |
if (!newestVersion) { | |
var newestVersion = this.get("id"); | |
var possiblyNewer = this.get("obsoletedBy"); | |
} | |
//If this isn't obsoleted by anything, then there is no newer version | |
if (!possiblyNewer) { | |
this.set("newestVersion", newestVersion); | |
return; | |
} | |
var model = this; | |
//Get the system metadata for the possibly newer version | |
var requestSettings = { | |
url: | |
MetacatUI.appModel.get("metaServiceUrl") + | |
encodeURIComponent(possiblyNewer), | |
type: "GET", | |
success: function (data) { | |
// the response may have an obsoletedBy element | |
var obsoletedBy = $(data).find("obsoletedBy").text(); | |
//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 | |
else model.set("newestVersion", possiblyNewer); | |
}, | |
error: function (xhr) { | |
//If this newer version isn't found or accessible, then save the last | |
// accessible id as the newest version | |
if ( | |
xhr.status == 401 || | |
xhr.status == 404 || | |
xhr.status == "401" || | |
xhr.status == "404" | |
) { | |
model.set("newestVersion", newestVersion); | |
} | |
}, | |
}; | |
$.ajax( | |
_.extend( | |
requestSettings, | |
MetacatUI.appUserModel.createAjaxSettings(), | |
), | |
); | |
}, | |
/**** Provenance-related functions ****/ | |
/* | |
* Returns true if this provenance field points to a source of this data or metadata object | |
*/ | |
isSourceField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_generatedByExecution" || | |
field == "prov_generatedByProgram" || | |
field == "prov_used" || | |
field == "prov_wasDerivedFrom" || | |
field == "prov_wasInformedBy" | |
) | |
return true; | |
else return false; | |
}, | |
/* | |
* Returns true if this provenance field points to a derivation of this data or metadata object | |
*/ | |
isDerivationField: function (field) { | |
if (typeof field == "undefined" || !field) return false; | |
if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field)) | |
return false; | |
if ( | |
field == "prov_usedByExecution" || | |
field == "prov_usedByProgram" || | |
field == "prov_hasDerivations" || | |
field == "prov_generated" | |
) | |
return true; | |
else return false; | |
}, | |
/* | |
* Returns true if this SolrResult has a provenance trace (i.e. has either sources or derivations) | |
*/ | |
hasProvTrace: function () { | |
if (this.get("formatType") == "METADATA") { | |
if (this.get("prov_hasSources") || this.get("prov_hasDerivations")) | |
return true; | |
} | |
var fieldNames = MetacatUI.appSearchModel.getProvFields(), | |
currentField = ""; | |
for (var i = 0; i < fieldNames.length; i++) { | |
currentField = fieldNames[i]; | |
if (this.has(currentField)) return true; | |
} | |
return false; | |
}, | |
/* | |
* Returns an array of all the IDs of objects that are sources of this object | |
*/ | |
getSources: function () { | |
var sources = new Array(), | |
model = this, | |
//Get the prov fields but leave out references to executions which are not used in the UI yet | |
fields = _.reject( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (f) { | |
return f.indexOf("xecution") > -1; | |
}, | |
); //Leave out the first e in execution so we don't have to worry about case sensitivity | |
_.each(fields, function (provField, i) { | |
if (model.isSourceField(provField) && model.has(provField)) | |
sources.push(model.get(provField)); | |
}); | |
return _.uniq(_.flatten(sources)); | |
}, | |
/* | |
* Returns an array of all the IDs of objects that are derivations of this object | |
*/ | |
getDerivations: function () { | |
var derivations = new Array(), | |
model = this, | |
//Get the prov fields but leave out references to executions which are not used in the UI yet | |
fields = _.reject( | |
MetacatUI.appSearchModel.getProvFields(), | |
function (f) { | |
return f.indexOf("xecution") > -1; | |
}, | |
); //Leave out the first e in execution so we don't have to worry about case sensitivity | |
_.each(fields, function (provField, i) { | |
if (model.isDerivationField(provField) && model.has(provField)) | |
derivations.push(model.get(provField)); | |
}); | |
return _.uniq(_.flatten(derivations)); | |
}, | |
getInputs: function () { | |
return this.get("prov_used"); | |
}, | |
getOutputs: function () { | |
return this.get("prov_generated"); | |
}, | |
/* | |
* Uses the app configuration to check if this model's metrics should be hidden in the display | |
* | |
* @return {boolean} | |
*/ | |
hideMetrics: function () { | |
//If the AppModel is configured with cases of where to hide metrics, | |
if ( | |
typeof MetacatUI.appModel.get("hideMetricsWhen") == "object" && | |
MetacatUI.appModel.get("hideMetricsWhen") | |
) { | |
//Check for at least one match | |
return _.some( | |
MetacatUI.appModel.get("hideMetricsWhen"), | |
function (value, modelProperty) { | |
//Get the value of this property from this model | |
var modelValue = this.get(modelProperty); | |
//Check for the presence of this model's value in the AppModel value | |
if (Array.isArray(value) && typeof modelValue == "string") { | |
return _.contains(value, modelValue); | |
} | |
//Check for the presence of the AppModel's value in this model's value | |
else if (typeof value == "string" && Array.isArray(modelValue)) { | |
return _.contains(modelValue, value); | |
} | |
//Check for overlap of two arrays | |
else if (Array.isArray(value) && Array.isArray(modelValue)) { | |
return _.intersection(value, modelValue).length > 0; | |
} | |
//If the AppModel value is a function, execute it | |
else if (typeof value == "function") { | |
return value(modelValue); | |
} | |
//Otherwise, just check for equality | |
else { | |
return value === modelValue; | |
} | |
}, | |
this, | |
); | |
} else { | |
return false; | |
} | |
}, | |
/** | |
* Creates a URL for viewing more information about this metadata | |
* @return {string} | |
*/ | |
createViewURL: function () { | |
return this.getType() == "portal" || this.getType() == "collection" | |
? MetacatUI.root + | |
"/" + | |
MetacatUI.appModel.get("portalTermPlural") + | |
"/" + | |
encodeURIComponent( | |
this.get("label") || this.get("seriesId") || this.get("id"), | |
) | |
: MetacatUI.root + | |
"/view/" + | |
encodeURIComponent(this.get("seriesId") || this.get("id")); | |
}, | |
parseResourceMapField: function (json) { | |
if (typeof json.resourceMap == "string") { | |
return json.resourceMap.trim(); | |
} else if (Array.isArray(json.resourceMap)) { | |
let newResourceMapIds = []; | |
_.each(json.resourceMap, function (rMapId) { | |
if (typeof rMapId == "string") { | |
newResourceMapIds.push(rMapId.trim()); | |
} | |
}); | |
return newResourceMapIds; | |
} | |
//If nothing works so far, return an empty array | |
return []; | |
}, | |
}, | |
); |
Fix ES Linting errors for DataONEObject & Package model Ref: #2483
Add ES Lint fixes for the DataPackage collection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
prettier
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1515 in 4072928
success (response) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1552 in 4072928
error (data) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1600 in 4072928
getMember (context, args) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1906 in 4072928
triggerComplete (model) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1918 in 4072928
const notSynced = this.reject((m) => m.get("synced") || m.get("id") == model.get("id")); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1935 in 4072928
recordProvEdit (operation, subject, predicate, object) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1942 in 4072928
const editFound = _.find(this.provEdits, (edit) => ( |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 1946 to 1947 in 4072928
edit[3] == object | |
)); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1985 in 4072928
provEditsPending () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 1993 to 1994 in 4072928
saveProv () { | |
const {rdf} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 1997 in 4072928
const {provEdits} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2002 to 2006 in 4072928
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); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2016 in 4072928
let operation; let subject; let predicate; let object; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2034 to 2037 in 4072928
let executionId; let executionURI; let executionNode; | |
let programId; let programURI; let programNode; | |
let dataId; let dataURI; let dataNode; | |
let derivedDataURI; let derivedDataNode; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2043 in 4072928
const objectNode = rdf.sym(this.getURIFromRDF(object)); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2195 in 4072928
addToGraph (subject, predicate, object) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2211 in 4072928
removeIfLastProvRef (subjectNode, predicateNode, objectNode) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2307 in 4072928
removeOrphanedBlankNodes () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2357 to 2358 in 4072928
getExecutionId (programId) { | |
const {rdf} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2363 to 2365 in 4072928
const DCTERMS = rdf.Namespace(this.namespaces.DCTERMS); | |
const PROV = rdf.Namespace(this.namespaces.PROV); | |
const PROVONE = rdf.Namespace(this.namespaces.PROVONE); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2398 to 2399 in 4072928
getExecutionNode (executionId) { | |
const {rdf} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2424 to 2431 in 4072928
} | |
return testNode; | |
} | |
// The executionNode was found in the RDF graph as a urn | |
var executionNode = stmts[0].subject; | |
return executionNode; | |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2434 to 2435 in 4072928
addProgramToGraph (programId) { | |
const {rdf} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2438 to 2441 in 4072928
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); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2452 in 4072928
executionId = `urn:uuid:${ uuid.v4()}`; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2508 in 4072928
removeProgramFromGraph (programId) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2510 in 4072928
const {rdf} = this; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2514 to 2517 in 4072928
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); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2607 in 4072928
serialize () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2610 to 2614 in 4072928
let oldPidVariations; | |
let modifiedDate; | |
let subjectClone; | |
let predicateClone; | |
let objectClone; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2620 to 2625 in 4072928
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); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2629 to 2630 in 4072928
const oldPid = this.packageModel.get("oldPid"); | |
let cnResolveUrl = this.getCnURI(); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2755 in 4072928
const rMapIdNode = this.rdf.lit(pid); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2791 in 4072928
aggregationNode = this.rdf.sym(`${oldPid }#aggregation`); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2793 in 4072928
`${oldPid }#aggregation`, |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2816 to 2817 in 4072928
subjectClone.value = | |
`${this.getURIFromRDF(pid) }#aggregation`; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2857 to 2858 in 4072928
objectClone.value = | |
`${this.getURIFromRDF(pid) }#aggregation`; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2922 in 4072928
this.rdf.sym(`${this.getURIFromRDF(pid) }#aggregation`), |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2925 in 4072928
this.rdf.sym(`${this.getURIFromRDF(pid) }#aggregation`), |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2947 in 4072928
`${this.getURIFromRDF(this.packageModel.id) }#aggregation`, |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 2953 to 2955 in 4072928
`${MetacatUI.appUserModel.get("firstName") || "" | |
} ${ | |
MetacatUI.appUserModel.get("lastName") || ""}`, |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 2981 in 4072928
const idLiteral = this.rdf.lit(this.packageModel.id, "", XSD("string")); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3016 in 4072928
cloneNode (nodeToClone) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3029 to 3031 in 4072928
} | |
return this.rdf.literal(nodeToClone.value); | |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3043 in 4072928
`ERROR: unknown node type to clone: ${ nodeToClone.termType}`, |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3049 in 4072928
addToAggregation (id) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3052 to 3054 in 4072928
const DCTERMS = this.rdf.Namespace(this.namespaces.DCTERMS); | |
const XSD = this.rdf.Namespace(this.namespaces.XSD); | |
const CITO = this.rdf.Namespace(this.namespaces.CITO); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3058 to 3066 in 4072928
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 = []; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3100 to 3101 in 4072928
const isDocBy = model.get("isDocumentedBy"); | |
const documents = model.get("documents"); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3112 to 3116 in 4072928
_.map(this.models, (m) => { | |
if (m.get("formatType") == "METADATA") return m; | |
}), | |
); | |
const metadataInPackageIDs = _.each(metadataInPackage, (m) => m.get("id")); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3244 in 4072928
removeFromAggregation (id) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3280 in 4072928
getURIFromRDF (id) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3286 to 3293 in 4072928
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, | |
); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3299 to 3301 in 4072928
} | |
return this.getCnURI() + encodeURIComponent(id); | |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3309 in 4072928
getCnURI () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3313 in 4072928
} if (this.packageModel.get("oldPid")) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3316 to 3321 in 4072928
const idStatements = this.dataPackageGraph.statementsMatching( | |
undefined, | |
undefined, | |
idNode, | |
); | |
const idStatement = idStatements.length ? idStatements[0] : null; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3354 in 4072928
needsUpdate () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3368 in 4072928
let i = 0; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3374 to 3375 in 4072928
const id = this.models[i].get("id"); | |
let origIsDocBy = this.originalIsDocBy[id]; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3415 to 3416 in 4072928
getQueue () { | |
return this.filter((m) => m.get("uploadStatus") == "q" || m.get("uploadStatus") == "p"); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3422 in 4072928
addNewModel (model) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3433 in 4072928
handleAdd (dataONEObject) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3440 to 3441 in 4072928
} else if (!_.contains(metadataModel.get("documents"), dataONEObject.id)) | |
metadataModel.get("documents").push(dataONEObject.id); |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3491 in 4072928
fetchFromIndex () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3520 in 4072928
mergeModels (otherModels, fieldsToMerge) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Lines 3553 to 3556 in 4072928
// 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 = []; |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3580 in 4072928
updateRelationships () { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3595 in 4072928
saveReference (model) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3615 in 4072928
broadcastAccessPolicy (accessPolicy) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3652 in 4072928
setLoadingFiles (dataONEObject) { |
[prettier] reported by reviewdog 🐶
metacatui/src/js/collections/DataPackage.js
Line 3691 in 4072928
getAtLocation () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good to have the bytesToSize
consolidated into one location! Everything seems to still work & I see that your ES-lint changes are a WIP so we can ignore the remaining errors for this PR.
Approve conditional on a run of npm run format
(DataPackage
has some prettier errors). Feel free to merge after that! 🎉
@rushirajnenuji, I pushed a little fix for the formatting issue with |
…etacatui into enhancement-2483-2484-FH-2
Thank you for adding the reviewing and testing this PR, and for adding the formatting fix @robyngit , I've got this branch up to speed with |
Move bytestoSize to Utilities Ref #2484
[WIP] FH ES Lint issues #2483