@M01368:20:000000000-A46F5:1:1101:10302:11041/2<\/td><\/tr><\/table>",
- "history_content_type": "dataset",
- "purged": false,
- "display_apps": [],
- "file_size": 217570404,
- "genome_build": "?",
- "validated_state": "unknown",
- "extension": "fastqsanger",
- "url": "/api/histories/63cd3858d057a6d1/contents/ab50e8da6b53023e",
- "api_type": "file"
- },
- {
- "hda_ldda": "hda",
- "tags": [],
- "type": "file",
- "resubmitted": false,
- "validated_state_message": null,
- "misc_info": "uploaded fastqsanger file",
- "deleted": false,
- "id": "ee712460b9af5737",
- "type_id": "dataset-ee712460b9af5737",
- "rerunnable": false,
- "display_types": [],
- "create_time": "2020-06-24T11:31:25.377836",
- "uuid": "a8a88acf-7cd4-4acc-9bf0-23a917e62d94",
- "data_type": "galaxy.datatypes.sequence.FastqSanger",
- "state": "ok",
- "download_url": "/api/histories/63cd3858d057a6d1/contents/ee712460b9af5737/display",
- "model_class": "HistoryDatasetAssociation",
- "dataset_id": "9d04922baebb355f",
- "file_ext": "fastqsanger",
- "meta_files": [],
- "file_name": "/Users/mason/Projects/ohsu/galaxy-data-files/001/dataset_1029.dat",
- "annotation": null,
- "creating_job": "9d04922baebb355f",
- "accessible": true,
- "name": "M117C1-ch_1.fq.fastqsan",
- "created_from_basename": null,
- "visible": true,
- "hid": 7,
- "update_time": "2020-09-10T18:21:32.734118",
- "history_id": "63cd3858d057a6d1",
- "misc_blurb": "207.5 MB",
- "peek": "| @M01368:20:000000000-A46F5:1:1101:10294:8369/1<\/td><\/tr> | | TATTGAACGTAGGTGCGATAAATAATGGGATGAGGCAGGAATCAAAGACAGATACTGCGACATAGGGTGCTCCGGCTCCAGCGTCTCGCAATGCTATCGCGTGCATACCCCCCAGACGAAAATACCAAATGCATGGAGAGCTCCCGTGAGTGGTTAATAGGGTGATAGACCTGTGATCCATCGTGATGTCTTATTTAAGGGGAACGTGTGGGCTATTTAGGCTTTATGGCCCTGAAGTAGGAACCAGATGT<\/td><\/tr> | | +<\/td><\/tr> | | BBBBBFFFFBFFGGGGGGGGGGHGFGHHHGHGHHHGGGGGHHHHHHHGGFHHHGHHGHGGGGGHHHHHGGHHHGGGGGGGHHGGGGHHGGGGHHHGHHGGGGGGHHHHHHHGGGGHGGGGGGHGHGHFGHGHHHHHGHFDHHHHGGAEEEDGFGGHFGFHHHGGHHHFGGHHHHHHHHHHHGGAFBFGGGGGFGGGGGGGGGGGGGGGGGFDFFFFFFFFFFFFFFFFFFFFFFF/9:BFFFFF?AF.BF/<\/td><\/tr> | @M01368:20:000000000-A46F5:1:1101:10302:11041/1<\/td><\/tr><\/table>",
- "history_content_type": "dataset",
- "purged": false,
- "display_apps": [],
- "file_size": 217620050,
- "genome_build": "?",
- "validated_state": "unknown",
- "extension": "fastqsanger",
- "url": "/api/histories/63cd3858d057a6d1/contents/ee712460b9af5737",
- "api_type": "file"
- },
- {
- "hda_ldda": "hda",
- "tags": [],
- "type": "file",
- "resubmitted": false,
- "validated_state_message": null,
- "misc_info": "uploaded fastqsanger file",
- "deleted": false,
- "id": "85f94a3b50966dc9",
- "type_id": "dataset-85f94a3b50966dc9",
- "rerunnable": false,
- "display_types": [],
- "create_time": "2020-06-24T11:30:35.766010",
- "uuid": "fad0ccfa-f164-45d6-b779-1b433178da13",
- "data_type": "galaxy.datatypes.sequence.FastqSanger",
- "state": "ok",
- "download_url": "/api/histories/63cd3858d057a6d1/contents/85f94a3b50966dc9/display",
- "model_class": "HistoryDatasetAssociation",
- "dataset_id": "5733d8d6dc37a408",
- "file_ext": "fastqsanger",
- "meta_files": [],
- "file_name": "/Users/mason/Projects/ohsu/galaxy-data-files/001/dataset_1026.dat",
- "annotation": null,
- "creating_job": "5733d8d6dc37a408",
- "accessible": true,
- "name": "M117-ch_2.fq.fastqsanger",
- "created_from_basename": null,
- "visible": true,
- "hid": 4,
- "update_time": "2020-06-24T11:30:47.520492",
- "history_id": "63cd3858d057a6d1",
- "misc_blurb": "326.1 MB",
- "peek": "| @M01368:13:000000000-A41AR:1:1101:10488:10383/2<\/td><\/tr> | | CTTCAGGGCCATAAAGCCTAAATAGCCCACACGTTCCCCTTAAATAAGACATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCATTTGGTATTTTCGTCTGGGGGGTATGCACGCGATAGCATTGCGAGACGCTGGAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATCCCATTATTTATCGCACCTACGTTCAATATTACAGGCGAAC<\/td><\/tr> | | +<\/td><\/tr> | | CCCCCFFFCCCFGGGGGGGGGGHHHHHHHGGGGHHHHHHHHHHGHGHHHHHHHHHGGHGGHHHHHHHHHHHGHHHHHHHHHHGHHHHGHHHHHGGGGGHHHFHHHHHEHHHHHHFHHHHHGHGHGHFEGGCFGGGGGGGGGGFGGEFFGGGGFFFFFFFFFFFFFF@DAEFEFFFFFFFFFAFFFFFFFFFFFFFEFFFFFFFFFF0BBFFEFFFFBFFF09..:;FFFDFFFBBBBFFF0BFBEFF@B<\/td><\/tr> | @M01368:13:000000000-A41AR:1:1101:10763:9363/2<\/td><\/tr><\/table>",
- "history_content_type": "dataset",
- "purged": false,
- "display_apps": [],
- "file_size": 341989137,
- "genome_build": "?",
- "validated_state": "unknown",
- "extension": "fastqsanger",
- "url": "/api/histories/63cd3858d057a6d1/contents/85f94a3b50966dc9",
- "api_type": "file"
- },
- {
- "hda_ldda": "hda",
- "tags": [],
- "type": "file",
- "resubmitted": false,
- "validated_state_message": null,
- "misc_info": "uploaded fastqsanger file",
- "deleted": false,
- "id": "5733d8d6dc37a408",
- "type_id": "dataset-5733d8d6dc37a408",
- "rerunnable": false,
- "display_types": [],
- "create_time": "2020-06-24T11:30:22.204488",
- "uuid": "1fbccf35-ef7c-4e67-9e9e-b7878b1db264",
- "data_type": "galaxy.datatypes.sequence.FastqSanger",
- "state": "ok",
- "download_url": "/api/histories/63cd3858d057a6d1/contents/5733d8d6dc37a408/display",
- "model_class": "HistoryDatasetAssociation",
- "dataset_id": "e41dd4fdd9a586ef",
- "file_ext": "fastqsanger",
- "meta_files": [],
- "file_name": "/Users/mason/Projects/ohsu/galaxy-data-files/001/dataset_1025.dat",
- "annotation": null,
- "creating_job": "e41dd4fdd9a586ef",
- "accessible": true,
- "name": "M117-ch_1.fq.fastqsanger",
- "created_from_basename": null,
- "visible": true,
- "hid": 3,
- "update_time": "2020-06-24T11:30:32.534455",
- "history_id": "63cd3858d057a6d1",
- "misc_blurb": "325.9 MB",
- "peek": "| @M01368:13:000000000-A41AR:1:1101:10488:10383/1<\/td><\/tr> | | CACTTTAGTAAGTATGTTCGCCTGTAATATTGAACGTAGGTGCGATAAATAATGGGATGAGGCAGGAATCAAAGACAGATACTGCGACATAGGGTGCTCCGGCTCCAGCGTCTCGCAATGCTATCGCGTGCATACCCCCCAGACGAAAATACCAAATGCATGGAGAGCTCCCGTGAGTGGTTAATAGGGTGATAGACCTGTGATCCATCGTGATGTCTTATTTAAGGGGAACGTGTGGGCTATTTAGGCTT<\/td><\/tr> | | +<\/td><\/tr> | | CCCCCFFFFFFFGGGGGGGGGGGHHHHHHHHHGHHHHHHGHHHGGHGGHHHHHHHHHHGHHHGGGGGHHHHHHGHHHHHHHHHHHGGGGGHHHHHGGHHHGGGGGGHHHGFGGHHGGGGHHHHHHGGGGGGHHHHHHHGGGGHGGGGGGHHHHHHHHHHHHHHHHHGHHGHGGGGAEGGGGGGGGEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFF<\/td><\/tr> | @M01368:13:000000000-A41AR:1:1101:10763:9363/1<\/td><\/tr><\/table>",
- "history_content_type": "dataset",
- "purged": false,
- "display_apps": [],
- "file_size": 341692613,
- "genome_build": "?",
- "validated_state": "unknown",
- "extension": "fastqsanger",
- "url": "/api/histories/63cd3858d057a6d1/contents/5733d8d6dc37a408",
- "api_type": "file"
- },
- {
- "hda_ldda": "hda",
- "tags": [],
- "type": "file",
- "resubmitted": false,
- "validated_state_message": null,
- "misc_info": "uploaded fastqsanger file",
- "deleted": false,
- "id": "e41dd4fdd9a586ef",
- "type_id": "dataset-e41dd4fdd9a586ef",
- "rerunnable": false,
- "display_types": [],
- "create_time": "2020-06-24T11:30:08.266197",
- "uuid": "37c3aaf0-c47d-4398-9f82-7f4218fb4c2f",
- "data_type": "galaxy.datatypes.sequence.FastqSanger",
- "state": "ok",
- "download_url": "/api/histories/63cd3858d057a6d1/contents/e41dd4fdd9a586ef/display",
- "model_class": "HistoryDatasetAssociation",
- "dataset_id": "03feb7df0a14654f",
- "file_ext": "fastqsanger",
- "meta_files": [],
- "file_name": "/Users/mason/Projects/ohsu/galaxy-data-files/001/dataset_1024.dat",
- "annotation": null,
- "creating_job": "03feb7df0a14654f",
- "accessible": true,
- "name": "M117-bl_2.fq.fastqsanger",
- "created_from_basename": null,
- "visible": true,
- "hid": 2,
- "update_time": "2020-06-24T11:30:20.546660",
- "history_id": "63cd3858d057a6d1",
- "misc_blurb": "497.4 MB",
- "peek": "| @M01368:28:000000000-A5KYY:1:1101:10045:12104/2<\/td><\/tr> | | CCATAAAGCCTAAATAGCCCACACGTTCCCCTTAAATAAGACATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCATTTGGTATTTTCGTCTGGGGGGTATGCACGCGATAGCATTGCGAGACGCTGGAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATCCCATTATTTATCGCACCTACGTTCAATAT<\/td><\/tr> | | +<\/td><\/tr> | | ?????<<<B<BBBBB<>CFFFFHEHHH?-CEFFB?ECC??EFFDDGDEHFFDD?A?C??;5AEAEGCFFDFBEDC-AFHHFFFHHD*<DDDCGHHHHHFHFFF@@+77D@=DEFHFFBBA=8.?CEECC????8EEEEECC?.8??AA;A?'8??E;;'82AC*8AECCEEEE2AEEE?EE:?AC::AE?AEE::*0:C?A?EC?C*:?E:/?A?848:?CECEEEEECE<\/td><\/tr> | @M01368:28:000000000-A5KYY:1:1101:10126:23848/2<\/td><\/tr><\/table>",
- "history_content_type": "dataset",
- "purged": false,
- "display_apps": [],
- "file_size": 521548589,
- "genome_build": "?",
- "validated_state": "unknown",
- "extension": "fastqsanger",
- "url": "/api/histories/63cd3858d057a6d1/contents/e41dd4fdd9a586ef",
- "api_type": "file"
- }
-]
\ No newline at end of file
diff --git a/client/src/components/providers/History/test/providerTestHelpers.js b/client/src/components/providers/History/test/providerTestHelpers.js
deleted file mode 100644
index 7b97fc520a0..00000000000
--- a/client/src/components/providers/History/test/providerTestHelpers.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { watchForChange } from "jest/helpers";
-import { defaultPayload } from "components/providers/History/ContentProvider";
-
-/**
- * Vue adds a bunch of setters and getters so we need to create our own comparison fn
- */
-export const sameVueObj = (a, b) => {
- return JSON.stringify(a) == JSON.stringify(b);
-};
-
-/**
- * Detects a new emission of a "payload" a standardized object delivered by HistoryContentProvider
- * and CollectionContentProvider, skips the default value that's emitted when it initialized
- *
- * @param {Object} config vm, propName, label, other watchForChange configs
- * @return {Promise} promise that resolves with the changed payload
- */
-export const payloadChange = async (config = {}) => {
- const { vm, propName = "payload", label = "payload change", skipDefault = false, ...cfg } = config;
- const result = await watchForChange({ vm, propName, label, ...cfg });
-
- // can see how fast the response comes back by monitoring this result
- // console.log("payloadChange result", JSON.stringify(result.newVal));
- // chopout the placeholder flag for the default
- // eslint-disable-next-line no-unused-vars
- const { placeholder, ...payload } = result.newVal;
- if (skipDefault && sameVueObj(payload, defaultPayload)) {
- return await payloadChange(config);
- }
-
- return payload;
-};
-
-export const reportPayload = (payload, { indexKey = "hid", label = "payload" } = {}) => {
- const { contents = [], startKeyIndex, ...resultFields } = payload;
- const keys = contents.map((o, idx) => {
- const thisKey = o[indexKey];
- const keyString = idx == startKeyIndex ? `${thisKey}*` : thisKey;
- return keyString;
- });
- const report = { keys, startKeyIndex, ...resultFields };
- console.log(label, report);
-};
diff --git a/client/src/components/providers/History/test/testCollection.js b/client/src/components/providers/History/test/testCollection.js
deleted file mode 100644
index 70c6d59899a..00000000000
--- a/client/src/components/providers/History/test/testCollection.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Doctored collection & children for unit tests
-import rawCollection from "./json/DatasetCollection";
-import rawNestedCollection from "./json/DatasetCollection.nested.json";
-import rawCollectionContent from "./json/collectionContent.json";
-import rawNestedCollectionContent from "./json/collectionContent.nested.json";
-
-// Sample collection for CCP tests
-
-const parent_url = "/foo";
-
-export const testCollectionContent = rawCollectionContent.map((props) => ({ ...props, parent_url }));
-
-export const testCollection = Object.assign({}, rawCollection, {
- contents_url: parent_url,
- element_count: testCollectionContent.length,
-});
-
-export const testNestedCollection = Object.assign({}, rawNestedCollection, {
- parent_url: parent_url,
- contents_url: "/foo/bar",
- element_count: rawNestedCollectionContent.length,
-});
-
-// a list of element_indexes handy for testing
-export const indices = (list) => list.map(({ element_index, _id, parent_url }) => ({ element_index, _id, parent_url }));
diff --git a/client/src/components/providers/History/test/testHistory.js b/client/src/components/providers/History/test/testHistory.js
deleted file mode 100644
index 6371bc79005..00000000000
--- a/client/src/components/providers/History/test/testHistory.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { History } from "components/History/model";
-import { SearchParams } from "components/providers/History/SearchParams";
-import rawHistory from "./json/History.json";
-import rawHistoryContent from "./json/historyContent.json";
-
-// doctor the sample content
-export const testHistoryContent = rawHistoryContent.map((item) => {
- item.history_id = rawHistory.id;
- return item;
-});
-
-// doctor the sample history
-export const testHistory = new History({
- ...rawHistory,
- hid_counter: testHistoryContent[0].hid + 1,
-});
-
-// simplified filter simlates server-side complete set
-export const serverContent = (filters = new SearchParams()) => {
- return testHistoryContent.filter((o) => {
- if (!filters.showDeleted && o.deleted) {
- return false;
- }
- if (!filters.showHidden && !o.visible) {
- return false;
- }
- return true;
- });
-};
diff --git a/client/src/components/providers/InvocationStepProvider.js b/client/src/components/providers/InvocationStepProvider.js
index 1d0808bdeff..b2352e4e888 100644
--- a/client/src/components/providers/InvocationStepProvider.js
+++ b/client/src/components/providers/InvocationStepProvider.js
@@ -1,11 +1,24 @@
-import { default as RxProviderMixin } from "./rxProviders";
-import { invocationStepMonitor } from "./monitors";
+import axios from "axios";
+import { getAppRoot } from "onload/loadConfig";
+import { SingleQueryProvider } from "components/providers/SingleQueryProvider";
+import { rethrowSimple } from "utils/simple-error";
-export default {
- mixins: [RxProviderMixin],
- methods: {
- buildMonitor() {
- return invocationStepMonitor();
- },
- },
+const TERMINAL_JOB_STATES = ["ok", "error", "deleted", "paused"];
+
+async function getInvocation({ id }) {
+ try {
+ const { data } = await axios.get(`${getAppRoot()}api/invocations/any/steps/${id}`);
+ return data;
+ } catch (e) {
+ rethrowSimple(e);
+ }
+}
+
+const stepIsTerminal = (step) => {
+ const isTerminal =
+ ["scheduled", "cancelled", "failed"].includes(step.state) &&
+ step.jobs.every((job) => TERMINAL_JOB_STATES.includes(job.state));
+ return isTerminal;
};
+
+export default SingleQueryProvider(getInvocation, stepIsTerminal);
diff --git a/client/src/components/providers/JobProvider.js b/client/src/components/providers/JobProvider.js
index 484432e3415..af5ddf42b43 100644
--- a/client/src/components/providers/JobProvider.js
+++ b/client/src/components/providers/JobProvider.js
@@ -2,6 +2,7 @@ import axios from "axios";
import { getAppRoot } from "onload/loadConfig";
import { SingleQueryProvider } from "components/providers/SingleQueryProvider";
import { rethrowSimple } from "utils/simple-error";
+import { stateIsTerminal } from "./utils";
async function jobDetails({ jobid }) {
const url = `${getAppRoot()}api/jobs/${jobid}?full=True`;
@@ -23,5 +24,5 @@ async function jobProblems({ jobid }) {
}
}
-export const JobDetailsProvider = SingleQueryProvider(jobDetails);
-export const JobProblemProvider = SingleQueryProvider(jobProblems);
+export const JobDetailsProvider = SingleQueryProvider(jobDetails, stateIsTerminal);
+export const JobProblemProvider = SingleQueryProvider(jobProblems, stateIsTerminal);
diff --git a/client/src/components/providers/History/UserHistories/MockCurrentHistory.js b/client/src/components/providers/MockCurrentHistory.js
similarity index 100%
rename from client/src/components/providers/History/UserHistories/MockCurrentHistory.js
rename to client/src/components/providers/MockCurrentHistory.js
diff --git a/client/src/components/providers/SingleQueryProvider.js b/client/src/components/providers/SingleQueryProvider.js
index 98b33727d57..8cdc310e5e6 100644
--- a/client/src/components/providers/SingleQueryProvider.js
+++ b/client/src/components/providers/SingleQueryProvider.js
@@ -1,4 +1,5 @@
import hash from "object-hash";
+import { LastQueue } from "utils/promise-queue";
/**
* Builds a provider that gets its result from a single promise-based query function and
@@ -6,60 +7,94 @@ import hash from "object-hash";
*
* @param {Function} lookup async function that loads the result, parameters will be an object
* whose properties are the attributes assigned to the provider component
+ * @param {Function} stopRefresh function that will be called with result of lookup.
+ * If function returns true refresh will be stopped.
* @return {VueComponentOptions} Vue component options definition
*/
-export const SingleQueryProvider = (lookup) => {
+export const SingleQueryProvider = (lookup, stopRefresh = (result) => false) => {
const promiseCache = new Map();
-
return {
props: {
useCache: {
type: Boolean,
- default: true,
+ default: false,
+ },
+ autoRefresh: {
+ type: Boolean,
+ default: false,
+ },
+ autoTime: {
+ type: Number,
+ default: 3000,
},
},
data() {
return {
- result: undefined,
- error: undefined,
+ result: null,
+ error: null,
+ timeoutId: null,
};
},
+ created() {
+ this.queue = new LastQueue(this.autoTime);
+ },
computed: {
loading() {
- return this.result === undefined;
+ return this.result === null;
},
cacheKey() {
return hash(this.$attrs || {});
},
},
mounted() {
- let lookupPromise;
- if (this.useCache) {
- lookupPromise = promiseCache.get(this.cacheKey);
- if (!lookupPromise) {
- lookupPromise = lookup(this.$attrs);
- promiseCache.set(this.cacheKey, lookupPromise);
- }
- } else {
- lookupPromise = lookup(this.$attrs);
+ this.doQuery();
+ },
+ destroyed() {
+ if (this.timeoutId) {
+ clearTimeout(this.timeoutId);
}
- lookupPromise.then(
- (result) => {
- this.result = result;
- },
- (err) => {
- this.result = {};
- this.error = err;
- this.$emit("error", err);
- }
- );
},
render() {
- return this.$scopedSlots.default({
- loading: this.loading,
- result: this.result,
- error: this.error,
- });
+ return (
+ this.$scopedSlots.default &&
+ this.$scopedSlots.default({
+ loading: this.loading,
+ result: this.result,
+ error: this.error,
+ })
+ );
+ },
+ methods: {
+ doQuery() {
+ let lookupPromise;
+ if (this.useCache) {
+ lookupPromise = promiseCache.get(this.cacheKey);
+ if (!lookupPromise) {
+ lookupPromise = lookup(this.$attrs);
+ promiseCache.set(this.cacheKey, lookupPromise);
+ }
+ } else {
+ lookupPromise = this.queue.enqueue(lookup, this.$attrs);
+ }
+ lookupPromise.then(
+ (result) => {
+ this.result = result;
+ this.$emit("update:result", result);
+ this.error = null;
+ if (this.autoRefresh && !stopRefresh(result)) {
+ this.timeoutId = setTimeout(() => {
+ this.doQuery();
+ }, this.autoTime);
+ }
+ },
+ (err) => {
+ this.result = {};
+ this.error = err;
+ this.$emit("error", err);
+ console.debug("Failed to fulfill promise.", err);
+ }
+ );
+ },
},
};
};
diff --git a/client/src/components/providers/UrlDataProvider.js b/client/src/components/providers/UrlDataProvider.js
index a7051ceef71..d2d856b674d 100644
--- a/client/src/components/providers/UrlDataProvider.js
+++ b/client/src/components/providers/UrlDataProvider.js
@@ -1,15 +1,4 @@
-import axios from "axios";
-import { getAppRoot } from "onload/loadConfig";
import { SingleQueryProvider } from "components/providers/SingleQueryProvider";
-import { rethrowSimple } from "utils/simple-error";
-
-async function urlData({ url }) {
- try {
- const { data } = await axios.get(`${getAppRoot()}${url}`);
- return data;
- } catch (e) {
- rethrowSimple(e);
- }
-}
+import { urlData } from "utils/url";
export const UrlDataProvider = SingleQueryProvider(urlData);
diff --git a/client/src/components/providers/History/UserHistories/UserHistories.js b/client/src/components/providers/UserHistories.js
similarity index 81%
rename from client/src/components/providers/History/UserHistories/UserHistories.js
rename to client/src/components/providers/UserHistories.js
index 7132e31872c..c8c65824889 100644
--- a/client/src/components/providers/History/UserHistories/UserHistories.js
+++ b/client/src/components/providers/UserHistories.js
@@ -13,47 +13,42 @@
*/
import { mapActions, mapGetters } from "vuex";
-import { History } from "components/History/model/History";
export default {
props: {
user: { type: Object, required: true },
},
computed: {
- ...mapGetters("betaHistory", ["currentHistoryId", "currentHistory", "histories"]),
+ ...mapGetters("history", ["currentHistoryId", "currentHistory", "histories", "historiesLoading"]),
currentHistoryModel() {
if (this.currentHistory !== null) {
- return new History(this.currentHistory);
+ return Object.assign({}, this.currentHistory);
}
return null;
},
historyModels() {
- return this.histories.map((h) => new History(h));
+ return this.histories.map((h) => Object.assign({}, h));
},
},
methods: {
- ...mapActions("betaHistory", [
+ ...mapActions("history", [
"loadHistoryById",
"createNewHistory",
"updateHistory",
"deleteHistory",
"setCurrentHistoryId",
"setHistory",
- "loadUserHistories",
+ "loadHistories",
"secureHistory",
]),
-
- updateCurrentHistory(updates) {
- this.updateHistory({ ...updates, id: this.currentHistory.id });
- },
},
watch: {
// when user changes reload histories
user: {
immediate: true,
handler() {
- this.loadUserHistories();
+ this.loadHistories();
},
},
@@ -70,6 +65,7 @@ export default {
// currently selected history object, should be a full object not just a summary
currentHistory: this.currentHistoryModel,
currentHistoryId: this.currentHistoryId,
+ historiesLoading: this.historiesLoading,
handlers: {
// Updates the history in the store without a trip to the server, in the event that a
@@ -85,11 +81,6 @@ export default {
// save new history params should be an object with an id property and any additional
// properties that are to be updated on the server. A full history object is not required
updateHistory: this.updateHistory,
- updateCurrentHistory: this.updateCurrentHistory,
-
- // also conform to .sync event name format
- "update:history": this.updateHistory,
- "update:currentHistory": this.updateCurrentHistory,
// delete history then clear currentHistoryId
deleteHistory: (history) => this.deleteHistory({ history }),
diff --git a/client/src/components/providers/History/UserHistories/UserHistories.test.js b/client/src/components/providers/UserHistories.test.js
similarity index 91%
rename from client/src/components/providers/History/UserHistories/UserHistories.test.js
rename to client/src/components/providers/UserHistories.test.js
index f3dfa4c6264..ee809bb337d 100644
--- a/client/src/components/providers/History/UserHistories/UserHistories.test.js
+++ b/client/src/components/providers/UserHistories.test.js
@@ -1,8 +1,7 @@
import Vuex from "vuex";
import { shallowMount } from "@vue/test-utils";
import { getLocalVue, wait, waitForLifecyleEvent, watchForChange } from "jest/helpers";
-import { historyStore } from "components/History/model/historyStore";
-import { History } from "components/History/model/History";
+import { historyStore } from "store/historyStore/historyStore";
import UserHistories from "./UserHistories";
// #region Test Data
@@ -32,11 +31,10 @@ import {
createNewHistory,
updateHistoryFields,
deleteHistoryById,
-} from "components/History/model/queries";
+} from "store/historyStore/model/queries";
jest.mock("app");
-jest.mock("components/providers/History/caching");
-jest.mock("components/History/model/queries");
+jest.mock("store/historyStore/model/queries");
const maxServerDelay = 60;
const serverDelay = async (min = 0, max = maxServerDelay) => {
@@ -95,7 +93,7 @@ const localVue = getLocalVue();
// Generate store for tesint, just need the one module we talk to
const historiesStore = new Vuex.Store({
modules: {
- betaHistory: historyStore,
+ history: historyStore,
},
});
@@ -123,7 +121,7 @@ describe("UserHistories", () => {
});
afterEach(async () => {
- historiesStore.dispatch("betaHistory/reset");
+ historiesStore.dispatch("history/resetHistory");
if (wrapper) {
await wrapper.destroy();
}
@@ -135,13 +133,13 @@ describe("UserHistories", () => {
expect(histories).toBeInstanceOf(Array);
expect(histories.length).toEqual(historySummaries.length);
histories.forEach((h) => {
- expect(h).toBeInstanceOf(History);
+ expect(h).toBeInstanceOf(Object);
});
});
test("currentHistory", async () => {
const { histories, currentHistory } = slotProps;
- expect(currentHistory).toBeInstanceOf(History);
+ expect(currentHistory).toBeInstanceOf(Object);
expect(histories.find((h) => h.id == currentHistory.id)).toEqual(currentHistory);
});
});
@@ -164,7 +162,7 @@ describe("UserHistories", () => {
// wait for it to register
const { currentHistory: changedHistory, histories: newHistories } = slotProps;
expect(changedHistory.id).toEqual(nextHistory.id);
- expect(changedHistory).toBeInstanceOf(History);
+ expect(changedHistory).toBeInstanceOf(Object);
expect(newHistories.find((h) => h.id == changedHistory.id)).toEqual(changedHistory);
});
@@ -190,7 +188,7 @@ describe("UserHistories", () => {
handlers: { updateHistory },
} = slotProps;
expect(updateHistory).toBeInstanceOf(Function);
- expect(currentHistory).toBeInstanceOf(History);
+ expect(currentHistory).toBeInstanceOf(Object);
const modifiedHistory = { ...currentHistory, foo: "bar" };
expect(modifiedHistory.id).toBeDefined();
diff --git a/client/src/components/providers/fetch.js b/client/src/components/providers/fetch.js
deleted file mode 100644
index 185bc7d07cb..00000000000
--- a/client/src/components/providers/fetch.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { pipe } from "rxjs";
-import { map, mergeMap } from "rxjs/operators";
-import { ajax } from "rxjs/ajax";
-import { prependPath } from "utils/redirect";
-
-export const fetchDatasetById = () => {
- return pipe(
- map((id) => prependPath(`/api/datasets/${id}`)),
- mergeMap((url) => ajax.getJSON(url))
- );
-};
-
-export const fetchDatasetCollectionById = () => {
- return pipe(
- map((id) => prependPath(`/api/dataset_collections/${id}?instance_type=history`)),
- mergeMap((url) => ajax.getJSON(url))
- );
-};
-
-export const fetchInvocationStepById = () => {
- return pipe(
- map((id) => prependPath(`/api/invocations/any/steps/${id}`)),
- mergeMap((url) => ajax.getJSON(url))
- );
-};
diff --git a/client/src/components/providers/monitors/index.js b/client/src/components/providers/monitors/index.js
deleted file mode 100644
index 1eccdcc0dc9..00000000000
--- a/client/src/components/providers/monitors/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// Public exports
-export {
- buildHistoryMonitor,
- datasetMonitor,
- datasetCollectionMonitor,
- getHistoryMonitor,
- invocationStepMonitor,
-} from "./monitors";
-
-export { monitorHistoryUntilTrue } from "./monitorHistoriesUntilTrue";
diff --git a/client/src/components/providers/monitors/monitorHistoriesUntilTrue.js b/client/src/components/providers/monitors/monitorHistoriesUntilTrue.js
deleted file mode 100644
index 7be014c8439..00000000000
--- a/client/src/components/providers/monitors/monitorHistoriesUntilTrue.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { concat } from "rxjs";
-import { share, take, takeWhile } from "rxjs/operators";
-import { getHistoryMonitor } from "./monitors";
-
-export const monitorHistoryUntilTrue = (condition, historyId, monitorEvery = 3000) => {
- // monitorHistory until condition is true, then fetch one last update
- const historyMonitor$ = getHistoryMonitor(historyId, monitorEvery);
- const primaryHistoryMonitor$ = historyMonitor$.pipe(
- takeWhile(() => !condition()),
- share()
- );
- return concat(
- primaryHistoryMonitor$,
- historyMonitor$.pipe(
- // get one more update after invocation is terminal
- take(1)
- )
- );
-};
diff --git a/client/src/components/providers/monitors/monitorHistoriesUntilTrue.test.js b/client/src/components/providers/monitors/monitorHistoriesUntilTrue.test.js
deleted file mode 100644
index d151939d31e..00000000000
--- a/client/src/components/providers/monitors/monitorHistoriesUntilTrue.test.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { of } from "rxjs";
-import { ObserverSpy } from "@hirez_io/observer-spy";
-import { loadHistoryContents } from "components/providers/History/caching";
-import { monitorHistoryUntilTrue } from "./monitorHistoriesUntilTrue";
-
-jest.mock("components/providers/History/caching");
-
-// prettier-ignore
-describe("monitorHistoryUntilTrue", () => {
- let monitor$;
- let spy;
- const historyId = 1;
- let stopMonitor;
- const stopFn = () => stopMonitor == true;
- let mockLoadHistoryContent;
- let completed;
-
- beforeEach(async () => {
- stopMonitor = false;
- completed = false;
- mockLoadHistoryContent = of(1, 2, 3);
- loadHistoryContents.mockImplementation(() => () => mockLoadHistoryContent);
- monitor$ = monitorHistoryUntilTrue(stopFn, historyId, 20);
- });
-
- test("that history subscription runs until condition is met, and then once more", async () => {
- // spy on output of observable;
- spy = new ObserverSpy();
- monitor$.subscribe(spy);
- monitor$.subscribe({
- next: (val) => {
- // stop once we reach 2
- if (val >= 2) {
- stopMonitor = true;
- }
- },
- complete: () => {
- completed = true;
- }
- })
- await spy.onComplete();
-
- // We stop at 2, and then take one more value.
- expect(spy.getValues()).toEqual([1, 2, 1]);
- expect(completed).toBeTruthy();
- });
-});
diff --git a/client/src/components/providers/monitors/monitors.js b/client/src/components/providers/monitors/monitors.js
deleted file mode 100644
index c946a287a92..00000000000
--- a/client/src/components/providers/monitors/monitors.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { of, race, pipe, concat, iif } from "rxjs";
-import { filter, map, mergeMap, switchMap, delay, share, repeat, take, takeWhile } from "rxjs/operators";
-import { cacheContent, monitorContentQuery, loadHistoryContents } from "components/providers/History/caching";
-import { fetchDatasetById, fetchDatasetCollectionById, fetchInvocationStepById } from "../fetch";
-
-// prettier-ignore
-export const datasetMonitor = (cfg = {}) => {
- // delay gives the monitor a head-start so it can
- // spin up a little before we decide to do an ajax call
- const { spinUpDelay } = cfg;
-
- return pipe(
- switchMap((id) => createContentMonitor(id, 'dataset', spinUpDelay))
- );
-};
-
-export const datasetCollectionMonitor = (cfg = {}) => {
- const { spinUpDelay } = cfg;
- return pipe(switchMap((id) => createContentMonitor(id, "dataset_collection", spinUpDelay)));
-};
-
-const createContentMonitor = (id, contentType, spinUpDelay = 250) => {
- let fetcher;
- switch (contentType) {
- case "dataset":
- fetcher = fetchDatasetById;
- break;
- case "dataset_collection":
- fetcher = fetchDatasetCollectionById;
- break;
- default:
- console.error(`Can't create monitor for ${contentType}-${id}`);
- }
- return createMonitor(id, contentType, fetcher, spinUpDelay);
-};
-
-const buildPouchRequest = (id, contentType) => {
- return {
- selector: {
- id: { $eq: id },
- history_content_type: contentType,
- },
- };
-};
-
-// prettier-ignore
-const createMonitor = (id, contentType, fetcher, spinUpDelay = 250) => {
- // build the pouchdb/mongo request, which is a selector
- // and limits, offsets, etc
- const request = buildPouchRequest(id, contentType);
-
- // retrieve changes from cache
- const changes$ = of(request).pipe(
- monitorContentQuery(),
- // singleUpdateResult(),
- filter(change => change.match && change.doc),
- map(change => change.doc),
- share()
- );
-
- // cache results that reflect non-deleted existing data in the cache
- const existing$ = changes$.pipe(
- filter(Boolean),
- );
-
- // load and cache dataset from server then switch over to the monitor
- const fetchItem$ = of(id).pipe(
- delay(spinUpDelay),
- fetcher(),
- mergeMap((rawJson) => cacheContent(rawJson, true))
- );
-
- // let the monitor and the ajax call race, first one wins
- const firstValue$ = race(fetchItem$, existing$).pipe(take(1));
-
- return concat(firstValue$, changes$);
-};
-
-const TERMINAL_JOB_STATES = ["ok", "error", "deleted", "paused"];
-const stepIsTerminal = (step) => {
- const isTerminal =
- ["scheduled", "cancelled", "failed"].includes(step.state) &&
- step.jobs.every((job) => TERMINAL_JOB_STATES.includes(job.state));
- return isTerminal;
-};
-
-const createInvocationStepMonitor = (id, monitorEvery = 3000) => {
- const initialFetch$ = of(id).pipe(fetchInvocationStepById());
- const pollingFetch$ = of(id).pipe(
- delay(monitorEvery),
- fetchInvocationStepById(),
- repeat(),
- // takeWhile cancels source on true, so also cancels repeat
- takeWhile((val) => !stepIsTerminal(val), true)
- );
- return initialFetch$.pipe(
- // poll only if initial status is non-terminal. If polling, concat the initial result we already have
- // with pollingFetch$ which emits after the delay
- mergeMap((val) => iif(() => stepIsTerminal(val), of(val), concat(of(val), pollingFetch$)))
- );
-};
-
-export const invocationStepMonitor = (monitorEvery = 3000) =>
- pipe(switchMap((id) => createInvocationStepMonitor(id, monitorEvery)));
-
-// feed observables, keyed by history id. Keeps just one historyMonitor around.
-export const historyFeeds = new Map();
-
-export const getHistoryMonitor = (historyId, monitorEvery = 3000) => {
- if (!historyFeeds.has(historyId)) {
- historyFeeds.set(historyId, buildHistoryMonitor(historyId, monitorEvery));
- }
- return historyFeeds.get(historyId);
-};
-
-// prettier-ignore
-export const buildHistoryMonitor = (historyId, monitorEvery = 3000) => {
- // temporary hack until we can subscribe to invocations and their outputs.
- // set large windowSize around which to monitor (could we just monitor all updates?)
- const windowSize = 100000;
- return of([historyId, {showHidden: true, showVisible: true}, 1]).pipe(
- // noInitial skips fetching exisiting datasets and only monitors
- // for updated datastes after the last query (or now, if this is the first query)
- loadHistoryContents({windowSize, noInitial: true}),
- delay(monitorEvery),
- repeat(),
- share());
-};
diff --git a/client/src/components/providers/monitors/monitors.test.js b/client/src/components/providers/monitors/monitors.test.js
deleted file mode 100644
index 4a3c434a08b..00000000000
--- a/client/src/components/providers/monitors/monitors.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { of } from "rxjs";
-import { ObserverSpy } from "@hirez_io/observer-spy";
-import { invocationStepMonitor } from "./monitors";
-import * as fetch from "../fetch";
-
-describe("invocationStepMonitor", () => {
- let monitor$;
- let spy;
- let fetchInvocationMock;
- const invocationId = 1;
- const invocationStepDataNew = { jobs: [{ state: "new" }], state: "scheduled" };
- const invocationStepDataRunning = { jobs: [{ state: "running" }], state: "scheduled" };
- const invocationStepDataOk = { jobs: [{ state: "ok" }], state: "scheduled" };
-
- beforeEach(async () => {
- fetchInvocationMock = jest.spyOn(fetch, "fetchInvocationStepById");
- spy = new ObserverSpy();
- });
-
- test("invocationStepMonitor runs until step and job are terminal", async () => {
- // mocks fetchInvocationStep in initialFetch$
- fetchInvocationMock.mockImplementationOnce(() => () => of(invocationStepDataNew));
- // mocks fetchInvocationStep in pollingFetch$
- fetchInvocationMock.mockImplementationOnce(
- () => () => of(invocationStepDataRunning, invocationStepDataOk, invocationStepDataOk)
- );
- monitor$ = of(invocationId).pipe(invocationStepMonitor(20));
- monitor$.subscribe(spy);
- await spy.onComplete();
- expect(spy.getValues()).toEqual([invocationStepDataNew, invocationStepDataRunning, invocationStepDataOk]);
- });
- test("invocationStepMonitor terminates immediately if step is terminal", async () => {
- fetchInvocationMock.mockImplementationOnce(() => () => of(invocationStepDataOk));
- fetchInvocationMock.mockImplementationOnce(
- () => () => of(invocationStepDataRunning, invocationStepDataOk, invocationStepDataOk, invocationStepDataOk)
- );
- monitor$ = of(invocationId).pipe(invocationStepMonitor(20));
- monitor$.subscribe(spy);
- await spy.onComplete();
- expect(spy.getValues()).toEqual([invocationStepDataOk]);
- });
-});
diff --git a/client/src/components/providers/rxProviders.js b/client/src/components/providers/rxProviders.js
deleted file mode 100644
index 534cfeee426..00000000000
--- a/client/src/components/providers/rxProviders.js
+++ /dev/null
@@ -1,46 +0,0 @@
-export default {
- props: {
- id: { type: String, required: true },
- },
- data() {
- return {
- item: undefined,
- error: undefined,
- };
- },
- computed: {
- loading() {
- return this.item === undefined && this.error === undefined;
- },
- },
- methods: {
- buildMonitor() {
- throw new Error("define me");
- },
- },
- created() {
- const id$ = this.watch$("id");
- const monitor$ = id$.pipe(this.buildMonitor());
-
- this.listenTo(monitor$, {
- next: (ds) => {
- this.item = ds;
- this.error = undefined;
- },
- error: (err) => {
- this.error = err.response.err_msg;
- console.warn("error in content monitor", err);
- },
- complete: () => {
- console.log("I should only complete on destroy");
- },
- });
- },
- render() {
- return this.$scopedSlots.default({
- loading: this.loading,
- item: this.item,
- error: this.error,
- });
- },
-};
diff --git a/client/src/components/providers/storeProviders.js b/client/src/components/providers/storeProviders.js
index 975aef2d091..15cf7a4bdd3 100644
--- a/client/src/components/providers/storeProviders.js
+++ b/client/src/components/providers/storeProviders.js
@@ -1,8 +1,8 @@
// Simple dataset provider, looks at api for result, renders to slot prop
import axios from "axios";
import { prependPath } from "utils/redirect";
+import { mapActions, mapGetters } from "vuex";
import { mapCacheActions } from "vuex-cache";
-import { mapGetters } from "vuex";
export const SimpleProviderMixin = {
props: {
@@ -47,39 +47,6 @@ export const SimpleProviderMixin = {
},
};
-export const StoreProviderMixin = {
- computed: {
- storeItem() {
- throw new Error("define me");
- },
- },
- watch: {
- storeItem: {
- handler(newItem, oldItem) {
- this.item = newItem;
- },
- },
- },
-};
-
-export const DatasetCollectionProvider = {
- mixins: [SimpleProviderMixin, StoreProviderMixin],
- methods: {
- ...mapCacheActions("datasetCollections", ["fetchDatasetCollection"]),
- async load() {
- this.loading = true;
- this.item = await this.fetchDatasetCollection(this.id);
- this.loading = false;
- },
- },
- computed: {
- ...mapGetters("datasetCollections", ["getDatasetCollectionById"]),
- storeItem() {
- return this.getDatasetCollectionById(this.id);
- },
- },
-};
-
export const GenomeProvider = {
mixins: [SimpleProviderMixin],
props: {
@@ -159,3 +126,76 @@ export const JobProvider = {
},
},
};
+
+/**
+ * Provider component interface to the actual stores i.e. history items and collection elements stores.
+ * @param {String} storeAction The store action is executed when the consuming component e.g. the history panel, changes the provider props.
+ * @param {String} storeGetter The store getter passes its result to the slot of the corresponding provider.
+ * @param {String} storeCountGetter The query stats store getter passes its matches counts to the slot of the corresponding provider.
+ */
+export const StoreProvider = (storeAction, storeGetter, storeCountGetter = undefined) => {
+ return {
+ watch: {
+ $attrs(newVal, oldVal) {
+ if (JSON.stringify(newVal) != JSON.stringify(oldVal)) {
+ this.load();
+ }
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ error: null,
+ };
+ },
+ created() {
+ this.load();
+ },
+ computed: {
+ ...mapGetters([storeGetter, storeCountGetter]),
+ attributes() {
+ return this.toCamelCase(this.$attrs);
+ },
+ result() {
+ return this[storeGetter](this.attributes);
+ },
+ count() {
+ return storeCountGetter ? this[storeCountGetter]() : undefined;
+ },
+ },
+ render() {
+ return this.$scopedSlots.default({
+ error: this.error,
+ loading: this.loading,
+ result: this.result,
+ count: this.count,
+ });
+ },
+ methods: {
+ ...mapActions([storeAction]),
+ async load() {
+ this.loading = true;
+ try {
+ await this[storeAction](this.attributes);
+ this.error = null;
+ this.loading = false;
+ } catch (error) {
+ this.error = error;
+ this.loading = false;
+ }
+ },
+ toCamelCase(attributes) {
+ const result = {};
+ for (const key in attributes) {
+ const newKey = key.replace(/-./g, (x) => x[1].toUpperCase());
+ result[newKey] = attributes[key];
+ }
+ return result;
+ },
+ },
+ };
+};
+
+export const DatasetProvider = StoreProvider("fetchDataset", "getDataset");
+export const CollectionElementsProvider = StoreProvider("fetchCollectionElements", "getCollectionElements");
+export const HistoryItemsProvider = StoreProvider("fetchHistoryItems", "getHistoryItems", "getTotalMatchesCount");
diff --git a/client/src/components/providers/History/test/json/Dataset.json b/client/src/components/providers/test/json/Dataset.json
similarity index 100%
rename from client/src/components/providers/History/test/json/Dataset.json
rename to client/src/components/providers/test/json/Dataset.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.error.json b/client/src/components/providers/test/json/DatasetCollection.error.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.error.json
rename to client/src/components/providers/test/json/DatasetCollection.error.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.heterogeneous.json b/client/src/components/providers/test/json/DatasetCollection.heterogeneous.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.heterogeneous.json
rename to client/src/components/providers/test/json/DatasetCollection.heterogeneous.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.homogeneous.json b/client/src/components/providers/test/json/DatasetCollection.homogeneous.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.homogeneous.json
rename to client/src/components/providers/test/json/DatasetCollection.homogeneous.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.json b/client/src/components/providers/test/json/DatasetCollection.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.json
rename to client/src/components/providers/test/json/DatasetCollection.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.nested.json b/client/src/components/providers/test/json/DatasetCollection.nested.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.nested.json
rename to client/src/components/providers/test/json/DatasetCollection.nested.json
diff --git a/client/src/components/providers/History/test/json/DatasetCollection.processing.json b/client/src/components/providers/test/json/DatasetCollection.processing.json
similarity index 100%
rename from client/src/components/providers/History/test/json/DatasetCollection.processing.json
rename to client/src/components/providers/test/json/DatasetCollection.processing.json
diff --git a/client/src/components/providers/utils.js b/client/src/components/providers/utils.js
new file mode 100644
index 00000000000..b06b25c4aa6
--- /dev/null
+++ b/client/src/components/providers/utils.js
@@ -0,0 +1,5 @@
+import JOB_STATES_MODEL from "mvc/history/job-states-model";
+
+export function stateIsTerminal(result) {
+ return !JOB_STATES_MODEL.NON_TERMINAL_STATES.includes(result.state);
+}
diff --git a/client/src/components/xrefs.vue b/client/src/components/xrefs.vue
deleted file mode 100644
index 0cdf65c2a7e..00000000000
--- a/client/src/components/xrefs.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
- References
-
-
-
-
-
diff --git a/client/src/entry/analysis/AnalysisRouter.js b/client/src/entry/analysis/AnalysisRouter.js
index f3d687eda11..d7405318d44 100644
--- a/client/src/entry/analysis/AnalysisRouter.js
+++ b/client/src/entry/analysis/AnalysisRouter.js
@@ -17,42 +17,43 @@ import decodeUriComponent from "decode-uri-component";
import Router from "layout/router";
import ToolForm from "components/Tool/ToolForm";
import FormGeneric from "components/Form/FormGeneric";
-import Sharing from "components/Sharing/Sharing.vue";
-import UserPreferences from "components/User/UserPreferences.vue";
-import DatasetList from "components/Dataset/DatasetList.vue";
+import Sharing from "components/Sharing/Sharing";
+import UserPreferences from "components/User/UserPreferences";
+import DatasetList from "components/Dataset/DatasetList";
import { getUserPreferencesModel } from "components/User/UserPreferencesModel";
-import CustomBuilds from "components/User/CustomBuilds.vue";
+import CustomBuilds from "components/User/CustomBuilds";
import Tours from "mvc/tours";
import GridView from "mvc/grid/grid-view";
import GridShared from "mvc/grid/grid-shared";
-import JobDetails from "components/JobInformation/JobDetails.vue";
-import WorkflowImport from "components/Workflow/WorkflowImport.vue";
-import TrsImport from "components/Workflow/TrsImport.vue";
-import TrsSearch from "components/Workflow/TrsSearch.vue";
-import InteractiveTools from "components/InteractiveTools/InteractiveTools.vue";
-import WorkflowList from "components/Workflow/WorkflowList.vue";
-import CollectionEditView from "components/Collections/common/CollectionEditView.vue";
-import HistoryImport from "components/HistoryImport.vue";
+import JobDetails from "components/JobInformation/JobDetails";
+import WorkflowImport from "components/Workflow/WorkflowImport";
+import TrsImport from "components/Workflow/TrsImport";
+import TrsSearch from "components/Workflow/TrsSearch";
+import InteractiveTools from "components/InteractiveTools/InteractiveTools";
+import WorkflowList from "components/Workflow/WorkflowList";
+import CollectionEditView from "components/Collections/common/CollectionEditView";
+import HistoryImport from "components/HistoryImport";
import { HistoryExport } from "components/HistoryExport/index";
-import HistoryView from "components/HistoryView.vue";
-import WorkflowInvocationReport from "components/Workflow/InvocationReport.vue";
-import WorkflowRun from "components/Workflow/Run/WorkflowRun.vue";
+import HistoryView from "components/HistoryView";
+import WorkflowInvocationReport from "components/Workflow/InvocationReport";
+import WorkflowRun from "components/Workflow/Run/WorkflowRun";
import UserInvocations from "components/Workflow/UserInvocations";
-import ToolsView from "components/ToolsView/ToolsView.vue";
-import ToolsJson from "components/ToolsView/ToolsSchemaJson/ToolsJson.vue";
+import ToolsView from "components/ToolsView/ToolsView";
+import ToolsJson from "components/ToolsView/ToolsSchemaJson/ToolsJson";
import HistoryList from "mvc/history/history-list";
import VisualizationsList from "components/Visualizations/Index";
import QueryStringParsing from "utils/query-string-parsing";
import DatasetError from "components/DatasetInformation/DatasetError";
import DatasetAttributes from "components/DatasetInformation/DatasetAttributes";
-import Citations from "components/Citation/Citations.vue";
-import DisplayStructure from "components/DisplayStructured.vue";
+import Citations from "components/Citation/Citations";
+import DisplayStructure from "components/DisplayStructured";
import { CloudAuth } from "components/User/CloudAuth";
import { ExternalIdentities } from "components/User/ExternalIdentities";
-import Confirmation from "components/login/Confirmation.vue";
-import DatasetDetails from "components/Details/DatasetDetails.vue";
+import Confirmation from "components/login/Confirmation";
+import DatasetDetails from "components/DatasetInformation/DatasetDetails";
import Libraries from "components/Libraries";
import { mountVueComponent } from "utils/mountVueComponent";
+import { StorageDashboardRouter } from "components/User/DiskUsage";
import { newUserDict } from "../../../../static/plugins/welcome_page/new_user/dist/static/topics/index";
@@ -109,9 +110,17 @@ export const getAnalysisRouter = (Galaxy) => {
"(/)datasets(/)(:dataset_id)/show_params": "show_dataset_details",
"(/)interactivetool_entry_points(/)list": "show_interactivetool_list",
"(/)libraries*path": "show_library_folder",
+ "(/)storage*path": "show_storage_dashboard",
},
- require_login: ["show_user", "show_user_form", "show_workflows", "show_cloud_auth", "show_external_ids"],
+ require_login: [
+ "show_user",
+ "show_user_form",
+ "show_workflows",
+ "show_cloud_auth",
+ "show_external_ids",
+ "show_storage_dashboard",
+ ],
authenticate: function (args, name) {
const Galaxy = getGalaxyInstance();
@@ -167,6 +176,10 @@ export const getAnalysisRouter = (Galaxy) => {
this._display_vue_helper(Libraries);
},
+ show_storage_dashboard: function () {
+ this._display_vue_helper(StorageDashboardRouter);
+ },
+
show_cloud_auth: function () {
this._display_vue_helper(CloudAuth);
},
@@ -381,7 +394,8 @@ export const getAnalysisRouter = (Galaxy) => {
},
show_custom_builds: function () {
- const historyPanel = this.page.historyPanel.historyView;
+ const Galaxy = getGalaxyInstance();
+ const historyPanel = Galaxy.currHistoryPanel;
if (!historyPanel || !historyPanel.model || !historyPanel.model.id) {
window.setTimeout(() => {
this.show_custom_builds();
diff --git a/client/src/entry/analysis/index.js b/client/src/entry/analysis/index.js
index 8898b967012..94ef41bd077 100644
--- a/client/src/entry/analysis/index.js
+++ b/client/src/entry/analysis/index.js
@@ -5,15 +5,16 @@ import MvcHistoryPanel from "entry/panels/history-panel";
import Page from "layout/page";
// Vue adapter emulates current features of backbone history panel
-import { HistoryPanelProxy } from "components/History";
-import { isBetaHistoryOpen } from "components/History/adapters/betaToggle";
+import { HistoryPanelProxy } from "components/History/adapters/HistoryPanelProxy";
+import { shouldUseLegacyHistory } from "components/History/adapters/betaToggle";
addInitialization((Galaxy, { options = {} }) => {
console.log("Analysis custom page setup");
// Handle beta history panel
// Need to mock Galaxy.currHistoryPanel
- const HistoryPanel = isBetaHistoryOpen() ? HistoryPanelProxy : MvcHistoryPanel;
+ // pass config through for defaults
+ const HistoryPanel = shouldUseLegacyHistory(Galaxy.config.use_legacy_history) ? MvcHistoryPanel : HistoryPanelProxy;
const pageOptions = Object.assign({}, options, {
config: Object.assign({}, options.config, {
@@ -24,7 +25,6 @@ addInitialization((Galaxy, { options = {} }) => {
Right: HistoryPanel,
Router: getAnalysisRouter(Galaxy),
});
-
Galaxy.page = new Page.View(pageOptions);
});
diff --git a/client/src/entry/panels/history-panel.js b/client/src/entry/panels/history-panel.js
index 6780cd37987..b9de6a804b6 100644
--- a/client/src/entry/panels/history-panel.js
+++ b/client/src/entry/panels/history-panel.js
@@ -67,12 +67,13 @@ const HistoryPanel = Backbone.View.extend({
target: "galaxy_main",
icon: "fa fa-cog",
href: `${this.root}root/history_options`,
+ description: "history options",
});
panelHeaderButtons.push(this.buttonOptions);
this.model = new Backbone.Model({
// define components
- cls: "history-right-panel history-details",
+ cls: "history-right-panel details",
title: _l("History"),
buttons: panelHeaderButtons,
});
diff --git a/client/src/galaxy.interactive_environments.js b/client/src/galaxy.interactive_environments.js
deleted file mode 100644
index cb641e1832c..00000000000
--- a/client/src/galaxy.interactive_environments.js
+++ /dev/null
@@ -1,262 +0,0 @@
-import $ from "jquery";
-import { Toast } from "ui/toast";
-
-/**
- * Internal function to remove content from the main area and add the notebook.
- * Not idempotent
- * TODO: This isn't just internal, some notebooks are calling it?
- */
-export function append_notebook(url) {
- clear_main_area();
- $("#main").append(
- ``
- );
-}
-
-export function clear_main_area() {
- $("#spinner").remove();
- $("#main").children().remove();
-}
-
-export function display_spinner() {
- $("#main").append(``);
-}
-
-/* Create a spin_state object used by spin() and spin_again() */
-export function make_spin_state(
- type,
- ajax_timeout_init,
- ajax_timeout_max,
- ajax_timeout_step,
- sleep_init,
- sleep_max,
- sleep_step,
- log_attempts
-) {
- return {
- type: typeof type !== "undefined" ? type : "GIE spin",
- ajax_timeout: typeof ajax_timeout_init !== "undefined" ? ajax_timeout_init : 2000,
- ajax_timeout_max: typeof ajax_timeout_max !== "undefined" ? ajax_timeout_max : 16000,
- ajax_timeout_step: typeof ajax_timeout_step !== "undefined" ? ajax_timeout_step : 500,
- sleep: typeof sleep_init !== "undefined" ? sleep_init : 500,
- sleep_max: typeof sleep_max !== "undefined" ? sleep_max : 8000,
- sleep_step: typeof sleep_step !== "undefined" ? sleep_step : 100,
- log_attempts: typeof log_attempts !== "undefined" ? log_attempts : true,
- count: 0,
- };
-}
-
-/* Log/display an error when spinning fails. */
-export function spin_error(console_msg, user_msg, clear) {
- console.log(console_msg);
- if (clear) {
- clear_main_area();
- }
- if (typeof user_msg == "string") {
- Toast.clear();
- Toast.error(user_msg, "Error", {
- closeButton: true,
- timeOut: 0,
- extendedTimeOut: 0,
- tapToDismiss: false,
- });
- }
-}
-
-/* Increase sleep time and spin again. */
-function spin_again(spin_state) {
- if (spin_state.sleep < spin_state.sleep_max) {
- spin_state.sleep += spin_state.sleep_step;
- }
- if (spin_state.log_attempts) {
- console.log(
- `${spin_state.type} request ${spin_state.count} request timeout ${spin_state.ajax_timeout}ms sleeping ${
- spin_state.sleep / 1000
- }s`
- );
- }
- window.setTimeout(spin_state.spinner, spin_state.sleep);
-}
-
-/*
- * Spin on a URL as long as it times out, otherwise, call the provided success or error callback. If the callback
- * returns `true`, the condition is considered "resolved" and spinning stops. Otherwise, continue spinning, increasing
- * AJAX timeouts and/or sleep values as configured in the spin_state.
- */
-export function spin(url, bool_response, success_callback, timeout_callback, error_callback, spin_state) {
- var spinner = () => {
- var ajax_params = {
- url: url,
- xhrFields: {
- withCredentials: true,
- },
- type: "GET",
- timeout: spin_state.ajax_timeout,
- success: function (data, status, jqxhr) {
- if (!success_callback(data, status, jqxhr)) {
- spin_state.count++;
- spin_again(spin_state);
- }
- },
- error: function (jqxhr, status, error) {
- if (status == "timeout") {
- if (spin_state.ajax_timeout < spin_state.ajax_timeout_max) {
- spin_state.ajax_timeout += spin_state.ajax_timeout_step;
- }
- spin_state.count++;
- if (!timeout_callback(jqxhr, status, error)) {
- spin_again(spin_state);
- }
- } else {
- spin_state.count++;
- if (!error_callback(jqxhr, status, error)) {
- spin_again(spin_state);
- }
- }
- },
- };
- if (bool_response) {
- ajax_params.dataType = "json";
- }
- $.ajax(ajax_params);
- };
- console.log(`Setting up new spinner for ${spin_state.type} on ${url}`);
- spin_state.spinner = spinner;
- window.setTimeout(spinner, spin_state.sleep);
-}
-
-/*
- * Spin on a URL forever until there is an acceptable response.
- * @param {String} url: URL to test response of. Must return a 200 (302->200 is OK).
- * @param {Boolean} bool_response: If set to `true`, do not stop spinning until the response is `true`. Otherwise, stop
- * as soon as a successful response is received.
- */
-function spin_until(url, bool_response, messages, success_callback, spin_state) {
- var warn_at = 40; // ~2 mins
- var message_once = (message, spin_state) => {
- if (spin_state.count == 1) {
- display_spinner();
- Toast.info(message, null, {
- closeButton: true,
- timeOut: 0,
- extendedTimeOut: 0,
- tapToDismiss: false,
- });
- }
- };
- var wrapped_success = (data) => {
- if (!bool_response || (bool_response && data == true)) {
- console.log(messages.success);
- clear_main_area();
- Toast.clear();
- success_callback();
- } else if (bool_response && data == false) {
- message_once(messages.not_ready, spin_state);
- return false; // keep spinning
- } else {
- spin_error(`Invalid response to ${spin_state.type} request`, messages.invalid_response, true);
- }
- return true; // stop spinning
- };
- var timeout_error = (jqxhr, status, error) => {
- message_once(messages.waiting, spin_state);
- if (spin_state.count == warn_at) {
- Toast.warning(messages.wait_warn, "Warning", {
- closeButton: true,
- timeOut: 0,
- extendedTimeOut: 0,
- tapToDismiss: false,
- });
- }
- return false; // keep spinning
- };
- spin(url, bool_response, wrapped_success, timeout_error, timeout_error, spin_state);
-}
-
-/**
- * Test availability of a URL, and call a callback when done.
- * http://stackoverflow.com/q/25390206/347368
- * @param {String} url: URL to test availability of. Must return a 200 (302->200 is OK).
- * @param {String} callback: function to call once successfully connected.
- *
- */
-export function test_ie_availability(url, success_callback) {
- var messages = {
- success: "IE connection succeeded, returning",
- waiting: "Interactive environment container is running, attempting to connect to the IE. Please wait...",
- wait_warn:
- "It is taking an usually long time to connect to the interactive environment. Attempts will continue but you may want to report this condition to a Galaxy administrator if it does not succeed soon.",
- error: "An error was encountered while attempting to connect to the interactive environment, contact your administrator.",
- };
- var spin_state = make_spin_state("IE availability");
- spin_until(url, false, messages, success_callback, spin_state);
-}
-
-/**
- * Test a boolean (json) response from a URL, and call a callback when done.
- * http://stackoverflow.com/q/25390206/347368
- * @param {String} url: URL to test response of. Must return a 200 (302->200 is OK) and either `true` or `false`.
- * @param {String} callback: function to call once successfully connected.
- *
- */
-export function load_when_ready(url, success_callback) {
- var messages = {
- success: "Galaxy reports IE container ready, returning",
- not_ready: "Galaxy is launching a container in which to run this interactive environment. Please wait...",
- unknown_response:
- "Galaxy failed to launch a container in which to run this interactive environment, contact a Galaxy administrator.",
- waiting: "Galaxy is launching a container in which to run this interactive environment. Please wait...",
- wait_warn:
- "It is taking an usually long time to start a container. Attempts will continue but you may want to report this condition to a Galaxy administrator if it does not succeed soon.",
- error: "Galaxy encountered an error while attempting to determine the readiness of this interactive environment's container, contact a Galaxy administrator.",
- };
- var spin_state = make_spin_state("IE container readiness");
- spin_until(url, true, messages, success_callback, spin_state);
-}
-
-/**
- * Keep the container alive by pinging `notebookAccessURL` every 10 seconds.
- * If the user leaves this site this function is not constantly pinging the
- * container, the container will terminate itself.
- */
-export function keepAlive(notebookAccessURL) {
- var request_count = 0;
- var interval = window.setInterval(function () {
- $.ajax({
- url: notebookAccessURL,
- xhrFields: {
- withCredentials: true,
- },
- type: "GET",
- timeout: 500,
- success: function () {
- console.log("Connected to IE, returning");
- },
- error: function (jqxhr, status, error) {
- request_count++;
- console.log("Request " + request_count);
- if (request_count > 30) {
- window.clearInterval(interval);
- clear_main_area();
- Toast.error("Could not connect to IE, contact your administrator", "Error", {
- closeButton: true,
- timeOut: 20000,
- tapToDismiss: false,
- });
- }
- },
- });
- }, 10000);
-}
-
-export default {
- append_notebook,
- clear_main_area,
- display_spinner,
- make_spin_state,
- spin_error,
- spin,
- test_ie_availability,
- load_when_ready,
- keepAlive,
-};
diff --git a/client/src/layout/menu.js b/client/src/layout/menu.js
index 124ed2a832d..0ed3a76019c 100644
--- a/client/src/layout/menu.js
+++ b/client/src/layout/menu.js
@@ -183,18 +183,6 @@ export function fetchMenu(options = {}) {
target: "_blank",
hidden: !options.support_url,
},
- {
- title: _l("Search"),
- url: options.search_url,
- target: "_blank",
- hidden: !options.search_url,
- },
- {
- title: _l("Mailing Lists"),
- url: options.mailing_lists,
- target: "_blank",
- hidden: !options.mailing_lists,
- },
{
title: _l("Videos"),
url: options.screencasts_url,
@@ -202,7 +190,7 @@ export function fetchMenu(options = {}) {
hidden: !options.screencasts_url,
},
{
- title: _l("Wiki"),
+ title: _l("Community Hub"),
url: options.wiki_url,
target: "_blank",
hidden: !options.wiki_url,
@@ -212,10 +200,6 @@ export function fetchMenu(options = {}) {
url: options.citation_url,
target: "_blank",
},
- {
- title: _l("Interactive Tours"),
- url: "tours",
- },
{
title: _l("Introduction to Galaxy"),
url: "welcome/new",
diff --git a/client/src/layout/scratchbook.js b/client/src/layout/scratchbook.js
index f23cfba0257..bdbc9534b6b 100644
--- a/client/src/layout/scratchbook.js
+++ b/client/src/layout/scratchbook.js
@@ -68,13 +68,13 @@ export default Backbone.View.extend({
let current_dataset = null;
const Galaxy = getGalaxyInstance();
if (Galaxy && Galaxy.currHistoryPanel) {
- const history_id = Galaxy.currHistoryPanel.collection.historyId;
+ const history_id = Galaxy.currHistoryPanel.model.id;
this.history_cache[history_id] = {
name: Galaxy.currHistoryPanel.model.get("name"),
dataset_ids: [],
};
Galaxy.currHistoryPanel.collection.each((model) => {
- if (!model.get("deleted") && model.get("visible")) {
+ if (!model.get("deleted") && model.get("visible") && model.get("history_content_type") == "dataset") {
self.history_cache[history_id].dataset_ids.push(model.get("id"));
}
});
@@ -91,15 +91,13 @@ export default Backbone.View.extend({
}
}
};
- const _loadDatasetOffset = (dataset, offset, frame) => {
+ const _loadOffset = (dataset, offset, frame) => {
const new_dataset_id = _findDataset(dataset, offset);
if (new_dataset_id) {
self._loadDataset(new_dataset_id, (new_dataset, config) => {
current_dataset = new_dataset;
frame.model.set(config);
});
- } else {
- frame.model.trigger("change");
}
};
this._loadDataset(dataset_id, (dataset, config) => {
@@ -112,7 +110,7 @@ export default Backbone.View.extend({
icon: "fa fa-chevron-circle-left",
tooltip: _l("Previous in History"),
onclick: function (frame) {
- _loadDatasetOffset(current_dataset, -1, frame);
+ _loadOffset(current_dataset, -1, frame);
},
disabled: function () {
return !_findDataset(current_dataset, -1);
@@ -122,7 +120,7 @@ export default Backbone.View.extend({
icon: "fa fa-chevron-circle-right",
tooltip: _l("Next in History"),
onclick: function (frame) {
- _loadDatasetOffset(current_dataset, 1, frame);
+ _loadOffset(current_dataset, 1, frame);
},
disabled: function () {
return !_findDataset(current_dataset, 1);
diff --git a/client/src/mvc/annotation.js b/client/src/mvc/annotation.js
index cc94408adb0..7382a2e597c 100644
--- a/client/src/mvc/annotation.js
+++ b/client/src/mvc/annotation.js
@@ -54,7 +54,7 @@ var AnnotationEditor = Backbone.View.extend(baseMVC.LoggableMixin)
_l("Annotation"),
"",
// set up initial tags by adding as CSV to input vals (necc. to init select2)
- '',
+ ' ',
_.escape(annotation),
" ",
].join("");
diff --git a/client/src/mvc/collection/collection-model.js b/client/src/mvc/collection/collection-model.js
index 56135897264..8c70bf2ce79 100644
--- a/client/src/mvc/collection/collection-model.js
+++ b/client/src/mvc/collection/collection-model.js
@@ -252,6 +252,15 @@ var DatasetCollection = Backbone.Model.extend(BASE_MVC.LoggableMixin)
_.extend(element, {
parent_hdca_id: parentHdcaId,
});
+
+ // Warning: MEGA-hack ahead...
+ if (!element.element_type && !element.object) {
+ // The DCE has to be in error state... so we simulate it
+ _.extend(element, {
+ element_type: "hda",
+ object: { state: "error" },
+ });
+ }
});
this.elements = new collectionClass(elements);
//this.debug( 'collectionClass:', this.collectionClass + '', this.elements );
diff --git a/client/src/mvc/collection/collection-view.js b/client/src/mvc/collection/collection-view.js
index 07467adf885..13b10d5e0c1 100644
--- a/client/src/mvc/collection/collection-view.js
+++ b/client/src/mvc/collection/collection-view.js
@@ -62,11 +62,16 @@ var CollectionView = _super.extend(
},
handleWarning: function ($newRender) {
+ var $warns = $newRender.find(".elements-warning");
+ if (this.model.get("populated_state") === "failed") {
+ var error = _l(`${this.model.get("populated_state_message")}`);
+ $warns.html(` ${error} `);
+ return;
+ }
var viewLength = this.views.length;
var elementCount = this.model.get("element_count");
if (elementCount && elementCount !== viewLength) {
var warning = _l(`displaying only ${viewLength} of ${elementCount} items`);
- var $warns = $newRender.find(".elements-warning");
$warns.html(` ${warning} `);
}
},
diff --git a/client/src/mvc/dataset/dataset-li-edit.js b/client/src/mvc/dataset/dataset-li-edit.js
index 12d67112252..7931bddc5da 100644
--- a/client/src/mvc/dataset/dataset-li-edit.js
+++ b/client/src/mvc/dataset/dataset-li-edit.js
@@ -59,7 +59,7 @@ var DatasetListItemEdit = _super.extend(
var editBtnData = {
title: _l("Edit attributes"),
- href: `${getAppRoot()}datasets/edit?dataset_id=${this.model.attributes.id}`,
+ href: `${getAppRoot()}datasets/edit?dataset_id=${this.model.get("element_id") || this.model.get("id")}`,
faIcon: "fa-pencil",
classes: "edit-btn",
onclick: function (ev) {
@@ -226,7 +226,9 @@ var DatasetListItemEdit = _super.extend(
var self = this;
return faIconButton({
title: _l("View or report this error"),
- href: `${getAppRoot()}datasets/error?dataset_id=${this.model.attributes.id}`,
+ href: `${getAppRoot()}datasets/error?dataset_id=${
+ self.model.get("element_id") || self.model.get("id")
+ }`,
classes: "report-error-btn",
faIcon: "fa-bug",
onclick: function (ev) {
@@ -234,7 +236,7 @@ var DatasetListItemEdit = _super.extend(
if (Galaxy.router) {
ev.preventDefault();
Galaxy.router.push("datasets/error", {
- dataset_id: self.model.attributes.id,
+ dataset_id: self.model.get("element_id") || self.model.get("id"),
});
}
},
@@ -266,44 +268,27 @@ var DatasetListItemEdit = _super.extend(
/** Render an icon-button or popupmenu of links based on the applicable visualizations */
_renderVisualizationsButton: function () {
- //TODO: someday - lazyload visualizations
const Galaxy = getGalaxyInstance();
- const visualizations = this.model.get("visualizations");
- if (
- !Galaxy.config.visualizations_visible ||
- this.model.isDeletedOrPurged() ||
- !this.hasUser ||
- !this.model.hasData() ||
- _.isEmpty(visualizations)
- ) {
- return null;
- }
- if (!_.isObject(visualizations[0])) {
- this.warn("Visualizations have been switched off");
- return null;
- }
- if (visualizations.length >= 1) {
- const dsid = this.model.get("id");
- const url = getAppRoot() + "visualizations?dataset_id=" + dsid;
- return faIconButton({
- title: _l("Visualize this data"),
- href: url,
- classes: "visualization-link",
- faIcon: "fa-bar-chart-o",
- onclick: (ev) => {
- if (Galaxy.frame && Galaxy.frame.active) {
- ev.preventDefault();
- Galaxy.frame.add({ url: url, title: "Visualization" });
- } else if (Galaxy.router) {
- ev.preventDefault();
- Galaxy.router.push("visualizations", {
- dataset_id: dsid,
- });
- Galaxy.trigger("activate-hda", dsid);
- }
- },
- });
- }
+ const dsid = this.model.get("element_id") || this.model.get("id");
+ const url = getAppRoot() + "visualizations?dataset_id=" + dsid;
+ return faIconButton({
+ title: _l("Visualize this data"),
+ href: url,
+ classes: "visualization-link",
+ faIcon: "fa-bar-chart-o",
+ onclick: (ev) => {
+ if (Galaxy.frame && Galaxy.frame.active) {
+ ev.preventDefault();
+ Galaxy.frame.add({ url: url, title: "Visualization" });
+ } else if (Galaxy.router) {
+ ev.preventDefault();
+ Galaxy.router.push("visualizations", {
+ dataset_id: dsid,
+ });
+ Galaxy.trigger("activate-hda", dsid);
+ }
+ },
+ });
},
/** Render the tags list/control */
diff --git a/client/src/mvc/dataset/dataset-li.js b/client/src/mvc/dataset/dataset-li.js
index 5a9d8573eab..faf92813c09 100644
--- a/client/src/mvc/dataset/dataset-li.js
+++ b/client/src/mvc/dataset/dataset-li.js
@@ -280,7 +280,7 @@ export var DatasetListItemView = _super.extend(
faIcon: "fa-info-circle",
onclick: (ev) => {
const Galaxy = getGalaxyInstance();
- const showDetailsUrl = `/datasets/${this.model.get("id")}/details`;
+ const showDetailsUrl = `/datasets/${this.model.get("element_id") || this.model.get("id")}/details`;
if (Galaxy.frame && Galaxy.frame.active) {
ev.preventDefault();
Galaxy.frame.add({
@@ -290,7 +290,7 @@ export var DatasetListItemView = _super.extend(
} else if (Galaxy.router) {
ev.preventDefault();
Galaxy.router.push(showDetailsUrl);
- Galaxy.trigger("activate-hda", this.model.get("id"));
+ Galaxy.trigger("activate-hda", this.model.get("element_id") || this.model.get("id"));
}
},
});
diff --git a/client/src/mvc/dataset/dataset-model.js b/client/src/mvc/dataset/dataset-model.js
index bc69f77b972..cb62962d54d 100644
--- a/client/src/mvc/dataset/dataset-model.js
+++ b/client/src/mvc/dataset/dataset-model.js
@@ -74,7 +74,7 @@ var DatasetAssociation = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
purge: `datasets/${id}/purge_async`,
display: `datasets/${id}/display/?preview=True`,
edit: `datasets/edit?dataset_id=${id}`,
- download: `datasets/${id}/display${this._downloadQueryParameters()}`,
+ download: `api/datasets/${id}/display${this._downloadQueryParameters()}`,
report_error: `dataset/errors?id=${id}`,
rerun: `tool_runner/rerun?id=${id}`,
show_params: `datasets/${id}/details`,
@@ -103,19 +103,25 @@ var DatasetAssociation = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
this.trigger("state:ready", currModel, newState, this.previous("state"));
if (newState != "discarded") {
if (newState === "ok") {
- new Notification(`Job complete: ${this.get("name")}`, {
- icon: "static/favicon.ico",
- });
+ // If Notifications are supported, send one.
+ if (window.Notification) {
+ new Notification(`Job complete: ${this.get("name")}`, {
+ icon: "static/favicon.ico",
+ });
+ }
if (TAB_UPDATES.is_hidden()) {
TAB_UPDATES.hidden_count(hiddenupdates);
hiddenupdates++;
}
} else if (newState == "error") {
- new Notification(`Job failure: ${this.get("name")}`, {
- icon: "static/erricon.ico",
- });
- if (TAB_UPDATES.is_hidden() && Notification.permission == "granted") {
- TAB_UPDATES.change_favicon("static/erricon.ico");
+ // If Notifications are supported, send one.
+ if (window.Notification) {
+ new Notification(`Job failure: ${this.get("name")}`, {
+ icon: "static/erricon.ico",
+ });
+ if (TAB_UPDATES.is_hidden() && Notification.permission == "granted") {
+ TAB_UPDATES.change_favicon("static/erricon.ico");
+ }
}
}
}
diff --git a/client/src/mvc/history/history-view.js b/client/src/mvc/history/history-view.js
index 7eae932a382..e566b3d8a67 100644
--- a/client/src/mvc/history/history-view.js
+++ b/client/src/mvc/history/history-view.js
@@ -521,14 +521,14 @@ var HistoryView = _super.extend(
HistoryView.prototype.templates = (() => {
var mainTemplate = () =>
` `;
var controlsTemplate = BASE_MVC.wrapTemplate(
[
- ' ',
+ ' ',
' ',
' <%- history.name %> ',
" ",
diff --git a/client/src/mvc/history/job-states-model.js b/client/src/mvc/history/job-states-model.js
index 167650dd339..c330962eed6 100644
--- a/client/src/mvc/history/job-states-model.js
+++ b/client/src/mvc/history/job-states-model.js
@@ -7,6 +7,7 @@ var UPDATE_DELAY = 2000;
var NON_TERMINAL_STATES = ["new", "queued", "running", "waiting"];
var ERROR_STATES = ["error", "deleted"];
var TERMINAL_STATES = ["ok"].concat(ERROR_STATES);
+const POPULATED_STATE_FAILED = "failed";
/** Fetch state on add or just wait for polling to start. */
var FETCH_STATE_ON_ADD = false;
var BATCH_FETCH_STATE = true;
@@ -26,8 +27,12 @@ var JobStatesSummary = Backbone.Model.extend({
return !this.hasDetails() || this.get("populated_state") == "new";
},
+ populationFailed: function () {
+ return this.get("populated_state") === POPULATED_STATE_FAILED;
+ },
+
errored: function () {
- return this.get("populated_state") === "error" || this.anyWithStates(ERROR_STATES);
+ return this.populationFailed() || this.anyWithStates(ERROR_STATES);
},
states: function () {
diff --git a/client/src/mvc/rules/rule-definitions.js b/client/src/mvc/rules/rule-definitions.js
index dda2879ed47..0a69c965454 100644
--- a/client/src/mvc/rules/rule-definitions.js
+++ b/client/src/mvc/rules/rule-definitions.js
@@ -904,3 +904,5 @@ export default {
RULES: RULES,
MAPPING_TARGETS: MAPPING_TARGETS,
};
+
+export { RULES, MAPPING_TARGETS };
diff --git a/client/src/mvc/ui/popup-menu.js b/client/src/mvc/ui/popup-menu.js
index 0fff0e0a888..6d12989412a 100644
--- a/client/src/mvc/ui/popup-menu.js
+++ b/client/src/mvc/ui/popup-menu.js
@@ -66,7 +66,7 @@ const PopupMenu = Backbone.View.extend({
},
template: function (id, options) {
- return ` |
|
|
|
|