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

Improved Collection and Workflow State with Applications #5013

Merged
merged 22 commits into from Nov 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0ff639a
More debugging in workflows API tests.
jmchilton Oct 14, 2017
4e9ee22
More verbose workflow extraction test cases.
jmchilton Oct 16, 2017
0e0894b
Refactoring history contents API.
jmchilton Oct 18, 2017
8c1658f
Mixin for uses create and update time.
jmchilton Oct 17, 2017
c076b3b
Refactor workflow testing toward reuse via "populators".
jmchilton Oct 17, 2017
55aa7bc
Test cases for mapping for outputs with filter statements.
jmchilton Oct 17, 2017
d5f98f4
Formalize workflow invocation and invocation step outputs.
jmchilton Jul 25, 2017
64835c1
Three new worklfow test cases to verify mapping over workflows is pos…
jmchilton Aug 8, 2017
0185e04
Test recover_mapping in conjunction with subworkflow executions.
jmchilton Sep 11, 2017
9e471d3
Allow mapping over empty collections.
jmchilton Nov 15, 2017
53cba4a
Refactor collection manager into smaller, more focused methods.
jmchilton Oct 5, 2017
78babab
Pre-build collections during mapping to be more recoverable.
jmchilton Oct 4, 2017
40fd72a
Allow rescheduling of single workflow steps.
jmchilton Oct 17, 2017
d69962e
Implement model abstraction for ``ImplicitCollectionJobs``.
jmchilton Nov 15, 2017
e82012b
Undo splitting WorkflowInvocationStep...
jmchilton Oct 25, 2017
0dceb5a
Add element count to DatasetCollection objects.
jmchilton Nov 8, 2017
ca09e80
Dataset collection state UX test case.
jmchilton Nov 8, 2017
5a9f0dd
Add job state tracking to the GUI.
jmchilton Nov 4, 2017
e7e659c
Better dataset loading description.
jmchilton Nov 28, 2017
075d8c1
Rename huge database migration to reflect new PR topic.
jmchilton Nov 15, 2017
8fa5496
Restructure collection state GUI tests to separate flakey tests of ru…
jmchilton Nov 28, 2017
c47d899
Repack client.
jmchilton Nov 30, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
102 changes: 94 additions & 8 deletions client/galaxy/scripts/mvc/history/hdca-li.js
Expand Up @@ -16,10 +16,14 @@ var HDCAListItemView = _super.extend(
/** event listeners */
_setUpListeners: function() {
_super.prototype._setUpListeners.call(this);
var renderListen = (model, options) => {
this.render();
};
if (this.model.jobStatesSummary) {
this.listenTo(this.model.jobStatesSummary, "change", renderListen);
}
this.listenTo(this.model, {
"change:tags change:populated change:visible": function(model, options) {
this.render();
}
"change:tags change:visible change:state": renderListen
});
},

Expand All @@ -43,13 +47,95 @@ var HDCAListItemView = _super.extend(
_swapNewRender: function($newRender) {
_super.prototype._swapNewRender.call(this, $newRender);
//TODO: model currently has no state
var state = !this.model.get("populated") ? STATES.RUNNING : STATES.OK;
//if( this.model.has( 'state' ) ){
var state;
var jobStatesSummary = this.model.jobStatesSummary;
if (jobStatesSummary) {
if (jobStatesSummary.new()) {
state = "loading";
} else if (jobStatesSummary.errored()) {
state = "error";
} else if (jobStatesSummary.terminal()) {
state = "ok";
} else if (jobStatesSummary.running()) {
state = "running";
} else {
state = "queued";
}
} else if (this.model.get("job_source_id")) {
// Initial rendering - polling will fill in more details in a bit.
state = "loading";
} else {
state = this.model.get("populated_state") ? STATES.OK : STATES.RUNNING;
}
this.$el.addClass(`state-${state}`);
//}
var stateDescription = this.stateDescription();
this.$(".state-description").html(stateDescription);
return this.$el;
},

stateDescription: function() {
var collection = this.model;
var elementCount = collection.get("element_count");
var jobStateSource = collection.get("job_source_type");
var collectionType = this.model.get("collection_type");
var collectionTypeDescription;
if (collectionType == "list") {
collectionTypeDescription = "list";
} else if (collectionType == "paired") {
collectionTypeDescription = "dataset pair";
} else if (collectionType == "list:paired") {
collectionTypeDescription = "list of pairs";
} else {
collectionTypeDescription = "nested list";
}
var itemsDescription = "";
if (elementCount == 1) {
itemsDescription = ` with 1 item`;
} else if (elementCount) {
itemsDescription = ` with ${elementCount} items`;
}
var jobStatesSummary = collection.jobStatesSummary;
var simpleDescription = `${collectionTypeDescription}${itemsDescription}`;
if (!jobStateSource || jobStateSource == "Job") {
return `a ${simpleDescription}`;
} else if (!jobStatesSummary || !jobStatesSummary.hasDetails()) {
return `
<div class="progress state-progress">
<span class="note">Loading job data for ${collectionTypeDescription}.<span class="blinking">..</span></span>
<div class="progress-bar info" style="width:100%">
</div>`;
} else {
var isNew = jobStatesSummary.new();
var jobCount = isNew ? null : jobStatesSummary.jobCount();
if (isNew) {
return `
<div class="progress state-progress">
<span class="note">Creating jobs.<span class="blinking">..</span></span>
<div class="progress-bar info" style="width:100%">
</div>`;
} else if (jobStatesSummary.errored()) {
var errorCount = jobStatesSummary.numInError();
return `a ${collectionTypeDescription} with ${errorCount} / ${jobCount} jobs in error`;
} else if (jobStatesSummary.terminal()) {
return `a ${simpleDescription}`;
} else {
var running = jobStatesSummary.states()["running"] || 0;
var ok = jobStatesSummary.states()["ok"] || 0;
var okPercent = ok / (jobCount * 1.0);
var runningPercent = running / (jobCount * 1.0);
var otherPercent = 1.0 - okPercent - runningPercent;
var jobsStr = jobCount && jobCount > 1 ? `${jobCount} jobs` : `a job`;
return `
<div class="progress state-progress">
<span class="note">${jobsStr} generating a ${collectionTypeDescription}</span>
<div class="progress-bar ok" style="width:${okPercent * 100.0}%"></div>
<div class="progress-bar running" style="width:${runningPercent * 100.0}%"></div>
<div class="progress-bar new" style="width:${otherPercent * 100.0}%">
</div>`;
}
}
},

// ......................................................................... misc
/** String representation */
toString: function() {
Expand All @@ -69,15 +155,15 @@ HDCAListItemView.prototype.templates = (() => {
}
});

// could steal this from hda-base (or use mixed content)
var titleBarTemplate = collection => `
<div class="title-bar clear" tabindex="0">
<span class="state-icon"></span>
<div class="title">
<span class="hid">${collection.hid}</span>
<span class="name">${_.escape(collection.name)}</span>
</div>
<div class="subtitle"></div>
<div class="state-description">
</div>
${HISTORY_ITEM_LI.nametagTemplate(collection)}
</div>
`;
Expand Down
60 changes: 45 additions & 15 deletions client/galaxy/scripts/mvc/history/history-contents.js
Expand Up @@ -2,6 +2,7 @@ import CONTROLLED_FETCH_COLLECTION from "mvc/base/controlled-fetch-collection";
import HDA_MODEL from "mvc/history/hda-model";
import HDCA_MODEL from "mvc/history/hdca-model";
import HISTORY_PREFS from "mvc/history/history-preferences";
import JOB_STATES_MODEL from "mvc/history/job-states-model";
import BASE_MVC from "mvc/base-mvc";
import AJAX_QUEUE from "utils/ajax-queue";

Expand Down Expand Up @@ -37,6 +38,10 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({

/** Set up */
initialize: function(models, options) {
this.on({
"sync add": this.trackJobStates
});

options = options || {};
_super.prototype.initialize.call(this, models, options);

Expand All @@ -53,6 +58,29 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
this.model.prototype.idAttribute = "type_id";
},

trackJobStates: function() {
this.each(historyContent => {
if (historyContent.has("job_states_summary")) {
return;
}

if (historyContent.attributes.history_content_type === "dataset_collection") {
var jobSourceType = historyContent.attributes.job_source_type;
var jobSourceId = historyContent.attributes.job_source_id;
if (jobSourceType) {
this.jobStateSummariesCollection.add({
id: jobSourceId,
model: jobSourceType,
history_id: this.history_id,
collection_id: historyContent.attributes.id
});
var jobStatesSummary = this.jobStateSummariesCollection.get(jobSourceId);
historyContent.jobStatesSummary = jobStatesSummary;
}
}
});
},

// ........................................................................ composite collection
/** since history content is a mix, override model fn into a factory, creating based on history_content_type */
model: function(attrs, options) {
Expand Down Expand Up @@ -82,17 +110,30 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
};
},

stopPolling: function() {
if (this.jobStateSummariesCollection) {
this.jobStateSummariesCollection.active = false;
this.jobStateSummariesCollection.clearUpdateTimeout();
}
},

setHistoryId: function(newId) {
this.stopPolling();
this.historyId = newId;
this._setUpWebStorage();
if (newId) {
// If actually reflecting a history - setup storage and monitor jobs.

this._setUpWebStorage();

this.jobStateSummariesCollection = new JOB_STATES_MODEL.JobStatesSummaryCollection();
this.jobStateSummariesCollection.historyId = newId;
this.jobStateSummariesCollection.monitor();
}
},

/** Set up client side storage. Currently PersistanStorage keyed under 'history:<id>' */
_setUpWebStorage: function(initialSettings) {
// TODO: use initialSettings
if (!this.historyId) {
return;
}
this.storage = new HISTORY_PREFS.HistoryPrefs({
id: HISTORY_PREFS.HistoryPrefs.historyStorageKey(this.historyId)
});
Expand Down Expand Up @@ -303,17 +344,6 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
return this.fetch(options);
},

/** specialty fetch method for retrieving the element_counts of all hdcas in the history */
fetchCollectionCounts: function(options) {
options = options || {};
options.keys = ["type_id", "element_count"].join(",");
options.filters = _.extend(options.filters || {}, {
history_content_type: "dataset_collection"
});
options.remove = false;
return this.fetch(options);
},

// ............. quasi-batch ops
// TODO: to batch
/** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */
Expand Down
7 changes: 7 additions & 0 deletions client/galaxy/scripts/mvc/history/history-model.js
Expand Up @@ -237,6 +237,13 @@ var History = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
}
},

stopPolling: function() {
this.clearUpdateTimeout();
if (this.contents) {
this.contents.stopPolling();
}
},

// ........................................................................ ajax
/** override to use actual Dates objects for create/update times */
parse: function(response, options) {
Expand Down
30 changes: 4 additions & 26 deletions client/galaxy/scripts/mvc/history/history-view.js
Expand Up @@ -47,9 +47,6 @@ var HistoryView = _super.extend(
/** string used for search placeholder */
searchPlaceholder: _l("search datasets"),

/** @type {Number} ms to wait after history load to fetch/decorate hdcas with element_count */
FETCH_COLLECTION_COUNTS_DELAY: 2000,

// ......................................................................... SET UP
/** Set up the view, bind listeners.
* @param {Object} attributes optional settings for the panel
Expand All @@ -60,9 +57,6 @@ var HistoryView = _super.extend(
// control contents/behavior based on where (and in what context) the panel is being used
/** where should pages from links be displayed? (default to new tab/window) */
this.linkTarget = attributes.linkTarget || "_blank";

/** timeout id for detailed fetch of collection counts, etc... */
this.detailedFetchTimeoutId = null;
},

/** create and return a collection for when none is initially passed */
Expand All @@ -77,20 +71,11 @@ var HistoryView = _super.extend(
freeModel: function() {
_super.prototype.freeModel.call(this);
if (this.model) {
this.model.clearUpdateTimeout();
this.model.stopPolling();
}
this._clearDetailedFetchTimeout();
return this;
},

/** clear the timeout and the cached timeout id */
_clearDetailedFetchTimeout: function() {
if (this.detailedFetchTimeoutId) {
clearTimeout(this.detailedFetchTimeoutId);
this.detailedFetchTimeoutId = null;
}
},

/** create any event listeners for the panel
* @fires: rendered:initial on the first render
* @fires: empty-history when switching to a history with no contents or creating a new history
Expand All @@ -101,13 +86,6 @@ var HistoryView = _super.extend(
error: function(model, xhr, options, msg, details) {
this.errorHandler(model, xhr, options, msg, details);
},
"loading-done": () => {
// after the initial load, decorate with more time consuming fields (like HDCA element_counts)
this.detailedFetchTimeoutId = _.delay(() => {
this.detailedFetchTimeoutId = null;
this.model.contents.fetchCollectionCounts();
}, this.FETCH_COLLECTION_COUNTS_DELAY);
},
"views:ready view:attached view:removed": function(view) {
this._renderSelectButton();
},
Expand Down Expand Up @@ -378,17 +356,17 @@ var HistoryView = _super.extend(
}),

_clickPrevPage: function(ev) {
this.model.clearUpdateTimeout();
this.model.stopPolling();
this.model.contents.fetchPrevPage();
},

_clickNextPage: function(ev) {
this.model.clearUpdateTimeout();
this.model.stopPolling();
this.model.contents.fetchNextPage();
},

_changePageSelect: function(ev) {
this.model.clearUpdateTimeout();
this.model.stopPolling();
var page = $(ev.currentTarget).val();
this.model.contents.fetchPage(page);
},
Expand Down