Skip to content

Commit

Permalink
Add EMLAnnotation methods for canonical datasets
Browse files Browse the repository at this point in the history
- Add methods to add, remove, update, and find sameAs and derivedFrom annotations representing canonical datasets

Issue #2542
  • Loading branch information
robyngit committed Oct 17, 2024
1 parent 6189421 commit bea2d7b
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 3 deletions.
131 changes: 131 additions & 0 deletions src/js/collections/metadata/eml/EMLAnnotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ define(["underscore", "backbone", "models/metadata/eml211/EMLAnnotation"], (
Backbone,
EMLAnnotation,
) => {
const SCHEMA_ORG_SAME_AS = "http://www.w3.org/2002/07/owl#sameAs";
const PROV_WAS_DERIVED_FROM = "http://www.w3.org/ns/prov#wasDerivedFrom";
/**
* @class EMLAnnotations
* @classdesc A collection of EMLAnnotations.
Expand Down Expand Up @@ -56,6 +58,135 @@ define(["underscore", "backbone", "models/metadata/eml211/EMLAnnotation"], (
}
this.add(annotation);
},

/**
* Find all annotations with the given propertyURI.
* @param {string} propertyURI The propertyURI to search for.
* @returns {EMLAnnotation[]} An array of EMLAnnotations with the given
* propertyURI.
* @since 0.0.0
*/
findByProperty(propertyURI) {
return this.where({ propertyURI });
},

/**
* Adds canonical dataset annotations to this collection. A canonical
* dataset is the one that is considered the authoritative version; the
* current EML doc being essentially a duplicate version.
* @param {string} sourceId The DOI or URL of the canonical dataset.
* @returns {void}
* @since 0.0.0
*/
addCanonicalDatasetAnnotation(sourceId) {
if (!sourceId) return null;
// TODO: Check that sourceId is a valid DOI or URL

// TODO: Check that there is not already a canonical dataset annotation
// before adding a new one, since there should only be one.
return this.add([
{
propertyLabel: "derivedFrom",
propertyURI: PROV_WAS_DERIVED_FROM,
valueLabel: sourceId,
valueURI: sourceId,
},
{
propertyLabel: "sameAs",
propertyURI: SCHEMA_ORG_SAME_AS,
valueLabel: sourceId,
valueURI: sourceId,
},
]);
},

/**
* Find the annotations that make up the canonical dataset annotation. A
* canonical dataset is identified by having both a "derivedFrom" and a
* "sameAs" annotation with the same DOI or URL for the valueURI.
* @returns {object} An object with the derivedFrom and sameAs
* annotations.
* @since 0.0.0
*/
findCanonicalDatasetAnnotation() {
// There must be at least one derivedFrom and one sameAs annotation
// for this to have a canonical dataset annotation
if (!this.length) return null;
const derivedFrom = this.findByProperty(PROV_WAS_DERIVED_FROM);
if (!derivedFrom?.length) return null;
const sameAs = this.findByProperty(SCHEMA_ORG_SAME_AS);
if (!sameAs?.length) return null;

// Find all pairs that have matching valueURIs
const pairs = [];
derivedFrom.forEach((derived) => {
sameAs.forEach((same) => {
if (derived.get("valueURI") === same.get("valueURI")) {
// TODO? Check that the URI is a valid DOI or URL
pairs.push({ derived, same, uri: derived.get("valueURI") });
}
});
});

// If there are multiple pairs, we cannot determine which is the
// canonical dataset.
if (pairs.length > 1 || !pairs.length) return null;

// There is only one pair, so return it
return pairs[0];
},

/**
* Updates the canonical dataset annotations to have the given ID. If
* there is no canonical dataset annotation, one is added. If the ID is a
* falsy value, the canonical dataset annotation is removed.
* @param {string} newSourceId The DOI or URL of the canonical dataset.
* @returns {object} An object with the derivedFrom and sameAs annotations
* if the canonical dataset annotations were updated.
* @since 0.0.0
*/
updateCanonicalDataset(newSourceId) {
if (!newSourceId) {
this.removeCanonicalDatasetAnnotation();
return null;
}
const canonical = this.findCanonicalDatasetAnnotation();
if (!canonical) {
return this.addCanonicalDatasetAnnotation(newSourceId);
}

const { derived, same, uri } = canonical;
if (uri === newSourceId) return null;

derived.set("valueURI", newSourceId);
derived.set("valueLabel", newSourceId);
same.set("valueURI", newSourceId);
same.set("valueLabel", newSourceId);

return [derived, same];
},

/**
* Removes the canonical dataset annotations from this collection.
* @returns {EMLAnnotation[]} The canonical dataset annotations that were
* removed.
* @since 0.0.0
*/
removeCanonicalDatasetAnnotation() {
const canonical = this.findCanonicalDatasetAnnotation();
if (!canonical) return null;
return this.remove([canonical.derived, canonical.same]);
},

/**
* Returns the URI of the canonical dataset.
* @returns {string} The URI of the canonical dataset.
* @since 0.0.0
*/
getCanonicalURI() {
const canonical = this.findCanonicalDatasetAnnotation();
return canonical?.uri;
},
},
);

Expand Down
5 changes: 3 additions & 2 deletions src/js/models/metadata/eml211/EMLAnnotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
},

initialize: function (attributes, opions) {
this.on("change", this.trickleUpChange);
this.stopListening(this, "change", this.trickleUpChange);
this.listenTo(this, "change", this.trickleUpChange);
},

parse: function (attributes, options) {
Expand Down Expand Up @@ -175,7 +176,7 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {

/* Let the top level package know of attribute changes from this object */
trickleUpChange: function () {
MetacatUI.rootDataPackage.packageModel.set("changed", true);
MetacatUI.rootDataPackage.packageModel?.set("changed", true);
},
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ define([
const should = chai.should();
const expect = chai.expect;

describe("Accordion Test Suite", () => {
const SCHEMA_ORG_SAME_AS = "http://www.w3.org/2002/07/owl#sameAs";
const PROV_WAS_DERIVED_FROM = "http://www.w3.org/ns/prov#wasDerivedFrom";

describe("EML Annotations Test Suite", () => {
const state = cleanState(() => {
const annotations = new EMLAnnotations({
propertyLabel: "Property Label",
Expand Down Expand Up @@ -45,5 +48,68 @@ define([
state.annotations.remove(state.annotations.at(0));
state.annotations.length.should.equal(0);
});

it("finds annotations by property", () => {
state.annotations
.findByProperty("http://example.com/property")
.length.should.equal(1);
});

it("adds canonical dataset annotations", () => {
const annotations =
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.length.should.equal(3);
annotations[0].get("propertyURI").should.equal(PROV_WAS_DERIVED_FROM);
annotations[0].get("valueURI").should.equal("http://example.com");
annotations[1].get("propertyURI").should.equal(SCHEMA_ORG_SAME_AS);
annotations[1].get("valueURI").should.equal("http://example.com");
});

it("finds canonical dataset annotations", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
const annotations = state.annotations.findCanonicalDatasetAnnotation();
annotations.should.have.property("derived");
annotations.should.have.property("same");
annotations.derived.get("valueURI").should.equal("http://example.com");
annotations.same.get("valueURI").should.equal("http://example.com");
annotations.derived
.get("propertyURI")
.should.equal(PROV_WAS_DERIVED_FROM);
annotations.same.get("propertyURI").should.equal(SCHEMA_ORG_SAME_AS);
});

it("updates canonical dataset annotations", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.updateCanonicalDataset("http://newexample.com");
state.annotations.getCanonicalURI().should.equal("http://newexample.com");
});

it("removes canonical dataset annotations", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.removeCanonicalDatasetAnnotation();
state.annotations.length.should.equal(1);
});

it("gets the URI of the canonical dataset", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.getCanonicalURI().should.equal("http://example.com");
});

it("adds annotations if they didn't exist when updating", () => {
state.annotations.updateCanonicalDataset("http://example.com");
state.annotations.length.should.equal(3);
});

it("removes canonical dataset annotations if the ID is falsy", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.updateCanonicalDataset("");
state.annotations.length.should.equal(1);
});

it("does not update canonical dataset annotations if the ID is the same", () => {
state.annotations.addCanonicalDatasetAnnotation("http://example.com");
state.annotations.updateCanonicalDataset("http://example.com");
state.annotations.length.should.equal(3);
});
});
});

0 comments on commit bea2d7b

Please sign in to comment.