diff --git a/.circleci/config.yml b/.circleci/config.yml index ef892c831e96..833e897606c3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -188,6 +188,7 @@ jobs: - run: cd client && yarn install --frozen-lockfile - *save_yarn_cache - run: cd client && yarn run eslint + - run: cd client && yarn run prettier-check workflows: version: 2 get_code_and_test: diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..36b484bd26aa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +lib/galaxy/datatypes/test/dosimzml eol=crlf diff --git a/Makefile b/Makefile index 830bc896db01..d5906c8e6fa4 100644 --- a/Makefile +++ b/Makefile @@ -168,6 +168,11 @@ client-test: node-deps ## Run JS unit tests via Karma client-eslint: node-deps ## Run client linting cd client && yarn run eslint +client-format-check: node-deps # Run client formatting check + cd client && yarn run prettier-check + +client-lint: client-eslint client-format-check # ES lint and check format of client + client-test-watch: client ## Watch and run qunit tests on changes via Karma cd client && yarn run test-watch diff --git a/client/.eslintrc.js b/client/.eslintrc.js index 7dc407776231..e662c11cf8de 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -7,7 +7,10 @@ module.exports = { node: true, mocha: true }, - parserOptions: { parser: "babel-eslint" }, + parserOptions: { + parser: "babel-eslint", + sourceType: "module" + }, plugins: ["html"], rules: { "no-console": "off", diff --git a/client/galaxy/scripts/app/galaxy.js b/client/galaxy/scripts/app/galaxy.js index b9d547f103a8..7f9bb5dc322a 100644 --- a/client/galaxy/scripts/app/galaxy.js +++ b/client/galaxy/scripts/app/galaxy.js @@ -101,8 +101,8 @@ GalaxyApp.prototype._processOptions = function _processOptions(options) { this.options = {}; for (const k in defaults) { - if (defaults.hasOwnProperty(k)) { - this.options[k] = options.hasOwnProperty(k) ? options[k] : defaults[k]; + if (Object.prototype.hasOwnProperty.call(defaults, k)) { + this.options[k] = Object.prototype.hasOwnProperty.call(options, k) ? options[k] : defaults[k]; } } return this; @@ -126,7 +126,7 @@ GalaxyApp.prototype._patchGalaxy = function _patchGalaxy(patchWith) { // ...(for now) monkey patch any added attributes that the previous Galaxy may have had //TODO: move those attributes to more formal assignment in GalaxyApp for (const k in patchWith) { - if (patchWith.hasOwnProperty(k)) { + if (Object.prototype.hasOwnProperty.call(patchWith, k)) { // this.debug( '\t patching in ' + k + ' to Galaxy:', this[ k ] ); this[k] = patchWith[k]; } diff --git a/client/galaxy/scripts/components/DataDialog/DataDialog.test.js b/client/galaxy/scripts/components/DataDialog/DataDialog.test.js index b8264857e1dc..d60111e2327c 100644 --- a/client/galaxy/scripts/components/DataDialog/DataDialog.test.js +++ b/client/galaxy/scripts/components/DataDialog/DataDialog.test.js @@ -1,4 +1,3 @@ -import sinon from "sinon"; import { mount } from "@vue/test-utils"; import DataDialog from "./DataDialog.vue"; import { __RewireAPI__ as rewire } from "./DataDialog"; @@ -17,7 +16,7 @@ const mockOptions = { describe("model.js", () => { let result = null; it("Model operations for single, no format", () => { - let model = new Model(); + const model = new Model(); try { model.add({ idx: 1 }); throw "Accepted invalid record."; @@ -36,7 +35,7 @@ describe("model.js", () => { expect(result.tag).to.equals("tag"); }); it("Model operations for multiple, with format", () => { - let model = new Model({ multiple: true, format: "tag" }); + const model = new Model({ multiple: true, format: "tag" }); model.add({ id: 1, tag: "tag_1" }); expect(model.count()).to.equals(1); model.add({ id: 2, tag: "tag_2" }); @@ -54,7 +53,7 @@ describe("model.js", () => { describe("utilities.js/UrlTracker", () => { it("Test url tracker", () => { - let urlTracker = new UrlTracker("url_initial"); + const urlTracker = new UrlTracker("url_initial"); let url = urlTracker.getUrl(); expect(url).to.equals("url_initial"); expect(urlTracker.atRoot()).to.equals(true); @@ -75,7 +74,7 @@ describe("utilities.js/UrlTracker", () => { describe("services/Services:isDataset", () => { it("Test dataset identifier", () => { - let services = new Services(mockOptions); + const services = new Services(mockOptions); expect(services.isDataset({})).to.equals(false); expect(services.isDataset({ history_content_type: "dataset" })).to.equals(true); expect(services.isDataset({ history_content_type: "xyz" })).to.equals(false); @@ -88,27 +87,25 @@ describe("services/Services:isDataset", () => { describe("services.js/Services", () => { it("Test data population from raw data", () => { - let rawData = { + const rawData = { hid: 1, id: 1, history_id: 0, name: "name_1" }; - let services = new Services(mockOptions); - let items = services.getItems(rawData); + const services = new Services(mockOptions); + const items = services.getItems(rawData); expect(items.length).to.equals(1); - let first = items[0]; + const first = items[0]; expect(first.label).to.equals("1: name_1"); expect(first.download).to.equals("host/api/histories/0/contents/1/display"); }); }); describe("DataDialog.vue", () => { - let stub; let wrapper; - let emitted; - let rawData = [ + const rawData = [ { id: 1, hid: 1, @@ -128,10 +125,10 @@ describe("DataDialog.vue", () => { } ]; - let mockServices = class { + const mockServices = class { get(url) { - let services = new Services(mockOptions); - let items = services.getItems(rawData); + const services = new Services(mockOptions); + const items = services.getItems(rawData); return new Promise((resolve, reject) => { resolve(items); }); @@ -140,37 +137,32 @@ describe("DataDialog.vue", () => { beforeEach(() => { rewire.__Rewire__("Services", mockServices); - }); - - afterEach(() => { - if (stub) stub.restore(); - }); - - it("loads correctly, shows alert", () => { wrapper = mount(DataDialog, { propsData: mockOptions }); - emitted = wrapper.emitted(); - expect(wrapper.classes()).contain("data-dialog-modal"); - expect(wrapper.find(".fa-spinner").text()).to.equals(""); - expect(wrapper.contains(".fa-spinner")).to.equals(true); - return Vue.nextTick().then(() => { - expect(wrapper.findAll(".fa-folder").length).to.equals(2); - expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + }); + + it("loads correctly, shows alert", () => { + wrapper.vm.$nextTick().then(() => { + expect(wrapper.classes()).contain("data-dialog-modal"); + expect(wrapper.find(".fa-spinner").text()).to.equals(""); + expect(wrapper.contains(".fa-spinner")).to.equals(true); + return Vue.nextTick().then(() => { + expect(wrapper.findAll(".fa-folder").length).to.equals(2); + expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + }); }); }); it("loads correctly, shows datasets and folders", () => { - wrapper = mount(DataDialog, { - propsData: mockOptions - }); - emitted = wrapper.emitted(); - expect(wrapper.classes()).contain("data-dialog-modal"); - expect(wrapper.find(".fa-spinner").text()).to.equals(""); - expect(wrapper.contains(".fa-spinner")).to.equals(true); - return Vue.nextTick().then(() => { - expect(wrapper.findAll(".fa-folder").length).to.equals(2); - expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + wrapper.vm.$nextTick().then(() => { + expect(wrapper.classes()).contain("data-dialog-modal"); + expect(wrapper.find(".fa-spinner").text()).to.equals(""); + expect(wrapper.contains(".fa-spinner")).to.equals(true); + return Vue.nextTick().then(() => { + expect(wrapper.findAll(".fa-folder").length).to.equals(2); + expect(wrapper.findAll(".fa-file-o").length).to.equals(2); + }); }); }); }); diff --git a/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue b/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue index 3542664d104b..1dc33ec7fd3c 100644 --- a/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue +++ b/client/galaxy/scripts/components/DataDialog/DataDialogTable.vue @@ -57,17 +57,20 @@ export default { data() { return { currentPage: 1, - fields: { - label: { + fields: [ + { + key: "label", sortable: true }, - details: { + { + key: "details", sortable: true }, - time: { + { + key: "time", sortable: true } - }, + ], nItems: 0, perPage: 100 }; diff --git a/client/galaxy/scripts/components/JobStates/CollectionJobStates.vue b/client/galaxy/scripts/components/JobStates/CollectionJobStates.vue new file mode 100644 index 000000000000..cd5fa7146677 --- /dev/null +++ b/client/galaxy/scripts/components/JobStates/CollectionJobStates.vue @@ -0,0 +1,60 @@ + + diff --git a/client/galaxy/scripts/components/JobStates/index.js b/client/galaxy/scripts/components/JobStates/index.js new file mode 100644 index 000000000000..900901035365 --- /dev/null +++ b/client/galaxy/scripts/components/JobStates/index.js @@ -0,0 +1,4 @@ +import CollectionJobStates from "./CollectionJobStates"; +import { mountVueComponent } from "utils/mountVueComponent"; + +export const mountCollectionJobStates = mountVueComponent(CollectionJobStates); diff --git a/client/galaxy/scripts/components/JobStates/mixin.js b/client/galaxy/scripts/components/JobStates/mixin.js new file mode 100644 index 000000000000..a350c9fdcd18 --- /dev/null +++ b/client/galaxy/scripts/components/JobStates/mixin.js @@ -0,0 +1,53 @@ +/* VueJS mixin with computed properties from a base jobStatesSummary property for summarizing job states */ +export default { + computed: { + isNew() { + return !this.jobStatesSummary || this.jobStatesSummary.new(); + }, + isErrored() { + return this.jobStatesSummary && this.jobStatesSummary.errored(); + }, + isTerminal() { + return this.jobStatesSummary && this.jobStatesSummary.terminal(); + }, + jobCount() { + return !this.jobStatesSummary ? null : this.jobStatesSummary.jobCount(); + }, + jobsStr() { + const jobCount = this.jobCount; + return jobCount && jobCount > 1 ? `${jobCount} jobs` : `a job`; + }, + runningCount() { + return this.countStates(["running"]); + }, + okCount() { + return this.countStates(["ok"]); + }, + errorCount() { + return this.countStates(["error"]); + }, + runningPercent() { + return this.runningCount / (this.jobCount * 1.0); + }, + okPercent() { + return this.okCount / (this.jobCount * 1.0); + }, + errorPercent() { + return this.errorCount / (this.jobCount * 1.0); + }, + otherPercent() { + return 1.0 - this.okPercent - this.runningPercent - this.errorPercent; + } + }, + methods: { + countStates(states) { + let count = 0; + if (this.jobStatesSummary && this.jobStatesSummary.hasDetails()) { + for (const state of states) { + count += this.jobStatesSummary.states()[state] || 0; + } + } + return count; + } + } +}; diff --git a/client/galaxy/scripts/components/MaskedInput.js b/client/galaxy/scripts/components/MaskedInput.js index 7cac27045eba..f838bb22cf85 100644 --- a/client/galaxy/scripts/components/MaskedInput.js +++ b/client/galaxy/scripts/components/MaskedInput.js @@ -1,8 +1,8 @@ -import BInput from "bootstrap-vue/es/components/form-input/form-input"; +import { BFormInput } from "bootstrap-vue/src/components/form-input"; import { createMask } from "imask"; export default { - extends: BInput, + extends: BFormInput, props: { mask: { type: String, diff --git a/client/galaxy/scripts/components/ProgressBar.vue b/client/galaxy/scripts/components/ProgressBar.vue new file mode 100644 index 000000000000..40a05bf73d80 --- /dev/null +++ b/client/galaxy/scripts/components/ProgressBar.vue @@ -0,0 +1,40 @@ + + diff --git a/client/galaxy/scripts/components/Sharing.vue b/client/galaxy/scripts/components/Sharing.vue index 84ffbde9e3c8..c7f75a77e56f 100644 --- a/client/galaxy/scripts/components/Sharing.vue +++ b/client/galaxy/scripts/components/Sharing.vue @@ -116,7 +116,7 @@ The following users will see this {{ model_class_lc }} in their {{ model_class_lc }} list and will be able to view, import and run it. -