Skip to content
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

fix: improves DataPackage fetch method #2608

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 82 additions & 55 deletions src/js/collections/DataPackage.js
Original file line number Diff line number Diff line change
Expand Up @@ -573,67 +573,94 @@ define([
},

/**
* Overload fetch calls for a DataPackage
* @param {object} [options] - Optional options for this fetch that get
* sent with the XHR request
* @property {boolean} fetchModels - If false, this fetch will not fetch
* each model in the collection. It will only get the resource map object.
* @property {boolean} fromIndex - If true, the collection will be fetched
* from Solr rather than fetching the system metadata of each model.
* Useful when you only need to retrieve limited information about each
* package member. Set query-specific parameters on the `solrResults`
* SolrResults set on this collection.
* @returns {jqXHR} The jQuery XMLHttpRequest for the request
* Overload fetch calls for a DataPackage
*
* This fetch function will fetch the resource map RDF XML for this package
*
* + Example 1: `this.fetch();`
* + Example 2: `this.fetch({fetchModels: false});`
* + Example 3: `this.fetch({fromIndex: true});`
* + Example 4:
* ```
* this.fetch()
* .then(function() {
* console.log("Fetch complete!");
* })
* .catch(function() {
* console.log("Fetch failed!");
* });
* ```
*
* @param {object} [options] - Optional options for this fetch that get sent with the XHR request
* @property {boolean} fetchModels - If false, this fetch will not fetch
* each model in the collection. It will only get the resource map object.
* @property {boolean} fromIndex - If true, the collection will be fetched from Solr rather than
* fetching the system metadata of each model. Useful when you only need to retrieve limited information about
* each package member. Set query-specific parameters on the `solrResults` SolrResults set on this collection.
* @return {Promise} A promise that resolves when the fetch is complete
*/
fetch(options = {}) {
// Fetch the system metadata for this resource map
this.packageModel.fetch();

const fetchOptions = { dataType: "text", ...options };

// If the fetchModels property is set to false,
if (fetchOptions.fetchModels === false) {
// Save the property to the Collection itself so it is accessible in
// other functions
this.fetchModels = false;
// Remove the property from the options Object since we don't want to
// send it with the XHR
delete fetchOptions.fetchModels;
this.once("reset", this.triggerComplete);
}
// If the fetchFromIndex property is set to true
else if (fetchOptions.fromIndex) {
this.fetchFromIndex();
return null;
}
fetch(options) {
return new Promise((resolve, reject) => {
// Fetch the system metadata for this resource map
this.packageModel.fetch();

if (typeof options === "object") {
// If the fetchModels property is set to false,
if (options.fetchModels === false) {
// Save the property to the Collection itself so it is accessible in other functions
this.fetchModels = false;
// Remove the property from the options Object since we don't want to send it with the XHR
delete options.fetchModels;
this.once("reset", () => {
this.triggerComplete();
resolve();
});
}
// If the fetchFromIndex property is set to true
else if (options.fromIndex) {
this.fetchFromIndex();
resolve();
return;
}
}

const thisPackage = this;
// Set some custom fetch options
const fetchOptions = _.extend({ dataType: "text" }, options);

// Function to retry fetching with user login details if the initial
// fetch fails eslint-disable-next-line func-names
const retryFetch = () => {
// Add the authorization options
const authFetchOptions = _.extend(
fetchOptions,
MetacatUI.appUserModel.createAjaxSettings(),
);
const thisPackage = this;

// Function to retry fetching with user login details if the initial fetch fails
const retryFetch = function () {
// Add the authorization options
const authFetchOptions = _.extend(
fetchOptions,
MetacatUI.appUserModel.createAjaxSettings(),
);

// Fetch the resource map RDF XML with user login details
return Backbone.Collection.prototype.fetch
.call(thisPackage, authFetchOptions)
.fail(() => {
// trigger failure()
console.log("Fetch failed");

thisPackage.trigger("fetchFailed", thisPackage);
reject();
});
};

// Fetch the resource map RDF XML with user login details
return Backbone.Collection.prototype.fetch
.call(thisPackage, authFetchOptions)
// Fetch the resource map RDF XML
Backbone.Collection.prototype.fetch
.call(this, fetchOptions)
.done(() => resolve())
.fail(() => {
// TODO: Handle the fetch failure!
thisPackage.trigger("fetchFailed", thisPackage);
console.log("Fetch failed. Retrying with user login details...");
// If the initial fetch fails, retry with user login details
retryFetch()
.done(() => resolve())
.fail(() => reject());
});
};

// Fetch the resource map RDF XML
return Backbone.Collection.prototype.fetch
.call(this, fetchOptions)
.fail(() =>
// If the initial fetch fails, retry with user login details
retryFetch(),
);
});
},

/**
Expand Down
140 changes: 140 additions & 0 deletions test/js/specs/unit/collections/DataPackage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,42 @@ define([

describe("DataPackage Test Suite", function () {
let dataPackage;
let originalFetch,
originalTriggerComplete,
originalFetchFromIndex,
originalCreateAjaxSettings;
let fetchCallCount,
triggerCompleteCallCount,
fetchFromIndexCallCount,
createAjaxSettingsCallCount;

beforeEach(function () {
dataPackage = new DataPackage();
originalFetch = dataPackage.packageModel.fetch;
originalTriggerComplete = dataPackage.triggerComplete;
originalFetchFromIndex = dataPackage.fetchFromIndex;
originalCreateAjaxSettings = MetacatUI.appUserModel.createAjaxSettings;

fetchCallCount = 0;
triggerCompleteCallCount = 0;
fetchFromIndexCallCount = 0;
createAjaxSettingsCallCount = 0;

dataPackage.triggerComplete = function () {
triggerCompleteCallCount++;
};
dataPackage.fetchFromIndex = function () {
fetchFromIndexCallCount++;
};
MetacatUI.appUserModel.createAjaxSettings = function () {
createAjaxSettingsCallCount++;
};
});

afterEach(function () {
dataPackage.triggerComplete = originalTriggerComplete;
dataPackage.fetchFromIndex = originalFetchFromIndex;
MetacatUI.appUserModel.createAjaxSettings = originalCreateAjaxSettings;
dataPackage = undefined;
});

Expand Down Expand Up @@ -133,5 +163,115 @@ define([
}, 1000);
});
});

describe("fetch", function () {
let originalFetch;

// Save the original fetch method and mock it to return a resolved promise before each test
beforeEach(function () {
originalFetch = Backbone.Collection.prototype.fetch;
Backbone.Collection.prototype.fetch = function (options) {
// Mocked fetch method that returns a resolved promise
var deferred = $.Deferred();
deferred.resolve();
return deferred.promise();
};
});

// Restore the original fetch method after each test
afterEach(function () {
Backbone.Collection.prototype.fetch = originalFetch;
});

// Test that packageModel.fetch is called with no options
it("should call packageModel.fetch with no options", function (done) {
dataPackage.packageModel.fetch = function () {
// Mocked fetch method that increments fetchCallCount and returns a resolved promise
fetchCallCount++;
return Promise.resolve();
};

dataPackage
.fetch()
.then(function () {
expect(fetchCallCount).to.equal(1);
done(); // Call done() to finish the test
})
.catch(function () {
done(new Error("Expected fetch to succeed"));
});
});

// Test that packageModel.fetch is called with fetchModels: false
it("should call packageModel.fetch with fetchModels: false", function (done) {
dataPackage.packageModel.fetch = function () {
// Mocked fetch method that increments fetchCallCount and returns a resolved promise
fetchCallCount++;
return Promise.resolve();
};

dataPackage
.fetch({ fetchModels: false })
.then(function () {
expect(fetchCallCount).to.equal(1);
expect(fetchFromIndexCallCount).to.equal(0);
done(); // Call done() to finish the test
})
.catch(function () {
done(new Error("Expected fetch to succeed"));
});
});

// Test that fetchFromIndex is called with fromIndex: true
it("should call fetchFromIndex with fromIndex: true", function (done) {
dataPackage
.fetch({ fromIndex: true })
.then(function () {
expect(fetchFromIndexCallCount).to.equal(1);
done(); // Call done() to finish the test
})
.catch(function () {
done(new Error("Expected fetch to succeed"));
});
});

// Test that createAjaxSettings is called
it("should call createAjaxSettings", function (done) {
dataPackage
.fetch()
.then(function () {
expect(createAjaxSettingsCallCount).to.equal(1);
done(); // Call done() to finish the test
})
.catch(function () {
done(new Error("Expected fetch to succeed"));
});
});

// Test that the fetch method handles done and fail callbacks correctly
it("should handle done and fail callbacks", function (done) {
dataPackage.packageModel.fetch = function () {
// Mocked fetch method that increments fetchCallCount and returns a rejected promise
fetchCallCount++;
return Promise.reject();
};
Backbone.Collection.prototype.fetch = function (options) {
// Mocked fetch method that returns a rejected promise
var deferred = $.Deferred();
deferred.reject();
return deferred.promise();
};
dataPackage
.fetch()
.then(function () {
done(new Error("Expected fetch to fail"));
})
.catch(function () {
expect(fetchCallCount).to.equal(1);
expect(fetchFromIndexCallCount).to.equal(0);
done();
});
});
});
});
});
Loading