mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 02:04:04 +08:00
Merge branch 'dev' into list_icons
This commit is contained in:
@@ -35,10 +35,11 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
path: 'galaxy root'
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Cache venv dir
|
||||
- name: Cache pip dir
|
||||
uses: actions/cache@v1
|
||||
id: pip-cache
|
||||
with:
|
||||
|
||||
+75
-66
@@ -2,10 +2,7 @@
|
||||
|
||||
Galaxy welcomes new development! This document briefly describes how to
|
||||
contribute to the [core galaxy
|
||||
project](https://github.com/galaxyproject/galaxy) - also checkout our 2013
|
||||
Galaxy Community Conference presentation on the topic
|
||||
([video](https://vimeo.com/channels/581875/73486255),
|
||||
[presentation](https://depot.galaxyproject.org/hub/attachments/documents/presentations/gcc2013/BakerContribute.pdf)).
|
||||
repository](https://github.com/galaxyproject/galaxy).
|
||||
For information on contributing more broadly to the Galaxy ecosystem and a
|
||||
deeper discussion of some of these points - please see the
|
||||
[Develop](https://galaxyproject.org/develop) section of the [Galaxy Community
|
||||
@@ -18,12 +15,11 @@ best to communicate with Galaxy developers early. The primary venue for this is
|
||||
the [GitHub issue tracker](https://github.com/galaxyproject/galaxy/issues).
|
||||
Browse through existing GitHub issues and if one seems related, comment on it.
|
||||
For more direct communication, Galaxy developers are generally available via
|
||||
[IRC](https://galaxyproject.org/get-involved),
|
||||
[Gitter](https://gitter.im/galaxyproject/Lobby) and on the [development mailing
|
||||
[Gitter](https://gitter.im/galaxyproject/dev) and on the [development mailing
|
||||
list](http://dev.list.galaxyproject.org).
|
||||
|
||||
If you're looking to help but aren't sure where to start, we also maintain a
|
||||
[tag](https://github.com/galaxyproject/galaxy/issues?q=is%3Aissue+is%3Aopen+label%3Afriendliness%2Ffriendly)
|
||||
[tag](https://github.com/galaxyproject/galaxy/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
|
||||
on GitHub for smaller issues we believe would make the best entry points for
|
||||
new developers.
|
||||
|
||||
@@ -34,83 +30,96 @@ If no existing Galaxy issue seems appropriate, a new issue can be opened using
|
||||
|
||||
## How to Contribute
|
||||
|
||||
* All changes to the [core galaxy
|
||||
project](https://github.com/galaxyproject/galaxy) should be made through pull
|
||||
requests to this repository (with just two exceptions outlined below).
|
||||
All changes to the [core galaxy
|
||||
repository](https://github.com/galaxyproject/galaxy) should be made through pull
|
||||
requests (with just two exceptions outlined below).
|
||||
|
||||
* If you are new to Git, the [Try Git](http://try.github.com/) tutorial is a
|
||||
good places to start. More learning resources are listed at
|
||||
https://help.github.com/articles/good-resources-for-learning-git-and-github/
|
||||
If you are new to Git, the Software Carpentry's [Version Control with
|
||||
Git](https://swcarpentry.github.io/git-novice/) tutorial is a good place to
|
||||
start. More learning resources are listed at
|
||||
https://help.github.com/en/github/getting-started-with-github/git-and-github-learning-resources
|
||||
|
||||
* Make sure you have a free [GitHub](https://github.com/) account.
|
||||
1. Make sure you have a free [GitHub](https://github.com/) account.
|
||||
|
||||
* Fork the [galaxy repository](https://github.com/galaxyproject/galaxy) on
|
||||
GitHub to make your changes. To keep your copy up to date with respect to
|
||||
the main repository, you need to frequently [sync your
|
||||
fork](https://help.github.com/articles/syncing-a-fork/):
|
||||
```
|
||||
$ git remote add upstream https://github.com/galaxyproject/galaxy
|
||||
$ git fetch upstream
|
||||
$ git checkout dev
|
||||
$ git merge upstream/dev
|
||||
```
|
||||
2. Fork the [galaxy repository](https://github.com/galaxyproject/galaxy) on
|
||||
GitHub to make your changes. To keep your copy up to date with respect to
|
||||
the main repository, you need to frequently [sync your
|
||||
fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork):
|
||||
|
||||
* Choose the correct branch to develop your changes against.
|
||||
```
|
||||
$ git remote add upstream https://github.com/galaxyproject/galaxy
|
||||
$ git fetch upstream
|
||||
$ git checkout dev
|
||||
$ git merge upstream/dev
|
||||
```
|
||||
|
||||
* Additions of new features to the code base should be pushed to the `dev`
|
||||
branch (`git checkout dev`).
|
||||
3. Choose the correct branch to develop your changes against.
|
||||
|
||||
* Most bug fixes to previously release components (things in galaxy-dist)
|
||||
should be made against the recent `release_XX.XX` branch (`git checkout
|
||||
release_XX.XX`).
|
||||
* Additions of new features to the codebase should be pushed to the `dev`
|
||||
branch (`git checkout dev`).
|
||||
|
||||
* Serious security problems should not be fixed via pull request - please see
|
||||
[the Galaxy security policies](SECURITY_POLICY.md) for information about
|
||||
responsibly disclosing security issues.
|
||||
* Most bug fixes to previous (or frozen) releases should be made against the
|
||||
corrisponding `release_XX.XX` branch (`git checkout release_XX.XX`).
|
||||
|
||||
* If your changes modify code please ensure the resulting files conform to
|
||||
Galaxy [style guidelines](https://galaxyproject.org/develop/best-practices).
|
||||
* Serious security problems should not be fixed via pull request - please see
|
||||
[the Galaxy security policies](SECURITY_POLICY.md) for information about
|
||||
responsibly disclosing security issues.
|
||||
|
||||
* If you are working on the Galaxy Client (the user interface -- JavaScript,
|
||||
styles, etc.), see more information in the [client readme](client/README.md).
|
||||
4. If your changes modify code please ensure the resulting files conform to
|
||||
the [style guidelines](#style-guidelines) below.
|
||||
|
||||
* Galaxy contains hundreds of tests of different types and complexity and
|
||||
running each is difficult and probably not reasonable at this time (someday
|
||||
we will provide a holistic test procedure to make this possible). For now,
|
||||
please just review the [running tests
|
||||
documentation](https://galaxyproject.org/admin/running-tests) and run any
|
||||
that seem relevant. Developers reviewing your pull request will be happy to
|
||||
help guide you to running the most relevant tests as part of the pull request
|
||||
review process and may request the output of these tests. You can run the
|
||||
continuous integration tests locally using `tox`, example: `tox -e
|
||||
py27-lint,py27-unit`.
|
||||
If you are working on the Galaxy user interface (i.e. JavaScript,
|
||||
styles, etc.), see more information in the [client README](client/README.md).
|
||||
|
||||
* Commit and push your changes to your
|
||||
[fork](https://help.github.com/articles/pushing-to-a-remote/).
|
||||
5. Galaxy contains hundreds of tests of different types and complexity and
|
||||
running each is difficult and probably not reasonable on your workstation. So
|
||||
please review the [running tests documentation](test/TESTING.md) and run any
|
||||
that seem relevant.
|
||||
|
||||
* Open a [pull
|
||||
request](https://help.github.com/articles/creating-a-pull-request/) with
|
||||
these changes. You pull request message ideally should include:
|
||||
If possible, also try to add new tests for the features added or bugs fixed
|
||||
by your pull request.
|
||||
|
||||
* A description of why the changes should be made.
|
||||
Developers reviewing your pull request will be happy to help you add or run
|
||||
the relevant tests as part of the pull request review process.
|
||||
|
||||
6. Commit and push your changes to your
|
||||
[fork](https://help.github.com/en/github/using-git/pushing-commits-to-a-remote-repository).
|
||||
|
||||
7. Open a [pull
|
||||
request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
|
||||
with these changes. Your pull request message ideally should include:
|
||||
|
||||
* Why you made the changes (e.g. references to GitHub issues being fixed).
|
||||
|
||||
* A description of the implementation of the changes.
|
||||
|
||||
* A description of how to test the changes.
|
||||
* How to test the changes, if you haven't included specific tests already.
|
||||
|
||||
* The pull request should pass all the continuous integration tests which are
|
||||
automatically run by GitHub using e.g. Travis CI.
|
||||
8. The pull request should pass all the continuous integration tests which are
|
||||
automatically started by GitHub using e.g. Travis CI.
|
||||
|
||||
* Your pull request will be handled according to [some
|
||||
rules](doc/source/project/organization.rst#handling-pull-requests).
|
||||
9. Your pull request will be handled according to [some
|
||||
rules](doc/source/project/organization.rst#handling-pull-requests).
|
||||
|
||||
## Style guidelines
|
||||
|
||||
### Python
|
||||
|
||||
- Galaxy follows [PEP-8](https://www.python.org/dev/peps/pep-0008/), with
|
||||
particular emphasis on readability being the ultimate goal:
|
||||
- 4 spaces (not tabs!) per indentation level
|
||||
- divergences from PEP-8 are listed in the `[flake8]` section of the `setup.cfg`
|
||||
file.
|
||||
- Python imports should be ordered following the
|
||||
[smarkets](https://github.com/PyCQA/flake8-import-order/blob/master/tests/test_cases/complete_smarkets.py)
|
||||
style.
|
||||
- Python [docstrings](http://www.python.org/dev/peps/pep-0257/) need to be in
|
||||
[reStructured Text (RST)](https://docutils.sourceforge.io/rst.html) format and
|
||||
compatible with [Sphinx](https://www.sphinx-doc.org).
|
||||
|
||||
## A Quick Note about Tools
|
||||
|
||||
For the most part, Galaxy tools should be published to the [Tool
|
||||
Shed](https://galaxyproject.org/toolshed) and not in this repository
|
||||
directly. If you are looking for migrated core tools that used to exist in
|
||||
this repository - please checkout the
|
||||
[tools-iuc](https://github.com/galaxyproject/tools-iuc) repository on GitHub.
|
||||
|
||||
More information about tool development can be found [on the community
|
||||
hub](https://galaxyproject.org/develop).
|
||||
For the most part, Galaxy tools should be published to a [Tool
|
||||
Shed](https://galaxyproject.org/toolshed) and not in this repository directly.
|
||||
More information about tool development can be found [on the community
|
||||
hub](https://galaxyproject.org/develop).
|
||||
|
||||
+20
-8
@@ -1,5 +1,9 @@
|
||||
module.exports = {
|
||||
extends: ["eslint:recommended", "plugin:vue/essential"], // airbnb-base, eventually
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/strongly-recommended",
|
||||
//"airbnb-base", eventually
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
@@ -13,16 +17,24 @@ module.exports = {
|
||||
},
|
||||
plugins: ["html"],
|
||||
rules: {
|
||||
// Standard rules
|
||||
"no-console": "off",
|
||||
"no-unused-vars": ["error", { args: "none" }],
|
||||
"prefer-const": "error",
|
||||
// I'd love to turn on camelcase, but it's a big shift with tons of current errors.
|
||||
// camelcase: [
|
||||
// "error",
|
||||
// {
|
||||
// properties: "always"
|
||||
// }
|
||||
// ]
|
||||
|
||||
// Vue TODO (enable these)
|
||||
"vue/require-default-prop": "off",
|
||||
"vue/require-prop-types": "off",
|
||||
"vue/prop-name-casing": "off",
|
||||
|
||||
// Prettier compromises/workarounds -- mostly #wontfix?
|
||||
"vue/html-indent": "off",
|
||||
"vue/max-attributes-per-line": "off",
|
||||
"vue/html-self-closing": "off",
|
||||
"vue/singleline-html-element-content-newline": "off",
|
||||
"vue/multiline-html-element-content-newline": "off",
|
||||
"vue/html-closing-bracket-newline": "off",
|
||||
"vue/html-closing-bracket-spacing": "off"
|
||||
},
|
||||
globals: {
|
||||
// chai tests
|
||||
|
||||
+9
-10
@@ -73,10 +73,9 @@ Even Better Automatic Rebuilding (HMR)
|
||||
======================================
|
||||
|
||||
For even more rapid development you can use the webpack development server for
|
||||
HMR. This technique allows swapping out of modules while the application is
|
||||
running without requiring a full page reload most of the time. Upon editing
|
||||
and saving client files webpack will rebuild the necessary modules and then
|
||||
notify your web to update the page with your new code.
|
||||
Hot Module Replacement (HMR). This technique allows swapping out of modules
|
||||
while the application is running without requiring a full page reload most of
|
||||
the time.
|
||||
|
||||
Setting this up is a little more involved, but it is the fastest possible way
|
||||
to iterate when developing the client. You'll need to start two separate
|
||||
@@ -151,26 +150,26 @@ directory. This is what happens during a complete client build.
|
||||
|
||||
During client-side development, it is more convenient to have granular testing
|
||||
options. The various testing scripts are defined inside package.json within the
|
||||
client folder, and are called either with npm (or yarn) as follows:
|
||||
|
||||
client folder, and are called either with `yarn` as demonstrated in the
|
||||
following commands.
|
||||
|
||||
#### Run all tests (mocha and qunit):
|
||||
|
||||
npm run test
|
||||
yarn run test
|
||||
|
||||
#### Watch and rerun all client-side unit-tests every time a source file changes:
|
||||
|
||||
npm run test-watch
|
||||
yarn run test-watch
|
||||
|
||||
#### Run only specified test files when a source file changes:
|
||||
|
||||
npm run test-watch watch-only="Tags/*.test.js"
|
||||
yarn run test-watch watch-only="Tags/*.test.js"
|
||||
|
||||
(The above watch-only parameter will be mapped to the following glob expression "**/Tags/*.test.js")
|
||||
|
||||
#### Run qunit legacy tests
|
||||
|
||||
npm run test-qunit
|
||||
yarn run test-qunit
|
||||
|
||||
|
||||
## Writing a test file
|
||||
|
||||
@@ -7,7 +7,7 @@ import metricsLogger from "utils/metrics-logger";
|
||||
import addLogging from "utils/add-logging";
|
||||
import localize from "utils/localization";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { create, dialog } from "utils/data.js";
|
||||
import { create, dialog } from "utils/data";
|
||||
|
||||
// ============================================================================
|
||||
/** Base galaxy client-side application.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import DataDialog from "./DataDialog.vue";
|
||||
import SelectionDialog from "components/SelectionDialog/SelectionDialog.vue";
|
||||
import { __RewireAPI__ as rewire } from "./DataDialog";
|
||||
import { Model } from "./model.js";
|
||||
import { UrlTracker } from "./utilities.js";
|
||||
import { Model } from "./model";
|
||||
import { UrlTracker } from "./utilities";
|
||||
import { Services } from "./services";
|
||||
import { mount, createLocalVue } from "@vue/test-utils";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<selection-dialog
|
||||
:errorMessage="errorMessage"
|
||||
:optionsShow="optionsShow"
|
||||
:modalShow="modalShow"
|
||||
:hideModal="() => (modalShow = false)"
|
||||
:error-message="errorMessage"
|
||||
:options-show="optionsShow"
|
||||
:modal-show="modalShow"
|
||||
:hide-modal="() => (modalShow = false)"
|
||||
>
|
||||
<template v-slot:search>
|
||||
<data-dialog-search v-model="filter" />
|
||||
@@ -41,9 +41,9 @@
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import SelectionDialogMixin from "components/SelectionDialog/SelectionDialogMixin";
|
||||
import { UrlTracker } from "./utilities.js";
|
||||
import { Model } from "./model.js";
|
||||
import { Services } from "./services.js";
|
||||
import { UrlTracker } from "./utilities";
|
||||
import { Model } from "./model";
|
||||
import { Services } from "./services";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { Services } from "./services.js";
|
||||
import { Services } from "./services";
|
||||
import DatasetName from "./DatasetName";
|
||||
import DatasetHistory from "./DatasetHistory";
|
||||
import DelayedInput from "components/Common/DelayedInput";
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
<button id="import" class="btn btn-secondary">Import and start using history</button>
|
||||
</span>
|
||||
<span v-if="historyData.user_is_owner && historyData.history_is_current == false">
|
||||
<button id="switch-history" class="btn btn-secondary" v-on:click="switchHistory">
|
||||
<button id="switch-history" class="btn btn-secondary" @click="switchHistory">
|
||||
Switch to this history
|
||||
</button>
|
||||
</span>
|
||||
<button id="show-structure" class="btn btn-secondary" v-on:click="showStructure">
|
||||
<button id="show-structure" class="btn btn-secondary" @click="showStructure">
|
||||
Show structure
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { mount, createLocalVue } from "@vue/test-utils";
|
||||
import { createStore } from "../../store";
|
||||
import flushPromises from "flush-promises";
|
||||
import JobMetrics from "./JobMetrics";
|
||||
|
||||
const JOB_ID = "moo";
|
||||
@@ -50,10 +51,8 @@ describe("JobMetrics/JobMetrics.vue", () => {
|
||||
propsData,
|
||||
localVue
|
||||
});
|
||||
// One tick for axios request.
|
||||
await wrapper.vm.$nextTick();
|
||||
// One tick for Vue to render.
|
||||
await wrapper.vm.$nextTick();
|
||||
// Wait for axios and rendering.
|
||||
await flushPromises();
|
||||
expect(wrapper.vm.jobMetrics.length).to.equals(3);
|
||||
expect(wrapper.vm.jobId).to.equals(JOB_ID);
|
||||
expect(wrapper.vm.metricsByPlugins.core.runtime).to.equals(145);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="orderedPlugins.length > 0">
|
||||
<h3 v-if="includeTitle">Job Metrics</h3>
|
||||
<div v-for="plugin in orderedPlugins" v-bind:key="plugin" class="metrics_plugin">
|
||||
<div v-for="plugin in orderedPlugins" :key="plugin" class="metrics_plugin">
|
||||
<h4 class="metrics_plugin_title">{{ plugin }}</h4>
|
||||
<table class="tabletip info_data_table">
|
||||
<tbody>
|
||||
<tr v-for="(metricValue, metricTitle) in metricsByPlugins[plugin]" v-bind:key="metricTitle">
|
||||
<tr v-for="(metricValue, metricTitle) in metricsByPlugins[plugin]" :key="metricTitle">
|
||||
<td>{{ metricTitle }}</td>
|
||||
<td>{{ metricValue }}</td>
|
||||
</tr>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(parameter, index) in parameters" v-bind:key="index">
|
||||
<td v-bind:style="{ 'padding-left': `${(parameter.depth - 1) * 10}px` }">
|
||||
<tr v-for="(parameter, pIndex) in parameters" :key="pIndex">
|
||||
<td :style="{ 'padding-left': `${(parameter.depth - 1) * 10}px` }">
|
||||
{{ parameter.text }}
|
||||
</td>
|
||||
<td v-if="Array.isArray(parameter.value)">
|
||||
<ul style="padding-inline-start: 25px;">
|
||||
<li v-for="(elVal, index) in parameter.value" v-bind:key="index">
|
||||
<li v-for="(elVal, pvIndex) in parameter.value" :key="pvIndex">
|
||||
<span v-if="elVal.src == 'hda'">
|
||||
<a :href="appRoot() + 'datasets/' + elVal.id + '/show_params'">
|
||||
{{ elVal.hid }}: {{ elVal.name }}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
{{ simpleDescription }}
|
||||
</div>
|
||||
<div v-else-if="!jobStatesSummary || !jobStatesSummary.hasDetails()">
|
||||
<progress-bar :note="loadingNote" :loading="true" :infoProgress="1" />
|
||||
<progress-bar :note="loadingNote" :loading="true" :info-progress="1" />
|
||||
</div>
|
||||
<div v-else-if="isNew">
|
||||
<progress-bar note="Creating jobs" :loading="true" :infoProgress="1" />
|
||||
<progress-bar note="Creating jobs" :loading="true" :info-progress="1" />
|
||||
</div>
|
||||
<div v-else-if="isErrored">
|
||||
{{ errorDescription }}
|
||||
@@ -14,9 +14,9 @@
|
||||
<div v-else>
|
||||
<progress-bar
|
||||
:note="generatingNote"
|
||||
:okProgress="okPercent"
|
||||
:runningProgress="runningPercent"
|
||||
:newProgress="otherPercent"
|
||||
:ok-progress="okPercent"
|
||||
:running-progress="runningPercent"
|
||||
:new-progress="otherPercent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span>
|
||||
<span v-bind:class="spinnerClasses"></span>
|
||||
<span :class="spinnerClasses"></span>
|
||||
<span class="loading-message">{{ message }}.<span class="blinking">..</span></span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<markdown :markdown-config="markdownConfig" :exportLink="exportUrl"> </markdown>
|
||||
<markdown :markdown-config="markdownConfig" :export-link="exportUrl"> </markdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:onupdate="onUpdate"
|
||||
ref="markdownEditor"
|
||||
:toolbar="true"
|
||||
:showMarkdownHelp="showMarkdownHelp"
|
||||
:show-markdown-help="showMarkdownHelp"
|
||||
>
|
||||
</markdown-editor>
|
||||
</div>
|
||||
|
||||
@@ -55,9 +55,9 @@ export default {
|
||||
created() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
|
||||
Galaxy.upload.ui_button.model.on("change", () => {
|
||||
this.status = Galaxy.upload.ui_button.model.attributes.status;
|
||||
this.percentage = Galaxy.upload.ui_button.model.attributes.percentage;
|
||||
Galaxy.upload.model.on("change", () => {
|
||||
this.status = Galaxy.upload.model.attributes.status;
|
||||
this.percentage = Galaxy.upload.model.attributes.percentage;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("ToolSection", () => {
|
||||
expect(wrapper.findAll(".name").length).to.equal(1);
|
||||
});
|
||||
|
||||
it("test tool slider state", () => {
|
||||
it("test tool slider state", async () => {
|
||||
const wrapper = mount(ToolSection, {
|
||||
propsData: {
|
||||
category: {
|
||||
@@ -65,20 +65,34 @@ describe("ToolSection", () => {
|
||||
}
|
||||
});
|
||||
expect(wrapper.vm.opened).to.equal(true);
|
||||
|
||||
const $sectionName = wrapper.find(".name");
|
||||
$sectionName.trigger("click");
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(false);
|
||||
|
||||
wrapper.vm.queryFilter = "";
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(false);
|
||||
|
||||
wrapper.vm.queryFilter = "test";
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(true);
|
||||
|
||||
wrapper.vm.disableFilter = true;
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(true);
|
||||
|
||||
wrapper.vm.queryFilter = "";
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(false);
|
||||
|
||||
$sectionName.trigger("click");
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(true);
|
||||
|
||||
wrapper.vm.queryFilter = "test";
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.vm.opened).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
class="ml-2"
|
||||
:tool="el"
|
||||
:key="key"
|
||||
:toolKey="toolKey"
|
||||
:hideName="hideName"
|
||||
:operationTitle="operationTitle"
|
||||
:operationIcon="operationIcon"
|
||||
:tool-key="toolKey"
|
||||
:hide-name="hideName"
|
||||
:operation-title="operationTitle"
|
||||
:operation-icon="operationIcon"
|
||||
@onOperation="onOperation"
|
||||
@onClick="onClick"
|
||||
/>
|
||||
@@ -39,9 +39,9 @@
|
||||
<tool
|
||||
v-else
|
||||
:tool="category"
|
||||
:hideName="hideName"
|
||||
:operationTitle="operationTitle"
|
||||
:operationIcon="operationIcon"
|
||||
:hide-name="hideName"
|
||||
:operation-title="operationTitle"
|
||||
:operation-icon="operationIcon"
|
||||
@onOperation="onOperation"
|
||||
@onClick="onClick"
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<tool-section
|
||||
v-for="category in categories"
|
||||
:category="category"
|
||||
:queryFilter="query"
|
||||
:query-filter="query"
|
||||
:key="category.id"
|
||||
@onClick="onOpen"
|
||||
/>
|
||||
@@ -28,9 +28,9 @@
|
||||
</div>
|
||||
<div id="internal-workflows" class="toolSectionBody">
|
||||
<div class="toolSectionBg" />
|
||||
<div class="toolTitle" v-for="workflow in this.workflows" :key="workflow.id">
|
||||
<a :href="workflow.href">
|
||||
{{ workflow.title }}
|
||||
<div class="toolTitle" v-for="wf in this.workflows" :key="wf.id">
|
||||
<a :href="wf.href">
|
||||
{{ wf.title }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@ import ToolSection from "./Common/ToolSection";
|
||||
import ToolSearch from "./Common/ToolSearch";
|
||||
import UploadButton from "./Buttons/UploadButton";
|
||||
import FavoritesButton from "./Buttons/FavoritesButton";
|
||||
import { filterToolSections } from "./utilities.js";
|
||||
import { filterToolSections } from "./utilities";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { getAppRoot } from "onload";
|
||||
import _l from "utils/localization";
|
||||
|
||||
@@ -12,27 +12,27 @@
|
||||
<div class="toolMenuContainer">
|
||||
<tool-section
|
||||
v-for="category in moduleSections"
|
||||
:hideName="true"
|
||||
:hide-name="true"
|
||||
:category="category"
|
||||
toolKey="name"
|
||||
:sectionName="category.name"
|
||||
:queryFilter="query"
|
||||
:disableFilter="true"
|
||||
tool-key="name"
|
||||
:section-name="category.name"
|
||||
:query-filter="query"
|
||||
:disable-filter="true"
|
||||
:key="category.name"
|
||||
@onClick="onInsertModule"
|
||||
/>
|
||||
<tool-section
|
||||
:category="dataManagers"
|
||||
:key="dataManagers.id"
|
||||
:queryFilter="query"
|
||||
:disableFilter="true"
|
||||
:query-filter="query"
|
||||
:disable-filter="true"
|
||||
@onClick="onInsertTool"
|
||||
/>
|
||||
<div class="toolMenu" id="workflow-tool-menu">
|
||||
<tool-section
|
||||
v-for="category in categories"
|
||||
:category="category"
|
||||
:queryFilter="query"
|
||||
:query-filter="query"
|
||||
:key="category.id"
|
||||
@onClick="onInsertTool"
|
||||
/>
|
||||
@@ -40,10 +40,10 @@
|
||||
<tool-section
|
||||
:category="workflowSection"
|
||||
:key="workflowSection.name"
|
||||
operationIcon="fa fa-copy"
|
||||
operationTitle="Insert individual steps."
|
||||
:queryFilter="query"
|
||||
:disableFilter="true"
|
||||
operation-icon="fa fa-copy"
|
||||
operation-title="Insert individual steps."
|
||||
:query-filter="query"
|
||||
:disable-filter="true"
|
||||
@onClick="onInsertWorkflow"
|
||||
@onOperation="onInsertWorkflowSteps"
|
||||
/>
|
||||
@@ -55,7 +55,7 @@
|
||||
<script>
|
||||
import ToolSection from "./Common/ToolSection";
|
||||
import ToolSearch from "./Common/ToolSearch";
|
||||
import { filterToolSections } from "./utilities.js";
|
||||
import { filterToolSections } from "./utilities";
|
||||
|
||||
export default {
|
||||
name: "ToolBox",
|
||||
|
||||
@@ -3,15 +3,11 @@
|
||||
<span class="note" v-if="note">
|
||||
{{ note }}<span v-if="loading">.<span class="blinking">..</span> </span>
|
||||
</span>
|
||||
<div class="progress-bar info" v-bind:style="styleFromProgress(infoProgress)" :title="infoMessage"></div>
|
||||
<div class="progress-bar ok" v-bind:style="styleFromProgress(okProgress)" :title="okMessage"></div>
|
||||
<div
|
||||
class="progress-bar running"
|
||||
v-bind:style="styleFromProgress(runningProgress)"
|
||||
:title="runningMessage"
|
||||
></div>
|
||||
<div class="progress-bar new" v-bind:style="styleFromProgress(newProgress)" :title="newMessage"></div>
|
||||
<div class="progress-bar error" v-bind:style="styleFromProgress(errorProgress)" :title="errorMessage"></div>
|
||||
<div class="progress-bar info" :style="styleFromProgress(infoProgress)" :title="infoMessage"></div>
|
||||
<div class="progress-bar ok" :style="styleFromProgress(okProgress)" :title="okMessage"></div>
|
||||
<div class="progress-bar running" :style="styleFromProgress(runningProgress)" :title="runningMessage"></div>
|
||||
<div class="progress-bar new" :style="styleFromProgress(newProgress)" :title="newMessage"></div>
|
||||
<div class="progress-bar error" :style="styleFromProgress(errorProgress)" :title="errorMessage"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
<!-- column-headers -->
|
||||
<div
|
||||
class="rule-builder-body vertically-spaced"
|
||||
v-bind:class="{ 'flex-column-container': vertical }"
|
||||
:class="{ 'flex-column-container': vertical }"
|
||||
v-if="ruleView == 'normal'"
|
||||
>
|
||||
<!-- width: 30%; -->
|
||||
<div class="rule-column" v-bind:class="orientation">
|
||||
<div class="rule-column" :class="orientation">
|
||||
<div
|
||||
class="rules-container"
|
||||
v-bind:class="{
|
||||
:class="{
|
||||
'rules-container-vertical': initialElements && vertical,
|
||||
'rules-container-horizontal': initialElements && horizontal,
|
||||
'rules-container-full': initialElements == null
|
||||
@@ -267,7 +267,7 @@
|
||||
</label>
|
||||
</rule-component>
|
||||
<div v-if="displayRuleType == 'mapping'">
|
||||
<div class="map" v-for="(map, index) in mapping" v-bind:index="index" v-bind:key="map.type">
|
||||
<div class="map" v-for="(map, index) in mapping" :index="index" :key="map.type">
|
||||
<column-selector
|
||||
:class="'rule-map-' + map.type.replace(/_/g, '-')"
|
||||
:label="mappingTargets()[map.type].label"
|
||||
@@ -300,8 +300,8 @@
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a
|
||||
v-for="target in unmappedTargets"
|
||||
v-bind:index="target"
|
||||
v-bind:key="target"
|
||||
:index="target"
|
||||
:key="target"
|
||||
class="dropdown-item"
|
||||
href="javascript:void(0)"
|
||||
:class="'rule-add-mapping-' + target.replace(/_/g, '-')"
|
||||
@@ -337,9 +337,9 @@
|
||||
<!-- Example at the end of https://vuejs.org/v2/guide/list.html -->
|
||||
<rule-display
|
||||
v-for="(rule, index) in rules"
|
||||
v-bind:rule="rule"
|
||||
v-bind:index="index"
|
||||
v-bind:key="index"
|
||||
:rule="rule"
|
||||
:index="index"
|
||||
:key="index"
|
||||
@edit="editRule(rule, index)"
|
||||
@remove="removeRule(index)"
|
||||
:col-headers="colHeadersPerRule[index]"
|
||||
@@ -347,12 +347,12 @@
|
||||
<identifier-display
|
||||
v-for="(map, index) in mapping"
|
||||
v-bind="map"
|
||||
v-bind:index="index"
|
||||
v-bind:key="map.type"
|
||||
:index="index"
|
||||
:key="map.type"
|
||||
@remove="removeMapping(index)"
|
||||
@edit="displayRuleType = 'mapping'"
|
||||
v-on:mouseover.native="map.columns.forEach(col => highlightColumn(col))"
|
||||
v-on:mouseout.native="map.columns.forEach(col => unhighlightColumn(col))"
|
||||
@mouseover.native="map.columns.forEach(col => highlightColumn(col))"
|
||||
@mouseout.native="map.columns.forEach(col => unhighlightColumn(col))"
|
||||
:col-headers="colHeaders"
|
||||
/>
|
||||
<div v-if="mapping.length == 0">
|
||||
@@ -439,19 +439,14 @@
|
||||
</div>
|
||||
<!-- flex-column column -->
|
||||
<!-- style="width: 70%;" -->
|
||||
<div
|
||||
class="table-column"
|
||||
v-bind:class="orientation"
|
||||
style="width: 100%;"
|
||||
v-if="initialElements !== null"
|
||||
>
|
||||
<div class="table-column" :class="orientation" style="width: 100%;" v-if="initialElements !== null">
|
||||
<hot-table
|
||||
id="hot-table"
|
||||
ref="hotTable"
|
||||
:data="hotData.data"
|
||||
:colHeaders="colHeadersDisplay"
|
||||
:readOnly="true"
|
||||
stretchH="all"
|
||||
:col-headers="colHeadersDisplay"
|
||||
:read-only="true"
|
||||
stretch-h="all"
|
||||
>
|
||||
</hot-table>
|
||||
</div>
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<ol class="rules">
|
||||
<rule-display-preview
|
||||
v-for="(rule, index) in rules"
|
||||
v-bind:rule="rule"
|
||||
v-bind:index="index"
|
||||
v-bind:key="index"
|
||||
:rule="rule"
|
||||
:index="index"
|
||||
:key="index"
|
||||
:col-headers="columnData.colHeadersPerRule[index]"
|
||||
/>
|
||||
<identifier-display-preview
|
||||
v-for="(map, index) in mapping"
|
||||
v-bind="map"
|
||||
v-bind:index="index"
|
||||
v-bind:key="map.type"
|
||||
:index="index"
|
||||
:key="map.type"
|
||||
:col-headers="colHeaders"
|
||||
/>
|
||||
</ol>
|
||||
|
||||
@@ -3,13 +3,28 @@
|
||||
import $ from "jquery";
|
||||
|
||||
export default {
|
||||
props: ["options", "value", "placeholder"],
|
||||
props: ["options", "value", "placeholder", "containerClass", "enabled"],
|
||||
template: `<select><slot></slot></select>`,
|
||||
mounted: function() {
|
||||
const vm = this;
|
||||
// TODO: refactor property list to objects that allow defaults and types
|
||||
let enabled = this.enabled;
|
||||
if (enabled === undefined) {
|
||||
enabled = true;
|
||||
}
|
||||
const select2Options = {
|
||||
data: this.options,
|
||||
placeholder: this.placeholder,
|
||||
allowClear: !!this.placeholder,
|
||||
enable: enabled,
|
||||
dropdownAutoWidth: true
|
||||
};
|
||||
if (this.containerClass) {
|
||||
select2Options.containerCssClass = this.containerClass;
|
||||
}
|
||||
$(this.$el)
|
||||
// init select2
|
||||
.select2({ data: this.options, placeholder: this.placeholder, allowClear: this.placeholder })
|
||||
.select2(select2Options)
|
||||
.val(this.value)
|
||||
.trigger("change")
|
||||
// emit event on change.
|
||||
@@ -27,6 +42,9 @@ export default {
|
||||
$(this.$el)
|
||||
.empty()
|
||||
.select2({ data: options });
|
||||
},
|
||||
enabled: function(value) {
|
||||
$(this.$el).select2("enable", value);
|
||||
}
|
||||
},
|
||||
destroyed: function() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
import DatasetCollectionDialog from "./DatasetCollectionDialog.vue";
|
||||
import { __RewireAPI__ as rewire } from "./DatasetCollectionDialog";
|
||||
import { setupTestGalaxy } from "qunit/test-app";
|
||||
import SelectionDialog from "./SelectionDialog.vue";
|
||||
|
||||
import { shallowMount, createLocalVue } from "@vue/test-utils";
|
||||
@@ -12,10 +12,6 @@ const mockOptions = {
|
||||
history: "f2db41e1fa331b3e"
|
||||
};
|
||||
|
||||
const galaxyInstance = {
|
||||
root: "/"
|
||||
};
|
||||
|
||||
describe("DatasetCollectionDialog.vue", () => {
|
||||
let wrapper;
|
||||
let localVue;
|
||||
@@ -23,7 +19,7 @@ describe("DatasetCollectionDialog.vue", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
rewire.__Rewire__("getGalaxyInstance", () => galaxyInstance);
|
||||
setupTestGalaxy();
|
||||
localVue = createLocalVue();
|
||||
wrapper = shallowMount(DatasetCollectionDialog, {
|
||||
propsData: mockOptions,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<selection-dialog
|
||||
:errorMessage="errorMessage"
|
||||
:optionsShow="optionsShow"
|
||||
:modalShow="modalShow"
|
||||
:hideModal="() => (modalShow = false)"
|
||||
:error-message="errorMessage"
|
||||
:options-show="optionsShow"
|
||||
:modal-show="modalShow"
|
||||
:hide-modal="() => (modalShow = false)"
|
||||
>
|
||||
<template v-slot:search>
|
||||
<data-dialog-search v-model="filter" />
|
||||
@@ -14,7 +14,7 @@
|
||||
:items="items"
|
||||
:multiple="false"
|
||||
:filter="filter"
|
||||
leafIcon="fa fa-folder"
|
||||
leaf-icon="fa fa-folder"
|
||||
@clicked="clicked"
|
||||
@load="load"
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import WorkflowDialog from "./WorkflowDialog.vue";
|
||||
import { __RewireAPI__ as rewire } from "components/Workflow/services";
|
||||
import SelectionDialog from "./SelectionDialog.vue";
|
||||
import { setupTestGalaxy } from "qunit/test-app";
|
||||
|
||||
import { shallowMount, createLocalVue } from "@vue/test-utils";
|
||||
|
||||
@@ -11,15 +12,6 @@ const mockOptions = {
|
||||
modalStatic: true
|
||||
};
|
||||
|
||||
const userModel = {
|
||||
get: () => "username"
|
||||
};
|
||||
|
||||
const galaxyInstance = {
|
||||
root: "/",
|
||||
user: userModel
|
||||
};
|
||||
|
||||
describe("WorkflowDialog.vue", () => {
|
||||
let wrapper;
|
||||
let localVue;
|
||||
@@ -27,7 +19,7 @@ describe("WorkflowDialog.vue", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
rewire.__Rewire__("getGalaxyInstance", () => galaxyInstance);
|
||||
setupTestGalaxy();
|
||||
rewire.__Rewire__("_addAttributes", workflow => workflow);
|
||||
localVue = createLocalVue();
|
||||
wrapper = shallowMount(WorkflowDialog, {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<selection-dialog
|
||||
:errorMessage="errorMessage"
|
||||
:optionsShow="optionsShow"
|
||||
:modalShow="modalShow"
|
||||
:hideModal="() => (modalShow = false)"
|
||||
:error-message="errorMessage"
|
||||
:options-show="optionsShow"
|
||||
:modal-show="modalShow"
|
||||
:hide-modal="() => (modalShow = false)"
|
||||
>
|
||||
<template v-slot:search>
|
||||
<data-dialog-search v-model="filter" />
|
||||
@@ -14,7 +14,7 @@
|
||||
:items="items"
|
||||
:multiple="false"
|
||||
:filter="filter"
|
||||
leafIcon="fa fa-sitemap fa-rotate-270"
|
||||
leaf-icon="fa fa-sitemap fa-rotate-270"
|
||||
@clicked="clicked"
|
||||
@load="load"
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div v-else>
|
||||
There are multiple InteractiveTool result views available:
|
||||
<ul>
|
||||
<li v-for="entryPoint of entryPoints" v-bind:key="entryPoint.id">
|
||||
<li v-for="entryPoint of entryPoints" :key="entryPoint.id">
|
||||
{{ entryPoint.name }}
|
||||
<span v-if="entryPoint.active"> (<a :href="entryPoints[0].target">click here to display</a>) </span>
|
||||
<span v-else>
|
||||
|
||||
@@ -47,7 +47,10 @@ export default {
|
||||
total: 0,
|
||||
error: null,
|
||||
tabValue: true,
|
||||
tabOptions: [{ text: "Search All", value: true }, { text: "Installed Only", value: false }]
|
||||
tabOptions: [
|
||||
{ text: "Search All", value: true },
|
||||
{ text: "Installed Only", value: false }
|
||||
]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
</b-card>
|
||||
</span>
|
||||
<div v-else>
|
||||
<RepositoryDetails :repo="toolshedRepository" :toolshedUrl="repo.tool_shed_url" />
|
||||
<RepositoryDetails :repo="toolshedRepository" :toolshed-url="repo.tool_shed_url" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import RepositoryDetails from "../RepositoryDetails/Index.vue";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
id="repository-table"
|
||||
striped
|
||||
:fields="fields"
|
||||
:sortBy="sortBy"
|
||||
:sort-by="sortBy"
|
||||
:items="repositories"
|
||||
:filter="filter"
|
||||
@filtered="filtered"
|
||||
@@ -60,7 +60,7 @@
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import Monitor from "./Monitor";
|
||||
import RepositoryDetails from "./Details";
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import InstallationButton from "../RepositoryDetails/InstallationButton";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
<InstallationSettings
|
||||
v-if="showSettings"
|
||||
:repo="repo"
|
||||
:toolshedUrl="toolshedUrl"
|
||||
:changesetRevision="selectedChangeset"
|
||||
:requiresPanel="selectedRequiresPanel"
|
||||
:toolshed-url="toolshedUrl"
|
||||
:changeset-revision="selectedChangeset"
|
||||
:requires-panel="selectedRequiresPanel"
|
||||
@hide="onHide"
|
||||
@ok="onOk"
|
||||
/>
|
||||
@@ -54,7 +54,7 @@
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import InstallationSettings from "./InstallationSettings.vue";
|
||||
import InstallationButton from "./InstallationButton.vue";
|
||||
import RepositoryTools from "./RepositoryTools.vue";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Vue from "vue";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import RepositoryTools from "./RepositoryTools";
|
||||
|
||||
@@ -21,7 +22,7 @@ describe("RepositoryTools", () => {
|
||||
expect($second.text()).to.equal("version");
|
||||
});
|
||||
|
||||
it("test collapsing tool version list in repository details", () => {
|
||||
it("test collapsing tool version list in repository details", async () => {
|
||||
const wrapper = mount(RepositoryTools, {
|
||||
propsData: {
|
||||
tools: [
|
||||
@@ -56,6 +57,8 @@ describe("RepositoryTools", () => {
|
||||
expect($third.find("td:last-child").text()).to.equal("");
|
||||
const $link = wrapper.find("a");
|
||||
$link.trigger("click");
|
||||
await Vue.nextTick();
|
||||
|
||||
const $elExpanded = wrapper.findAll("tr");
|
||||
expect($elExpanded.length).to.equal(4);
|
||||
const $thirdExpanded = $elExpanded.at(3);
|
||||
@@ -66,6 +69,8 @@ describe("RepositoryTools", () => {
|
||||
expect($forthExpanded.find("td:last-child").text()).to.equal("");
|
||||
const $linkExpanded = wrapper.find("a");
|
||||
$linkExpanded.trigger("click");
|
||||
await Vue.nextTick();
|
||||
|
||||
const $elCollapsed = wrapper.findAll("tr");
|
||||
expect($elCollapsed.length).to.equal(3);
|
||||
const $thirdCollapsed = $elCollapsed.at(2);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<ServerSelection
|
||||
:toolshedUrl="toolshedUrl"
|
||||
:toolshedUrls="toolshedUrls"
|
||||
:toolshed-url="toolshedUrl"
|
||||
:toolshed-urls="toolshedUrls"
|
||||
:total="total"
|
||||
:loading="loading"
|
||||
@onToolshed="setToolshed"
|
||||
@@ -12,12 +12,12 @@
|
||||
<Repositories
|
||||
:query="query"
|
||||
:scrolled="scrolled"
|
||||
:toolshedUrl="toolshedUrl"
|
||||
:toolshed-url="toolshedUrl"
|
||||
@onError="setError"
|
||||
v-if="!queryEmpty"
|
||||
/>
|
||||
<Categories
|
||||
:toolshedUrl="toolshedUrl"
|
||||
:toolshed-url="toolshedUrl"
|
||||
:loading="loading"
|
||||
@onCategory="setQuery"
|
||||
@onTotal="setTotal"
|
||||
@@ -49,7 +49,10 @@ export default {
|
||||
total: 0,
|
||||
error: null,
|
||||
tabCurrent: "true",
|
||||
tabOptions: [{ text: "Search All", value: true }, { text: "Installed Only", value: false }]
|
||||
tabOptions: [
|
||||
{ text: "Search All", value: true },
|
||||
{ text: "Installed Only", value: false }
|
||||
]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mount } from "@vue/test-utils";
|
||||
import Repositories from "./Repositories";
|
||||
import { __RewireAPI__ as rewire } from "./Repositories";
|
||||
import Vue from "vue";
|
||||
import flushPromises from "flush-promises";
|
||||
|
||||
describe("Repositories", () => {
|
||||
beforeEach(() => {
|
||||
@@ -36,13 +37,17 @@ describe("Repositories", () => {
|
||||
toolshedUrl: "toolshedUrl"
|
||||
}
|
||||
});
|
||||
// Test initial state prior to the data fetch tick -- should be loading.
|
||||
expect(wrapper.find(".loading-message").text()).to.equal("Loading repositories...");
|
||||
wrapper.vm.pageState = wrapper.vm.COMPLETE;
|
||||
expect(wrapper.find(".unavailable-message").text()).to.equal("No matching repositories found.");
|
||||
await Vue.nextTick();
|
||||
const links = wrapper.findAll("a");
|
||||
expect(links.length).to.equal(2);
|
||||
expect(links.at(0).text()).to.equal("name_0");
|
||||
expect(links.at(1).text()).to.equal("name_1");
|
||||
// Reset repositories and state to test empty.
|
||||
wrapper.vm.repositories = [];
|
||||
wrapper.vm.pageState = 2; // COMPLETE is '2'
|
||||
await Vue.nextTick();
|
||||
expect(wrapper.find(".unavailable-message").text()).to.equal("No matching repositories found.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div>{{ row.item.description }}</div>
|
||||
</template>
|
||||
<template v-slot:row-details="row">
|
||||
<RepositoryDetails :repo="row.item" :toolshedUrl="toolshedUrl" />
|
||||
<RepositoryDetails :repo="row.item" :toolshed-url="toolshedUrl" />
|
||||
</template>
|
||||
</b-table>
|
||||
<div class="unavailable-message" v-if="noResultsFound">
|
||||
@@ -20,7 +20,7 @@
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { Services } from "../services.js";
|
||||
import { Services } from "../services";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import RepositoryDetails from "../RepositoryDetails/Index.vue";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Services } from "./Services";
|
||||
import { Services } from "./services";
|
||||
|
||||
describe("Toolshed service helpers", () => {
|
||||
const incoming = [
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import Collection from "./Collection.vue";
|
||||
import { mountWithApp } from "./test_helpers";
|
||||
|
||||
describe("Collection.vue", () => {
|
||||
it("loads with correct initial state", async () => {
|
||||
const { wrapper } = mountWithApp(Collection);
|
||||
expect(wrapper.vm.counterAnnounce).to.equals(0);
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
expect(wrapper.vm.extensions[0].id).to.equals("ab1");
|
||||
expect(wrapper.find("#btn-reset").classes()).to.contain("disabled");
|
||||
expect(wrapper.find("#btn-start").classes()).to.contain("disabled");
|
||||
expect(wrapper.find("#btn-stop").classes()).to.contain("disabled");
|
||||
expect(wrapper.findAll(".ui-limitloader").length).to.equals(0);
|
||||
});
|
||||
|
||||
it("does render FTP is site set", async () => {
|
||||
const { wrapper } = mountWithApp(Collection);
|
||||
expect(wrapper.find("#btn-ftp").isVisible()).to.equals(true);
|
||||
});
|
||||
|
||||
it("doesn't render FTP is no site set", async () => {
|
||||
const { wrapper } = mountWithApp(Collection, {
|
||||
currentFtp: () => {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
expect(wrapper.findAll("#btn-ftp").length).to.equals(0);
|
||||
});
|
||||
|
||||
it("resets properly", async () => {
|
||||
const { wrapper, localVue } = mountWithApp(Collection);
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
await localVue.nextTick();
|
||||
wrapper.find("#btn-new").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.showHelper).to.equals(false);
|
||||
expect(wrapper.vm.counterAnnounce).to.equals(1);
|
||||
|
||||
expect(wrapper.find("#btn-reset").classes()).to.not.contain("disabled");
|
||||
expect(wrapper.find("#btn-start").classes()).to.not.contain("disabled");
|
||||
|
||||
wrapper.find("#btn-reset").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
});
|
||||
|
||||
it("respects lazyLoadMax limit", async () => {
|
||||
const { wrapper, localVue } = mountWithApp(Collection, {}, { lazyLoadMax: 2 });
|
||||
expect(wrapper.findAll(".ui-limitloader").length).to.equals(1);
|
||||
await localVue.nextTick();
|
||||
wrapper.find("#btn-new").trigger("click");
|
||||
await localVue.nextTick();
|
||||
wrapper.find("#btn-new").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.findAll("table tbody tr").length).to.equals(2);
|
||||
wrapper.find("#btn-new").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.findAll("table tbody tr").length).to.equals(2);
|
||||
expect(wrapper.find(".ui-limitloader").text()).to.contain("only the first 2 entries");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<upload-wrapper ref="wrapper" :top-info="topInfo" :hightlight-box="hightlightBox">
|
||||
<div class="upload-helper" v-show="showHelper"><i class="fa fa-files-o" />Drop files here</div>
|
||||
<table class="upload-table ui-table-striped" v-show="!showHelper" ref="uploadTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
</table>
|
||||
<template v-slot:footer>
|
||||
<span class="upload-footer-title">Collection Type:</span>
|
||||
<select2
|
||||
container-class="upload-footer-collection-type"
|
||||
ref="footerCollectionType"
|
||||
v-model="collectionType"
|
||||
:enabled="!running"
|
||||
>
|
||||
<option value="list">List</option>
|
||||
<option value="paired">Pair</option>
|
||||
<option value="list:paired">List of Pairs</option>
|
||||
</select2>
|
||||
<span class="upload-footer-title">File Type:</span>
|
||||
<select2
|
||||
container-class="upload-footer-extension"
|
||||
ref="footerExtension"
|
||||
v-model="extension"
|
||||
:enabled="!running"
|
||||
>
|
||||
<option v-for="(ext, index) in extensions" :key="index" :value="ext.id">{{ ext.text }}</option>
|
||||
</select2>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search" />
|
||||
<span class="upload-footer-title">Genome (set all):</span>
|
||||
<select2 container-class="upload-footer-genome" ref="footerGenome" v-model="genome" :enabled="!running">
|
||||
<option v-for="(listGenome, index) in listGenomes" :key="index" :value="listGenome.id">{{
|
||||
listGenome.text
|
||||
}}</option>
|
||||
</select2>
|
||||
</template>
|
||||
<template v-slot:buttons>
|
||||
<b-button ref="btnClose" class="ui-button-default" id="btn-close" @click="app.hide()">
|
||||
{{ btnCloseTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnReset"
|
||||
class="ui-button-default"
|
||||
id="btn-reset"
|
||||
@click="_eventReset"
|
||||
:disabled="!enableReset"
|
||||
>
|
||||
{{ btnResetTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnStop"
|
||||
class="ui-button-default"
|
||||
id="btn-stop"
|
||||
@click="_eventStop"
|
||||
:disabled="counterRunning == 0"
|
||||
>
|
||||
{{ btnStopTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnBuild"
|
||||
class="ui-button-default"
|
||||
id="btn-build"
|
||||
@click="_eventBuild"
|
||||
:disabled="!enableBuild"
|
||||
:variant="enableBuild ? 'primary' : ''"
|
||||
>
|
||||
{{ btnBuildTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnStart"
|
||||
class="ui-button-default"
|
||||
id="btn-start"
|
||||
@click="_eventStart"
|
||||
:disabled="!enableStart"
|
||||
:variant="enableStart ? 'primary' : ''"
|
||||
>
|
||||
{{ btnStartTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnCreate"
|
||||
class="ui-button-default"
|
||||
id="btn-new"
|
||||
@click="_eventCreate"
|
||||
:disabled="!enableSources"
|
||||
>
|
||||
<span class="fa fa-edit"></span>{{ btnCreateTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnFtp"
|
||||
class="ui-button-default"
|
||||
id="btn-ftp"
|
||||
@click="_eventFtp"
|
||||
:disabled="!enableSources"
|
||||
v-if="ftpUploadSite"
|
||||
>
|
||||
<span class="fa fa-folder-open-o"></span>{{ btnFtpTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnLocal"
|
||||
class="ui-button-default"
|
||||
id="btn-local"
|
||||
:title="btnLocalTitle"
|
||||
@click="uploadSelect"
|
||||
:disabled="!enableSources"
|
||||
>
|
||||
<span class="fa fa-laptop"></span>{{ btnLocalTitle }}
|
||||
</b-button>
|
||||
</template>
|
||||
</upload-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadRow from "mvc/upload/collection/collection-row";
|
||||
import UploadBoxMixin from "./UploadBoxMixin";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
mixins: [UploadBoxMixin],
|
||||
data() {
|
||||
return {
|
||||
topInfo: "",
|
||||
showHelper: true,
|
||||
extension: this.app.defaultExtension,
|
||||
genome: this.app.defaultGenome,
|
||||
collectionType: "list",
|
||||
listExtensions: [],
|
||||
listGenomes: [],
|
||||
running: false,
|
||||
counterAnnounce: 0,
|
||||
counterSuccess: 0,
|
||||
counterError: 0,
|
||||
counterRunning: 0,
|
||||
uploadSize: 0,
|
||||
uploadCompleted: 0,
|
||||
enableReset: false,
|
||||
enableStart: false,
|
||||
enableSources: false,
|
||||
enableBuild: false,
|
||||
hightlightBox: false,
|
||||
rowUploadModel: UploadRow,
|
||||
btnLocalTitle: _l("Choose local files"),
|
||||
btnCreateTitle: _l("Paste/Fetch data"),
|
||||
btnFtpTitle: _l("Choose FTP files"),
|
||||
btnStartTitle: _l("Start"),
|
||||
btnBuildTitle: _l("Build"),
|
||||
btnStopTitle: _l("Pause"),
|
||||
btnResetTitle: _l("Reset"),
|
||||
btnCloseTitle: _l("Close")
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initCollection();
|
||||
this.initAppProperties();
|
||||
},
|
||||
mounted() {
|
||||
this.initExtensionInfo();
|
||||
this.initFtpPopover();
|
||||
// file upload
|
||||
this.initUploadbox({
|
||||
url: this.app.uploadPath,
|
||||
announce: (index, file) => {
|
||||
this._eventAnnounce(index, file);
|
||||
},
|
||||
initialize: index => {
|
||||
return this.app.toData([this.collection.get(index)], this.history_id);
|
||||
},
|
||||
progress: (index, percentage) => {
|
||||
this._eventProgress(index, percentage);
|
||||
},
|
||||
success: (index, message) => {
|
||||
this._eventSuccess(index, message);
|
||||
},
|
||||
warning: (index, message) => {
|
||||
this._eventWarning(index, message);
|
||||
},
|
||||
error: (index, message) => {
|
||||
this._eventError(index, message);
|
||||
},
|
||||
complete: () => {
|
||||
this._eventComplete();
|
||||
},
|
||||
ondragover: () => {
|
||||
this.hightlightBox = true;
|
||||
},
|
||||
ondragleave: () => {
|
||||
this.hightlightBox = false;
|
||||
}
|
||||
});
|
||||
this.collection.on("remove", model => {
|
||||
this._eventRemove(model);
|
||||
});
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
computed: {
|
||||
extensions() {
|
||||
const result = _.filter(this.listExtensions, ext => !ext.composite_files);
|
||||
return result;
|
||||
},
|
||||
appModel() {
|
||||
return this.app.model;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
extension: function(value) {
|
||||
this.updateExtension(value);
|
||||
},
|
||||
genome: function(value) {
|
||||
this.updateGenome(value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_newUploadModelProps: function(index, file) {
|
||||
return {
|
||||
id: index,
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
file_mode: file.mode || "local",
|
||||
file_path: file.path,
|
||||
file_data: file,
|
||||
extension: this.extension,
|
||||
genome: this.genome
|
||||
};
|
||||
},
|
||||
|
||||
/** Success */
|
||||
_eventSuccess: function(index, message) {
|
||||
var hids = _.pluck(message["outputs"], "hid");
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "success", hids: hids });
|
||||
this._updateStateForSuccess(it);
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
_eventBuild: function() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
var allHids = [];
|
||||
_.forEach(this.collection.models, upload => {
|
||||
allHids.push.apply(allHids, upload.get("hids"));
|
||||
});
|
||||
var models = _.map(allHids, hid => Galaxy.currHistoryPanel.collection.getByHid(hid));
|
||||
var selection = new Galaxy.currHistoryPanel.collection.constructor(models);
|
||||
// I'm building the selection wrong because I need to set this historyId directly.
|
||||
selection.historyId = Galaxy.currHistoryPanel.collection.historyId;
|
||||
Galaxy.currHistoryPanel.buildCollection(this.collectionType, selection, true);
|
||||
this.counterRunning = 0;
|
||||
this._updateStateForCounters();
|
||||
this._eventReset();
|
||||
this.app.hide();
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
if (this.counterAnnounce == 0 || this.counterRunning > 0) {
|
||||
return;
|
||||
}
|
||||
this.uploadSize = 0;
|
||||
this.uploadCompleted = 0;
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "init") {
|
||||
model.set("status", "queued");
|
||||
this.uploadSize += model.get("file_size");
|
||||
}
|
||||
});
|
||||
this.appModel.set({ percentage: 0, status: "success" });
|
||||
this.counterRunning = this.counterAnnounce;
|
||||
this.history_id = this.app.currentHistory();
|
||||
// package ftp files separately, and remove them from queue
|
||||
this._uploadFtp();
|
||||
this.uploadbox.start();
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.counterRunning == 0) {
|
||||
this.collection.reset();
|
||||
this.counterAnnounce = 0;
|
||||
this.counterSuccess = 0;
|
||||
this.counterError = 0;
|
||||
this.counterRunning = 0;
|
||||
this.uploadbox.reset();
|
||||
this.extension = this.app.defaultExtension;
|
||||
this.genome = this.app.defaultGenome;
|
||||
this.appModel.set("percentage", 0);
|
||||
this._updateStateForCounters();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Composite from "./Composite.vue";
|
||||
import { mountWithApp } from "./test_helpers";
|
||||
|
||||
describe("Composite.vue", () => {
|
||||
it("loads with correct initial state", async () => {
|
||||
const { wrapper } = mountWithApp(Composite);
|
||||
expect(wrapper.find("#btn-start").classes()).to.contain("disabled");
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
expect(wrapper.vm.readyStart).to.equals(false);
|
||||
// filters listExtensions to just the composite definition + _select_ value
|
||||
const extensions = wrapper.vm.extensions;
|
||||
expect(extensions.length).to.equals(2);
|
||||
expect(extensions[0].id).to.equals("_select_");
|
||||
expect(extensions[1].id).to.equals("affybatch");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<upload-wrapper wrapper-class="upload-view-composite">
|
||||
<div class="upload-helper" v-show="showHelper">Select Composite Type to show files required to upload</div>
|
||||
<table class="upload-table ui-table-striped" v-show="!showHelper" ref="uploadTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th />
|
||||
<th>Description</th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Settings</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
</table>
|
||||
<template v-slot:footer>
|
||||
<span class="upload-footer-title">Composite Type:</span>
|
||||
<select2
|
||||
container-class="upload-footer-extension"
|
||||
ref="footerExtension"
|
||||
v-model="extension"
|
||||
:enabled="!running"
|
||||
>
|
||||
<option v-for="(ext, index) in extensions" :key="index" :value="ext.id">{{ ext.text }}</option>
|
||||
</select2>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search" ref="footerExtensionInfo" />
|
||||
<span class="upload-footer-title">Genome/Build:</span>
|
||||
<select2 container-class="upload-footer-genome" ref="footerGenome" v-model="genome" :enabled="!running">
|
||||
<option v-for="(listGenome, index) in listGenomes" :key="index" :value="listGenome.id">{{
|
||||
listGenome.text
|
||||
}}</option>
|
||||
</select2>
|
||||
</template>
|
||||
<template v-slot:buttons>
|
||||
<b-button ref="btnClose" class="ui-button-default" @click="app.hide()">
|
||||
{{ btnCloseTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnStart"
|
||||
class="ui-button-default"
|
||||
@click="_eventStart"
|
||||
id="btn-start"
|
||||
:disabled="!readyStart"
|
||||
:variant="readyStart ? 'primary' : ''"
|
||||
>
|
||||
{{ btnStartTitle }}
|
||||
</b-button>
|
||||
<b-button ref="btnReset" class="ui-button-default" id="btn-reset" @click="_eventReset">
|
||||
{{ btnResetTitle }}
|
||||
</b-button>
|
||||
</template>
|
||||
</upload-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadRow from "mvc/upload/composite/composite-row";
|
||||
import UploadBoxMixin from "./UploadBoxMixin";
|
||||
|
||||
export default {
|
||||
mixins: [UploadBoxMixin],
|
||||
data() {
|
||||
return {
|
||||
extension: "_select_",
|
||||
genome: this.app.defaultGenome,
|
||||
listExtensions: [],
|
||||
listGenomes: [],
|
||||
running: false,
|
||||
showHelper: true,
|
||||
btnResetTitle: _l("Reset"),
|
||||
btnStartTitle: _l("Start"),
|
||||
btnCloseTitle: _l("Close"),
|
||||
readyStart: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initCollection();
|
||||
this.initAppProperties();
|
||||
},
|
||||
computed: {
|
||||
extensions() {
|
||||
const result = _.filter(this.listExtensions, ext => ext.composite_files);
|
||||
result.unshift({ id: "_select_", text: "Select" });
|
||||
return result;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initExtensionInfo();
|
||||
// listener for collection triggers on change in composite datatype and extension selection
|
||||
this.collection.on("add", model => {
|
||||
this._eventAnnounce(model);
|
||||
});
|
||||
this.collection.on("change add", () => {
|
||||
this.renderNonReactiveComponents();
|
||||
});
|
||||
this.renderNonReactiveComponents();
|
||||
},
|
||||
methods: {
|
||||
renderNonReactiveComponents: function() {
|
||||
var model = this.collection.first();
|
||||
if (model && model.get("status") == "running") {
|
||||
this.running = true;
|
||||
} else {
|
||||
this.running = false;
|
||||
}
|
||||
if (
|
||||
this.collection.where({ status: "ready" }).length + this.collection.where({ optional: true }).length ==
|
||||
this.collection.length &&
|
||||
this.collection.length > 0
|
||||
) {
|
||||
this.readyStart = true;
|
||||
} else {
|
||||
this.readyStart = false;
|
||||
}
|
||||
this.showHelper = this.collection.length == 0;
|
||||
},
|
||||
|
||||
//
|
||||
// upload events / process pipeline
|
||||
//
|
||||
|
||||
/** Builds the basic ui with placeholder rows for each composite data type file */
|
||||
_eventAnnounce: function(model) {
|
||||
var upload_row = new UploadRow(this, { model: model });
|
||||
this.$uploadTable()
|
||||
.find("tbody:first")
|
||||
.append(upload_row.$el);
|
||||
this.showHelper = this.collection.length == 0;
|
||||
upload_row.render();
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
this.collection.each(model => {
|
||||
model.set({
|
||||
genome: this.genome,
|
||||
extension: this.extension
|
||||
});
|
||||
});
|
||||
$.uploadpost({
|
||||
url: this.app.uploadPath,
|
||||
data: this.app.toData(this.collection.filter()),
|
||||
success: message => {
|
||||
this._eventSuccess(message);
|
||||
},
|
||||
error: message => {
|
||||
this._eventError(message);
|
||||
},
|
||||
progress: percentage => {
|
||||
this._eventProgress(percentage);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.collection.where({ status: "running" }).length == 0) {
|
||||
this.collection.reset();
|
||||
this.extension = this.app.defaultExtension;
|
||||
this.genome = this.app.defaultGenome;
|
||||
this.renderNonReactiveComponents();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh progress state */
|
||||
_eventProgress: function(percentage) {
|
||||
this.collection.each(it => {
|
||||
it.set("percentage", percentage);
|
||||
});
|
||||
},
|
||||
|
||||
/** Refresh success state */
|
||||
_eventSuccess: function(message) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.collection.each(it => {
|
||||
it.set("status", "success");
|
||||
});
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
/** Refresh error state */
|
||||
_eventError: function(message) {
|
||||
this.collection.each(it => {
|
||||
it.set({ status: "error", info: message });
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
extension: function(value) {
|
||||
this.collection.reset();
|
||||
const details = this.extensionDetails(value);
|
||||
if (details && details.composite_files) {
|
||||
_.each(details.composite_files, item => {
|
||||
this.collection.add({
|
||||
id: this.collection.size(),
|
||||
file_desc: item.description || item.name,
|
||||
optional: item.optional
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
import Default from "./Default.vue";
|
||||
import { mountWithApp } from "./test_helpers";
|
||||
import { __RewireAPI__ as rewire } from "./Default";
|
||||
import Backbone from "backbone";
|
||||
|
||||
describe("Default.vue", () => {
|
||||
beforeEach(() => {
|
||||
// Use of select2 in default-row requires this mock.
|
||||
rewire.__Rewire__("UploadRow", Backbone.View);
|
||||
});
|
||||
|
||||
it("loads with correct initial state", async () => {
|
||||
const { wrapper } = mountWithApp(Default);
|
||||
expect(wrapper.vm.counterAnnounce).to.equals(0);
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
expect(wrapper.vm.extensions[0].id).to.equals("ab1");
|
||||
expect(wrapper.find("#btn-reset").classes()).to.contain("disabled");
|
||||
expect(wrapper.find("#btn-start").classes()).to.contain("disabled");
|
||||
expect(wrapper.find("#btn-stop").classes()).to.contain("disabled");
|
||||
});
|
||||
|
||||
it("does render FTP is site set", async () => {
|
||||
const { wrapper, localVue } = mountWithApp(Default);
|
||||
expect(wrapper.find("#btn-ftp").isVisible()).to.equals(true);
|
||||
wrapper.find("#btn-ftp").trigger("click");
|
||||
await localVue.nextTick();
|
||||
// TODO: test popover appears... not sure best way to do this...
|
||||
});
|
||||
|
||||
it("doesn't render FTP is no site set", async () => {
|
||||
const { wrapper } = mountWithApp(Default, {
|
||||
currentFtp: () => {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
expect(wrapper.findAll("#btn-ftp").length).to.equals(0);
|
||||
});
|
||||
|
||||
it("resets properly", async () => {
|
||||
const { wrapper, localVue } = mountWithApp(Default);
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
await localVue.nextTick();
|
||||
wrapper.find("#btn-new").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.showHelper).to.equals(false);
|
||||
expect(wrapper.vm.counterAnnounce).to.equals(1);
|
||||
wrapper.find("#btn-reset").trigger("click");
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.showHelper).to.equals(true);
|
||||
});
|
||||
|
||||
it("renders a limitloader element if lazyLoadMax set", async () => {
|
||||
const { wrapper } = mountWithApp(Default, {}, { lazyLoadMax: 2 });
|
||||
expect(wrapper.findAll(".ui-limitloader").length).to.equals(1);
|
||||
// hard to actually test the functionality like in Collection.test.js
|
||||
// because we're stubbing out all of UploadRow.
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<upload-wrapper ref="wrapper" :top-info="topInfo">
|
||||
<div class="upload-helper" v-show="showHelper"><i class="fa fa-files-o" />Drop files here</div>
|
||||
<table class="upload-table ui-table-striped" v-show="!showHelper" ref="uploadTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Type</th>
|
||||
<th>Genome</th>
|
||||
<th>Settings</th>
|
||||
<th>Status</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
</table>
|
||||
<template v-slot:footer>
|
||||
<span class="upload-footer-title">Type (set all):</span>
|
||||
<select2
|
||||
container-class="upload-footer-extension"
|
||||
ref="footerExtension"
|
||||
v-model="extension"
|
||||
:enabled="!running"
|
||||
>
|
||||
<option v-for="(ext, index) in extensions" :key="index" :value="ext.id">{{ ext.text }}</option>
|
||||
</select2>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search" />
|
||||
<span class="upload-footer-title">Genome (set all):</span>
|
||||
<select2 container-class="upload-footer-genome" ref="footerGenome" v-model="genome" :enabled="!running">
|
||||
<option v-for="(listGenome, index) in listGenomes" :key="index" :value="listGenome.id">{{
|
||||
listGenome.text
|
||||
}}</option>
|
||||
</select2>
|
||||
</template>
|
||||
<template v-slot:buttons>
|
||||
<b-button ref="btnClose" class="ui-button-default" id="btn-close" @click="app.hide()">
|
||||
{{ btnCloseTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnReset"
|
||||
class="ui-button-default"
|
||||
id="btn-reset"
|
||||
@click="_eventReset"
|
||||
:disabled="!enableReset"
|
||||
>
|
||||
{{ btnResetTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnStop"
|
||||
class="ui-button-default"
|
||||
id="btn-stop"
|
||||
@click="_eventStop"
|
||||
:disabled="counterRunning == 0"
|
||||
>
|
||||
{{ btnStopTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnStart"
|
||||
class="ui-button-default"
|
||||
id="btn-start"
|
||||
@click="_eventStart"
|
||||
:disabled="!enableStart"
|
||||
:variant="enableStart ? 'primary' : ''"
|
||||
>
|
||||
{{ btnStartTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnCreate"
|
||||
class="ui-button-default"
|
||||
id="btn-new"
|
||||
@click="_eventCreate"
|
||||
:disabled="!enableSources"
|
||||
>
|
||||
<span class="fa fa-edit"></span>{{ btnCreateTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnFtp"
|
||||
class="ui-button-default"
|
||||
id="btn-ftp"
|
||||
@click="_eventFtp"
|
||||
:disabled="!enableSources"
|
||||
v-if="ftpUploadSite"
|
||||
>
|
||||
<span class="fa fa-folder-open-o"></span>{{ btnFtpTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnLocal"
|
||||
class="ui-button-default"
|
||||
id="btn-local"
|
||||
:title="btnLocalTitle"
|
||||
@click="uploadSelect"
|
||||
:disabled="!enableSources"
|
||||
>
|
||||
<span class="fa fa-laptop"></span>{{ btnLocalTitle }}
|
||||
</b-button>
|
||||
</template>
|
||||
</upload-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadRow from "mvc/upload/default/default-row";
|
||||
import UploadBoxMixin from "./UploadBoxMixin";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
mixins: [UploadBoxMixin],
|
||||
data() {
|
||||
return {
|
||||
topInfo: "",
|
||||
showHelper: true,
|
||||
extension: this.app.defaultExtension,
|
||||
genome: this.app.defaultGenome,
|
||||
listExtensions: [],
|
||||
listGenomes: [],
|
||||
running: false,
|
||||
rowUploadModel: UploadRow,
|
||||
counterAnnounce: 0,
|
||||
counterSuccess: 0,
|
||||
counterError: 0,
|
||||
counterRunning: 0,
|
||||
uploadSize: 0,
|
||||
uploadCompleted: 0,
|
||||
enableReset: false,
|
||||
enableStart: false,
|
||||
enableSources: false,
|
||||
btnLocalTitle: _l("Choose local files"),
|
||||
btnCreateTitle: _l("Paste/Fetch data"),
|
||||
btnFtpTitle: _l("Choose FTP files"),
|
||||
btnStartTitle: _l("Start"),
|
||||
btnStopTitle: _l("Pause"),
|
||||
btnResetTitle: _l("Reset"),
|
||||
btnCloseTitle: _l("Close")
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initCollection();
|
||||
this.initAppProperties();
|
||||
},
|
||||
mounted() {
|
||||
this.initExtensionInfo();
|
||||
this.initFtpPopover();
|
||||
// file upload
|
||||
this.initUploadbox({
|
||||
url: this.app.uploadPath,
|
||||
announce: (index, file) => {
|
||||
this._eventAnnounce(index, file);
|
||||
},
|
||||
initialize: index => {
|
||||
return this.app.toData([this.collection.get(index)], this.history_id);
|
||||
},
|
||||
progress: (index, percentage) => {
|
||||
this._eventProgress(index, percentage);
|
||||
},
|
||||
success: (index, message) => {
|
||||
this._eventSuccess(index, message);
|
||||
},
|
||||
error: (index, message) => {
|
||||
this._eventError(index, message);
|
||||
},
|
||||
warning: (index, message) => {
|
||||
this._eventWarning(index, message);
|
||||
},
|
||||
complete: () => {
|
||||
this._eventComplete();
|
||||
},
|
||||
ondragover: () => {
|
||||
this.hightlightBox = true;
|
||||
},
|
||||
ondragleave: () => {
|
||||
this.hightlightBox = false;
|
||||
}
|
||||
});
|
||||
this.collection.on("remove", model => {
|
||||
this._eventRemove(model);
|
||||
});
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
computed: {
|
||||
extensions() {
|
||||
const result = _.filter(this.listExtensions, ext => !ext.composite_files);
|
||||
return result;
|
||||
},
|
||||
appModel() {
|
||||
return this.app.model;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
extension: function(value) {
|
||||
this.updateExtension(value);
|
||||
},
|
||||
genome: function(value) {
|
||||
this.updateGenome(value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_newUploadModelProps: function(index, file) {
|
||||
return {
|
||||
id: index,
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
file_mode: file.mode || "local",
|
||||
file_path: file.path,
|
||||
file_data: file
|
||||
};
|
||||
},
|
||||
|
||||
/** Success */
|
||||
_eventSuccess: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "success" });
|
||||
this._updateStateForSuccess(it);
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
if (this.counterAnnounce !== 0 && this.counterRunning === 0) {
|
||||
// prepare upload process
|
||||
this.uploadSize = 0;
|
||||
this.uploadCompleted = 0;
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "init") {
|
||||
model.set("status", "queued");
|
||||
this.uploadSize += model.get("file_size");
|
||||
}
|
||||
});
|
||||
|
||||
this.appModel.set({ percentage: 0, status: "success" });
|
||||
this.counterRunning = this.counterAnnounce;
|
||||
this.history_id = this.app.currentHistory();
|
||||
// package ftp files separately, and remove them from queue
|
||||
this._uploadFtp();
|
||||
|
||||
// queue remaining files
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.uploadbox.start({
|
||||
id: Galaxy.user.id,
|
||||
chunk_upload_size: this.app.chunkUploadSize
|
||||
});
|
||||
this._updateStateForCounters();
|
||||
}
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.counterRunning === 0) {
|
||||
this.collection.reset();
|
||||
this.counterAnnounce = 0;
|
||||
this.counterSuccess = 0;
|
||||
this.counterError = 0;
|
||||
this.counterRunning = 0;
|
||||
this.uploadbox.reset();
|
||||
this.extension = this.app.defaultExtension;
|
||||
this.genome = this.app.defaultGenome;
|
||||
this.appModel.set("percentage", 0);
|
||||
this._updateStateForCounters();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
import RulesInput from "./RulesInput.vue";
|
||||
import { mountWithApp } from "./test_helpers";
|
||||
|
||||
describe("RulesInput.vue", () => {
|
||||
it("loads with correct initial state", async () => {
|
||||
const { wrapper } = mountWithApp(RulesInput);
|
||||
expect(wrapper.find("#btn-reset").classes()).to.contain("disabled");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<upload-wrapper ref="wrapper" :top-info="topInfo">
|
||||
<span style="width: 25%; display: inline; height: 100%" class="float-left">
|
||||
<div class="upload-rule-option">
|
||||
<div class="upload-rule-option-title">{{ l("Upload data as") }}</div>
|
||||
<div class="rule-data-type">
|
||||
<select2 v-model="dataType" container-class="upload-footer-selection">
|
||||
<option value="datasets">Datasets</option>
|
||||
<option value="collections">Collection(s)</option>
|
||||
</select2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-rule-option">
|
||||
<div class="upload-rule-option-title">{{ l("Load tabular data from") }}</div>
|
||||
<div class="rule-select-type">
|
||||
<select2 container-class="upload-footer-selection" v-model="selectionType">
|
||||
<option value="paste">{{ l("Pasted Table") }}</option>
|
||||
<option value="dataset">{{ l("History Dataset") }}</option>
|
||||
<option v-if="ftpUploadSite" value="ftp">{{ l("FTP Directory") }} </option></select2
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div id="upload-rule-dataset-option" class="upload-rule-option" v-if="selectionType == 'dataset'">
|
||||
<div class="upload-rule-option-title">{{ l("Select dataset to load") }}</div>
|
||||
<div class="dataset-selector">
|
||||
<select2
|
||||
v-model="selectedDatasetId"
|
||||
container-class="upload-footer-selection"
|
||||
placeholder="Select Dataset"
|
||||
>
|
||||
<option></option>
|
||||
<option v-for="dataset of datasets" :key="dataset.id" :value="dataset.id">
|
||||
{{ dataset.hid }}: {{ dataset.name }}</option
|
||||
>
|
||||
</select2>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span style="display: inline; float: right; width: 75%; height: 300px">
|
||||
<textarea
|
||||
class="upload-rule-source-content form-control"
|
||||
style="height: 100%"
|
||||
v-model="sourceContent"
|
||||
:disabled="selectionType != 'paste'"
|
||||
></textarea>
|
||||
</span>
|
||||
<template v-slot:buttons>
|
||||
<b-button ref="btnClose" class="ui-button-default" id="btn-close" @click="app.hide()">
|
||||
{{ btnCloseTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnBuild"
|
||||
class="ui-button-default"
|
||||
id="btn-build"
|
||||
@click="_eventBuild"
|
||||
:disabled="!sourceContent"
|
||||
:variant="sourceContent ? 'primary' : ''"
|
||||
>
|
||||
{{ btnBuildTitle }}
|
||||
</b-button>
|
||||
<b-button
|
||||
ref="btnReset"
|
||||
class="ui-button-default"
|
||||
id="btn-reset"
|
||||
@click="_eventReset"
|
||||
:disabled="!enableReset"
|
||||
>
|
||||
{{ btnResetTitle }}
|
||||
</b-button>
|
||||
</template>
|
||||
</upload-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _l from "utils/localization";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadBoxMixin from "./UploadBoxMixin";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import UploadUtils from "mvc/upload/upload-utils";
|
||||
import axios from "axios";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
mixins: [UploadBoxMixin],
|
||||
data() {
|
||||
return {
|
||||
l: _l,
|
||||
datasets: [],
|
||||
ftpFiles: [],
|
||||
datasetsSet: false,
|
||||
topInfo: _l("Tabular source data to extract collection files and metadata from"),
|
||||
enableReset: false,
|
||||
enableBuild: false,
|
||||
dataType: "datasets",
|
||||
selectedDatasetId: null,
|
||||
sourceContent: "",
|
||||
selectionType: "paste",
|
||||
btnBuildTitle: _l("Build"),
|
||||
btnResetTitle: _l("Reset"),
|
||||
btnCloseTitle: _l("Close")
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initCollection();
|
||||
this.initAppProperties();
|
||||
},
|
||||
watch: {
|
||||
selectionType: function(selectionType) {
|
||||
if (selectionType == "dataset" && !this.datasetsSet) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const history = Galaxy && Galaxy.currHistoryPanel && Galaxy.currHistoryPanel.model;
|
||||
const historyContentModels = history.contents.models;
|
||||
for (const historyContentModel of historyContentModels) {
|
||||
const attr = historyContentModel.attributes;
|
||||
if (attr.history_content_type !== "dataset") {
|
||||
continue;
|
||||
}
|
||||
this.datasets.push(attr);
|
||||
}
|
||||
this.datasetsSet = true;
|
||||
} else if (selectionType == "ftp") {
|
||||
UploadUtils.getRemoteFiles(ftp_files => {
|
||||
this.sourceContent = ftp_files.map(file => file["path"]).join("\n");
|
||||
this.ftpFiles = ftp_files;
|
||||
});
|
||||
}
|
||||
},
|
||||
selectedDatasetId: function(selectedDatasetId) {
|
||||
if (!selectedDatasetId) {
|
||||
this.sourceContent = "";
|
||||
return;
|
||||
}
|
||||
const Galaxy = getGalaxyInstance();
|
||||
axios
|
||||
.get(
|
||||
`${getAppRoot()}api/histories/${
|
||||
Galaxy.currHistoryPanel.model.id
|
||||
}/contents/${selectedDatasetId}/display`
|
||||
)
|
||||
.then(response => {
|
||||
this.sourceContent = response.data;
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_eventReset: function() {
|
||||
this.selectedDatasetId = null;
|
||||
this.sourceContent = "";
|
||||
},
|
||||
|
||||
_eventBuild: function() {
|
||||
this._buildSelection(this.sourceContent);
|
||||
},
|
||||
|
||||
_buildSelection: function(content) {
|
||||
const selectionType = this.selectionType;
|
||||
const selection = {};
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (selectionType == "dataset" || selectionType == "paste") {
|
||||
selection.selectionType = "raw";
|
||||
selection.content = content;
|
||||
} else if (selectionType == "ftp") {
|
||||
selection.selectionType = "ftp";
|
||||
selection.elements = this.ftpFiles;
|
||||
selection.ftpUploadSite = this.ftpUploadSite;
|
||||
}
|
||||
selection.dataType = this.dataType;
|
||||
Galaxy.currHistoryPanel.buildCollection("rules", selection, true);
|
||||
this.app.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,291 @@
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import Select2 from "components/Select2";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
import UploadExtension from "mvc/upload/upload-extension";
|
||||
import UploadModel from "mvc/upload/upload-model";
|
||||
import UploadWrapper from "./UploadWrapper";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadFtp from "mvc/upload/upload-ftp";
|
||||
import LazyLimited from "mvc/lazy/lazy-limited";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UploadWrapper,
|
||||
Select2
|
||||
},
|
||||
props: {
|
||||
app: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
lazyLoadMax: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
$uploadBox() {
|
||||
return $(this.$refs.wrapper.$refs.uploadBox);
|
||||
},
|
||||
initUploadbox(options) {
|
||||
const $uploadBox = this.$uploadBox();
|
||||
this.uploadbox = $uploadBox.uploadbox(options);
|
||||
if (this.lazyLoadMax !== null) {
|
||||
const $uploadBox = this.$uploadBox();
|
||||
this.loader = new LazyLimited({
|
||||
$container: $uploadBox,
|
||||
collection: this.collection,
|
||||
max: this.lazyLoadMax,
|
||||
new_content: model => {
|
||||
return this.renderNewModel(model);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
uploadSelect: function() {
|
||||
this.uploadbox.select();
|
||||
},
|
||||
/** Package and upload ftp files in a single request */
|
||||
_uploadFtp: function() {
|
||||
const list = [];
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "queued" && model.get("file_mode") == "ftp") {
|
||||
this.uploadbox.remove(model.id);
|
||||
list.push(model);
|
||||
}
|
||||
});
|
||||
if (list.length > 0) {
|
||||
$.uploadpost({
|
||||
data: this.app.toData(list),
|
||||
url: this.app.uploadPath,
|
||||
success: message => {
|
||||
_.each(list, model => {
|
||||
this._eventSuccess(model.id, message);
|
||||
});
|
||||
},
|
||||
error: message => {
|
||||
_.each(list, model => {
|
||||
self._eventError(model.id, message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
renderNewModel: function(model) {
|
||||
// Turn backbone upload model into row model and place in table,
|
||||
// render in next tick so table can respond first and dynamic
|
||||
// sizing works.
|
||||
var uploadRow = new this.rowUploadModel(this, { model: model });
|
||||
this.$uploadTable()
|
||||
.find("tbody:first")
|
||||
.append(uploadRow.$el);
|
||||
this._updateStateForCounters();
|
||||
this.$nextTick(() => {
|
||||
uploadRow.render();
|
||||
});
|
||||
return uploadRow;
|
||||
},
|
||||
_updateStateForCounters: function() {
|
||||
this.setTopInfoBasedOnCounters();
|
||||
this.enableReset =
|
||||
this.counterRunning == 0 && this.counterAnnounce + this.counterSuccess + this.counterError > 0;
|
||||
this.enableStart = this.counterRunning == 0 && this.counterAnnounce > 0;
|
||||
this.enableBuild =
|
||||
this.counterRunning == 0 &&
|
||||
this.counterAnnounce == 0 &&
|
||||
this.counterSuccess > 0 &&
|
||||
this.counterError == 0;
|
||||
this.enableSources = this.counterRunning == 0;
|
||||
var show_table = this.counterAnnounce + this.counterSuccess + this.counterError > 0;
|
||||
this.showHelper = !show_table;
|
||||
},
|
||||
setTopInfoBasedOnCounters: function() {
|
||||
var message = "";
|
||||
if (this.counterAnnounce == 0) {
|
||||
if (this.uploadbox.compatible()) {
|
||||
message = " ";
|
||||
} else {
|
||||
message =
|
||||
"Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.";
|
||||
}
|
||||
} else {
|
||||
if (this.counterRunning == 0) {
|
||||
message = `You added ${this.counterAnnounce} file(s) to the queue. Add more files or click 'Start' to proceed.`;
|
||||
} else {
|
||||
message = `Please wait...${this.counterAnnounce} out of ${this.counterRunning} remaining.`;
|
||||
}
|
||||
}
|
||||
this.topInfo = message;
|
||||
},
|
||||
/** Progress */
|
||||
_eventProgress: function(index, percentage) {
|
||||
var it = this.collection.get(index);
|
||||
it.set("percentage", percentage);
|
||||
this.appModel.set("percentage", this._uploadPercentage(percentage, it.get("file_size")));
|
||||
},
|
||||
/** Calculate percentage of all queued uploads */
|
||||
_uploadPercentage: function(percentage, size) {
|
||||
return (this.uploadCompleted + percentage * size) / this.uploadSize;
|
||||
},
|
||||
_updateStateForSuccess: function(model) {
|
||||
this.appModel.set("percentage", this._uploadPercentage(100, model.get("file_size")));
|
||||
this.uploadCompleted += model.get("file_size") * 100;
|
||||
this.counterAnnounce--;
|
||||
this.counterSuccess++;
|
||||
this._updateStateForCounters();
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
/** A new file has been dropped/selected through the uploadbox plugin */
|
||||
_eventAnnounce: function(index, file) {
|
||||
this.counterAnnounce++;
|
||||
const modelProps = this._newUploadModelProps(index, file);
|
||||
var newModel = new UploadModel.Model(modelProps);
|
||||
this.collection.add(newModel);
|
||||
// if using lazyLoader, let it handle it - else render directly
|
||||
if (this.loader) {
|
||||
this._updateStateForCounters();
|
||||
} else {
|
||||
this.renderNewModel(newModel);
|
||||
}
|
||||
},
|
||||
/** Error */
|
||||
_eventError: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "error", info: message });
|
||||
this.appModel.set({
|
||||
percentage: this._uploadPercentage(100, it.get("file_size")),
|
||||
status: "danger"
|
||||
});
|
||||
this.uploadCompleted += it.get("file_size") * 100;
|
||||
this.counterAnnounce--;
|
||||
this.counterError++;
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
/** Queue is done */
|
||||
_eventComplete: function() {
|
||||
this.collection.each(model => {
|
||||
model.get("status") == "queued" && model.set("status", "init");
|
||||
});
|
||||
this.counterRunning = 0;
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
/** Remove model from upload list */
|
||||
_eventRemove: function(model) {
|
||||
var status = model.get("status");
|
||||
if (status == "success") {
|
||||
this.counterSuccess--;
|
||||
} else if (status == "error") {
|
||||
this.counterError--;
|
||||
} else {
|
||||
this.counterAnnounce--;
|
||||
}
|
||||
this.uploadbox.remove(model.id);
|
||||
this._updateStateForCounters();
|
||||
},
|
||||
/** Show/hide ftp popup */
|
||||
_eventFtp: function() {
|
||||
this.ftp.show(
|
||||
new UploadFtp({
|
||||
collection: this.collection,
|
||||
ftp_upload_site: this.ftpUploadSite,
|
||||
onadd: ftp_file => {
|
||||
return this.uploadbox.add([
|
||||
{
|
||||
mode: "ftp",
|
||||
name: ftp_file.path,
|
||||
size: ftp_file.size,
|
||||
path: ftp_file.path
|
||||
}
|
||||
]);
|
||||
},
|
||||
onremove: function(model_index) {
|
||||
this.collection.remove(model_index);
|
||||
}
|
||||
}).$el
|
||||
);
|
||||
},
|
||||
/** Create a new file */
|
||||
_eventCreate: function() {
|
||||
this.uploadbox.add([{ name: "New File", size: 0, mode: "new" }]);
|
||||
},
|
||||
/** Pause upload process */
|
||||
_eventStop: function() {
|
||||
if (this.counterRunning > 0) {
|
||||
this.appModel.set("status", "info");
|
||||
this.topInfo = "Queue will pause after completing the current file...";
|
||||
this.uploadbox.stop();
|
||||
}
|
||||
},
|
||||
_eventWarning: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ status: "warning", info: message });
|
||||
},
|
||||
$uploadTable() {
|
||||
return $(this.$refs.uploadTable);
|
||||
},
|
||||
extensionDetails(extension) {
|
||||
var details = _.findWhere(this.listExtensions, {
|
||||
id: extension
|
||||
});
|
||||
return details;
|
||||
},
|
||||
initExtensionInfo() {
|
||||
$(this.$refs.footerExtensionInfo)
|
||||
.on("click", e => {
|
||||
const details = this.extensionDetails(this.extension);
|
||||
if (details) {
|
||||
new UploadExtension({
|
||||
$el: $(e.target),
|
||||
title: details && details.text,
|
||||
extension: details && details.id,
|
||||
list: this.listExtensions,
|
||||
placement: "top"
|
||||
});
|
||||
}
|
||||
})
|
||||
.on("mousedown", e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
},
|
||||
initCollection() {
|
||||
this.collection = new UploadModel.Collection();
|
||||
},
|
||||
initAppProperties() {
|
||||
this.listExtensions = this.app.listExtensions;
|
||||
this.listGenomes = this.app.listGenomes;
|
||||
this.ftpUploadSite = this.app.currentFtp();
|
||||
},
|
||||
initFtpPopover() {
|
||||
// add ftp file viewer
|
||||
this.ftp = new Popover({
|
||||
title: _l("FTP files"),
|
||||
container: $(this.$refs.btnFtp)
|
||||
});
|
||||
},
|
||||
/* walk collection and update un-modified default values when globals
|
||||
change */
|
||||
updateExtension(extension, defaults_only) {
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("extension") == this.app.defaultExtension || !defaults_only)
|
||||
) {
|
||||
model.set("extension", extension);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateGenome: function(genome, defaults_only) {
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("genome") == this.app.defaultGenome || !defaults_only)
|
||||
) {
|
||||
model.set("genome", genome);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
import UploadModal from "./UploadModal.vue";
|
||||
import { setupTestGalaxy } from "qunit/test-app";
|
||||
|
||||
import { shallowMount, createLocalVue } from "@vue/test-utils";
|
||||
|
||||
const propsData = {
|
||||
chunkUploadSize: 1024,
|
||||
uploadPath: "/api/tools"
|
||||
};
|
||||
|
||||
describe("UploadModal.vue", () => {
|
||||
let wrapper;
|
||||
let localVue;
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
const fastaResponse = {
|
||||
description_url: "https://wiki.galaxyproject.org/Learn/Datatypes#Fasta",
|
||||
display_in_upload: true,
|
||||
extension: "fasta",
|
||||
description:
|
||||
"A sequence in FASTA format consists of a single-line description, followed by lines of sequence data. The first character of the description line is a greater-than ('>') symbol in the first column. All lines should be shorter than 80 characters."
|
||||
};
|
||||
const datatypesResponse = [fastaResponse];
|
||||
axiosMock.onGet(`/api/datatypes?extension_only=False`).reply(200, datatypesResponse);
|
||||
const genomesResponse = [
|
||||
["Scarlet macaw Jun 2013 (SMACv1.1/araMac1) (araMac1)", "araMac1"],
|
||||
["Cat Dec. 2008 (NHGRI/GTB V17e/felCat4) (felCat4)", "felCat4"],
|
||||
["Cat Sep. 2011 (ICGSC Felis_catus 6.2/felCat5) (felCat5)", "felCat5"]
|
||||
];
|
||||
axiosMock.onGet(`/api/genomes`).reply(200, genomesResponse);
|
||||
|
||||
localVue = createLocalVue();
|
||||
setupTestGalaxy();
|
||||
wrapper = shallowMount(UploadModal, {
|
||||
propsData: propsData,
|
||||
localVue: localVue
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
});
|
||||
|
||||
it("should load with correct defaults", async () => {
|
||||
expect(wrapper.vm.auto.id).to.equals("auto");
|
||||
expect(wrapper.vm.datatypesDisableAuto).to.equals(false);
|
||||
});
|
||||
|
||||
it("should fetch datatypes and parse them", async () => {
|
||||
await localVue.nextTick();
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.listExtensions.length).to.equals(2);
|
||||
expect(wrapper.vm.listExtensions[0].id).to.equals("auto");
|
||||
expect(wrapper.vm.listExtensions[1].id).to.equals("fasta");
|
||||
});
|
||||
|
||||
it("should fetch genomes and parse them", async () => {
|
||||
await localVue.nextTick();
|
||||
await localVue.nextTick();
|
||||
expect(wrapper.vm.listGenomes.length).to.equals(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<b-modal
|
||||
v-model="modalShow"
|
||||
:static="modalStatic"
|
||||
header-class="no-separator"
|
||||
modal-class="ui-modal"
|
||||
dialog-class="upload-dialog"
|
||||
body-class="upload-dialog-body"
|
||||
no-enforce-focus
|
||||
hide-footer
|
||||
>
|
||||
<template v-slot:modal-header>
|
||||
<h4 class="title" tabindex="0">{{ title }}</h4>
|
||||
</template>
|
||||
<b-tabs v-if="historyAvailable">
|
||||
<b-tab title="Regular" id="regular" button-id="tab-title-link-regular">
|
||||
<default :app="this" :lazy-load-max="50" />
|
||||
</b-tab>
|
||||
<b-tab title="Composite" id="composite" button-id="tab-title-link-composite">
|
||||
<composite :app="this" />
|
||||
</b-tab>
|
||||
<b-tab title="Collection" id="collection" button-id="tab-title-link-collection">
|
||||
<collection :app="this" />
|
||||
</b-tab>
|
||||
<b-tab title="Rule-based" id="rule-based" button-id="tab-title-link-rule-based">
|
||||
<rules-input :app="this" />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
<div v-else>
|
||||
Loading required information from Galaxy server.
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Backbone from "backbone";
|
||||
import $ from "jquery";
|
||||
import _l from "utils/localization";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadUtils from "mvc/upload/upload-utils";
|
||||
import Composite from "./Composite";
|
||||
import Collection from "./Collection";
|
||||
import Default from "./Default";
|
||||
import RulesInput from "./RulesInput";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Collection,
|
||||
Composite,
|
||||
Default,
|
||||
RulesInput
|
||||
},
|
||||
props: {
|
||||
modalStatic: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
chunkUploadSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
uploadPath: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modalShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
ftpUploadSite: {
|
||||
type: String,
|
||||
default: "n/a"
|
||||
},
|
||||
defaultGenome: {
|
||||
type: String,
|
||||
default: UploadUtils.DEFAULT_GENOME
|
||||
},
|
||||
defaultExtension: {
|
||||
type: String,
|
||||
default: UploadUtils.DEFAULT_EXTENSION
|
||||
},
|
||||
datatypesDisableAuto: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
auto: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return UploadUtils.AUTO_EXTENSION;
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
title: _l("Download from web or upload from disk"),
|
||||
historyAvailable: false,
|
||||
currentUser: null,
|
||||
listGenomes: [],
|
||||
listExtensions: []
|
||||
};
|
||||
},
|
||||
created: function() {
|
||||
this.model = new Backbone.Model({
|
||||
label: "Load Data",
|
||||
percentage: 0,
|
||||
status: "",
|
||||
onunload: function() {},
|
||||
onclick: function() {}
|
||||
});
|
||||
$(window).on("beforeunload", () => this.model.get("onunload")());
|
||||
|
||||
// load extensions
|
||||
UploadUtils.getUploadDatatypes(
|
||||
listExtensions => {
|
||||
this.listExtensions = listExtensions;
|
||||
},
|
||||
this.datatypesDisableSuto,
|
||||
this.auto
|
||||
);
|
||||
|
||||
// load genomes
|
||||
UploadUtils.getUploadGenomes(listGenomes => {
|
||||
this.listGenomes = listGenomes;
|
||||
}, this.defaultGenome);
|
||||
|
||||
this.initStateWhenHistoryReady();
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.modalShow = true;
|
||||
this.$nextTick(this.tryMountingTabs);
|
||||
},
|
||||
hide() {
|
||||
this.modalShow = false;
|
||||
},
|
||||
initStateWhenHistoryReady() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (!Galaxy.currHistoryPanel || !Galaxy.currHistoryPanel.model) {
|
||||
window.setTimeout(() => {
|
||||
this.initStateWhenHistoryReady();
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
this.historyAvailable = true;
|
||||
this.currentUser = Galaxy.user.id;
|
||||
},
|
||||
currentFtp: function() {
|
||||
return this.currentUser && this.ftpUploadSite;
|
||||
},
|
||||
/** Refresh user and current history */
|
||||
currentHistory: function() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
return this.currentUser && Galaxy.currHistoryPanel.model.get("id");
|
||||
},
|
||||
/**
|
||||
* Package API data from array of models
|
||||
* @param{Array} items - Upload items/rows filtered from a collection
|
||||
*/
|
||||
toData: function(items, history_id) {
|
||||
// create dictionary for data submission
|
||||
var data = {
|
||||
payload: {
|
||||
tool_id: "upload1",
|
||||
history_id: history_id || this.currentHistory(),
|
||||
inputs: {}
|
||||
},
|
||||
files: [],
|
||||
error_message: null
|
||||
};
|
||||
// add upload tools input data
|
||||
if (items && items.length > 0) {
|
||||
var inputs = {
|
||||
file_count: items.length,
|
||||
dbkey: items[0].get("genome", "?"),
|
||||
// sometimes extension set to "" in automated testing after first upload of
|
||||
// a session. https://github.com/galaxyproject/galaxy/issues/5169
|
||||
file_type: items[0].get("extension") || "auto"
|
||||
};
|
||||
for (var index in items) {
|
||||
var it = items[index];
|
||||
it.set("status", "running");
|
||||
if (it.get("file_size") > 0) {
|
||||
var prefix = `files_${index}|`;
|
||||
inputs[`${prefix}type`] = "upload_dataset";
|
||||
if (it.get("file_name") != "New File") {
|
||||
inputs[`${prefix}NAME`] = it.get("file_name");
|
||||
}
|
||||
inputs[`${prefix}space_to_tab`] = (it.get("space_to_tab") && "Yes") || null;
|
||||
inputs[`${prefix}to_posix_lines`] = (it.get("to_posix_lines") && "Yes") || null;
|
||||
inputs[`${prefix}dbkey`] = it.get("genome", null);
|
||||
inputs[`${prefix}file_type`] = it.get("extension", null);
|
||||
switch (it.get("file_mode")) {
|
||||
case "new":
|
||||
inputs[`${prefix}url_paste`] = it.get("url_paste");
|
||||
break;
|
||||
case "ftp":
|
||||
inputs[`${prefix}ftp_files`] = it.get("file_path");
|
||||
break;
|
||||
case "local":
|
||||
data.files.push({
|
||||
name: `${prefix}file_data`,
|
||||
file: it.get("file_data")
|
||||
});
|
||||
}
|
||||
} else if (it.get("optional")) {
|
||||
continue;
|
||||
} else {
|
||||
data.error_message = "Upload content incomplete.";
|
||||
it.set("status", "error");
|
||||
it.set("info", data.error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
data.payload.inputs = JSON.stringify(inputs);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.upload-dialog {
|
||||
width: 900px;
|
||||
}
|
||||
.upload-dialog-body {
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- container for an uploadBox -->
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<div class="upload-top">
|
||||
<div class="upload-top-info" v-html="topInfo"></div>
|
||||
</div>
|
||||
<div class="upload-box" ref="uploadBox" :style="boxStyle" :class="{ highlight: hightlightBox }">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="upload-footer" v-if="hasFooter">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
<div class="clear" v-else />
|
||||
<div class="upload-buttons">
|
||||
<slot name="buttons" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
topInfo: {
|
||||
type: String,
|
||||
default: " "
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: "upload-view-default"
|
||||
},
|
||||
hightlightBox: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFooter() {
|
||||
// https://stackoverflow.com/questions/44077277/only-show-slot-if-it-has-content/50096300#50096300
|
||||
const name = "footer";
|
||||
return !!this.$slots[name] || !!this.$scopedSlots[name];
|
||||
},
|
||||
boxStyle: function() {
|
||||
return {
|
||||
height: this.hasFooter ? "300px" : "335px"
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
import _ from "underscore";
|
||||
import Backbone from "backbone";
|
||||
import "utils/uploadbox";
|
||||
import { mount, createLocalVue } from "@vue/test-utils";
|
||||
|
||||
export function mountWithApp(component, options = {}, propsData_ = {}) {
|
||||
const app = _.defaults(options, {
|
||||
defaultExtension: "auto",
|
||||
currentFtp: () => {
|
||||
return "ftp://localhost";
|
||||
},
|
||||
model: new Backbone.Model(),
|
||||
listExtensions: [
|
||||
{ id: "ab1", text: "ab1", description: "A binary sequence file in 'ab1' format with a '.ab1'" },
|
||||
{
|
||||
id: "affybatch",
|
||||
text: "affybatch",
|
||||
description: null,
|
||||
composite_files: [
|
||||
{
|
||||
name: "%s.pheno",
|
||||
optional: false,
|
||||
description: "Phenodata tab text file"
|
||||
},
|
||||
{
|
||||
name: "%s.affybatch",
|
||||
optional: false,
|
||||
description: "AffyBatch R object saved to file"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
const propsData = _.defaults(propsData_, { app });
|
||||
const localVue = createLocalVue();
|
||||
const wrapper = mount(component, {
|
||||
propsData,
|
||||
localVue,
|
||||
attachToDocument: true,
|
||||
stubs: {
|
||||
select2: true
|
||||
}
|
||||
});
|
||||
return { wrapper, localVue };
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<invocations
|
||||
:invocationItems="invocationItems"
|
||||
:invocation-items="invocationItems"
|
||||
:loading="loading"
|
||||
headerMessage="Your most recent workflow invocations are displayed on this page."
|
||||
noInvocationsMessage="There are no workflow invocations to show."
|
||||
header-message="Your most recent workflow invocations are displayed on this page."
|
||||
no-invocations-message="There are no workflow invocations to show."
|
||||
>
|
||||
</invocations>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div id="center" class="inbound">
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
<span class="sr-only">Workflow Editor</span>
|
||||
{{ editorConfig.name }}
|
||||
<WorkflowOptions
|
||||
:canvas="isCanvas"
|
||||
@onSave="onSave"
|
||||
@onSaveAs="onSaveAs"
|
||||
@onRun="onRun"
|
||||
@onDownload="onDownload"
|
||||
@onReport="onReport"
|
||||
@onReportHelp="onReportHelp"
|
||||
@onLayout="onLayout"
|
||||
@onEdit="onEdit"
|
||||
@onAttributes="onAttributes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body" id="workflow-canvas-body" v-show="isCanvas">
|
||||
<div id="canvas-viewport" class="workflow-canvas-content">
|
||||
<div id="canvas-container" />
|
||||
</div>
|
||||
<div id="workflow-parameters-box">
|
||||
<span class="workflow-parameters-box-title">
|
||||
Workflow Parameters
|
||||
</span>
|
||||
<div id="workflow-parameters-container" />
|
||||
</div>
|
||||
<div class="workflow-overview" aria-hidden="true">
|
||||
<div class="workflow-overview-body">
|
||||
<div id="overview">
|
||||
<canvas width="0" height="0" id="overview-canvas" />
|
||||
<div id="overview-viewport" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body workflow-report-body" v-show="!isCanvas">
|
||||
<markdown-editor ref="report-editor" initial-markdown="" :onupdate="onReportUpdate" :toolbar="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WorkflowView from "mvc/workflow/workflow-view";
|
||||
import WorkflowOptions from "./Options";
|
||||
import MarkdownEditor from "components/Markdown/MarkdownEditor";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { showReportHelp } from "./reportHelp";
|
||||
|
||||
export default {
|
||||
components: { MarkdownEditor, WorkflowOptions },
|
||||
props: {
|
||||
editorConfig: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCanvas: true
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.workflowView = new WorkflowView(this.editorConfig, this.$refs["report-editor"]);
|
||||
},
|
||||
methods: {
|
||||
onDownload() {
|
||||
window.location = `${getAppRoot()}api/workflows/${this.editorConfig.id}/download?format=json-download`;
|
||||
},
|
||||
onSaveAs() {
|
||||
this.workflowView.workflow_save_as();
|
||||
},
|
||||
onLayout() {
|
||||
this.workflowView.layout_editor();
|
||||
},
|
||||
onAttributes() {
|
||||
this.workflowView.workflow.clear_active_node();
|
||||
},
|
||||
onEdit() {
|
||||
this.isCanvas = true;
|
||||
},
|
||||
onReport() {
|
||||
this.isCanvas = false;
|
||||
},
|
||||
onReportUpdate(markdown) {
|
||||
this.workflowView.report_changed(markdown);
|
||||
},
|
||||
onRun() {
|
||||
window.location = `${getAppRoot()}workflows/run?id=${this.editorConfig.id}`;
|
||||
},
|
||||
onSave() {
|
||||
this.workflowView.save_current_workflow();
|
||||
},
|
||||
onReportHelp() {
|
||||
showReportHelp();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#center {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.workflow-report-body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.workflow-overview-body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: solid gray 1px;
|
||||
border-left: solid grey 1px;
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#workflow-parameters-box {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
border: solid grey 1px;
|
||||
padding: 5px;
|
||||
background: #eeeeee;
|
||||
z-index: 20000;
|
||||
overflow: auto;
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.workflow-parameters-box-title {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#overview {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#overview-canvas {
|
||||
background: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#overview-viewport {
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border: solid blue 1px;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
canvas.dragging {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="panel-header-buttons">
|
||||
<span v-if="canvas">
|
||||
<b-button
|
||||
id="workflow-save-button"
|
||||
role="button"
|
||||
title="Save Workflow"
|
||||
variant="link"
|
||||
aria-label="Save Workflow"
|
||||
v-b-tooltip.hover
|
||||
v-b-tooltip.bottom
|
||||
@click="$emit('onSave')"
|
||||
>
|
||||
<span class="fa fa-floppy-o" />
|
||||
</b-button>
|
||||
<b-button
|
||||
id="workflow-report-button"
|
||||
role="button"
|
||||
title="Edit Report"
|
||||
variant="link"
|
||||
aria-label="Edit Report"
|
||||
v-b-tooltip.hover
|
||||
v-b-tooltip.bottom
|
||||
@click="$emit('onReport')"
|
||||
>
|
||||
<span class="fa fa-edit" />
|
||||
</b-button>
|
||||
<b-dropdown
|
||||
no-caret
|
||||
right
|
||||
id="workflow-options-button"
|
||||
role="button"
|
||||
title="Workflow Options"
|
||||
variant="link"
|
||||
aria-label="Workflow Options"
|
||||
v-b-tooltip.hover
|
||||
v-b-tooltip.bottom
|
||||
>
|
||||
<template v-slot:button-content>
|
||||
<span class="fa fa-cog" />
|
||||
</template>
|
||||
<b-dropdown-item href="#" @click="$emit('onSaveAs')">Save As</b-dropdown-item>
|
||||
<b-dropdown-item href="#" @click="$emit('onAttributes')">Edit Attributes</b-dropdown-item>
|
||||
<b-dropdown-item href="#" @click="$emit('onLayout')">Auto Layout</b-dropdown-item>
|
||||
<b-dropdown-item href="#" @click="$emit('onDownload')">Download</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<b-button
|
||||
id="workflow-run-button"
|
||||
role="button"
|
||||
title="Run Workflow"
|
||||
variant="link"
|
||||
aria-label="Run Workflow"
|
||||
v-b-tooltip.hover
|
||||
v-b-tooltip.bottom
|
||||
@click="$emit('onRun')"
|
||||
>
|
||||
<span class="fa fa-play" />
|
||||
</b-button>
|
||||
</span>
|
||||
<span v-else>
|
||||
<b-button
|
||||
id="workflow-report-help-button"
|
||||
role="button"
|
||||
title="Report Syntax Help"
|
||||
sm="size"
|
||||
variant="link"
|
||||
aria-label="Report Syntax Help"
|
||||
v-b-tooltip.hover
|
||||
v-b-tooltip.bottom
|
||||
@click="$emit('onReportHelp')"
|
||||
>
|
||||
<span class="fa fa-question" />
|
||||
</b-button>
|
||||
<b-button
|
||||
id="workflow-canvas-button"
|
||||
role="button"
|
||||
title="Return to Workflow"
|
||||
sm="size"
|
||||
variant="link"
|
||||
aria-label="Return to Workflow"
|
||||
@click="$emit('onEdit')"
|
||||
>
|
||||
<span class="fa fa-times" />
|
||||
</b-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
canvas: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dropdown-menu {
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,236 +0,0 @@
|
||||
<template>
|
||||
<div id="center" class="inbound">
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
<span class="sr-only">Workflow Editor </span>
|
||||
{{ editorConfig["name"] }}
|
||||
<div class="panel-header-buttons">
|
||||
<a
|
||||
id="workflow-run-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Run"
|
||||
aria-label="Run"
|
||||
@click="navigateToRun"
|
||||
v-show="mode == 'canvas'"
|
||||
>
|
||||
<span class="fa fa-play"></span>
|
||||
</a>
|
||||
<a
|
||||
id="workflow-save-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Save"
|
||||
aria-label="Save"
|
||||
@click="save"
|
||||
v-show="mode == 'canvas'"
|
||||
>
|
||||
<span class="fa fa-floppy-o"></span>
|
||||
</a>
|
||||
<a
|
||||
id="workflow-report-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Edit Report"
|
||||
aria-label="Edit Report"
|
||||
v-show="mode == 'canvas'"
|
||||
@click="setMode('report')"
|
||||
>
|
||||
<span class="fa fa-edit"></span>
|
||||
</a>
|
||||
<a
|
||||
id="workflow-report-help-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Report Syntax Help"
|
||||
aria-label="Report Syntax Help"
|
||||
@click="showReportHelp"
|
||||
v-show="mode == 'report'"
|
||||
>
|
||||
<span class="fa fa-question"></span>
|
||||
</a>
|
||||
<a
|
||||
id="workflow-canvas-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Edit Workflow"
|
||||
aria-label="Edit Workflow"
|
||||
@click="setMode('canvas')"
|
||||
v-show="mode == 'report'"
|
||||
>
|
||||
<span class="fa fa-sitemap fa-rotate-270"></span>
|
||||
</a>
|
||||
<a
|
||||
id="workflow-options-button"
|
||||
class="panel-header-button"
|
||||
href="javascript:void(0)"
|
||||
role="button"
|
||||
title="Workflow options"
|
||||
aria-label="Workflow options"
|
||||
ref="save-button"
|
||||
v-show="mode == 'canvas'"
|
||||
>
|
||||
<span class="fa fa-cog"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body" id="workflow-canvas-body" v-show="mode == 'canvas'">
|
||||
<div id="canvas-viewport" class="workflow-canvas-content">
|
||||
<div id="canvas-container"></div>
|
||||
</div>
|
||||
<div id="workflow-parameters-box">
|
||||
<span class="workflow-parameters-box-title">
|
||||
Workflow Parameters
|
||||
</span>
|
||||
<div id="workflow-parameters-container"></div>
|
||||
</div>
|
||||
<div class="workflow-overview" v-show="mode == 'canvas'" aria-hidden="true">
|
||||
<div class="workflow-overview-body">
|
||||
<div id="overview">
|
||||
<canvas width="0" height="0" id="overview-canvas"></canvas>
|
||||
<div id="overview-viewport"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unified-panel-body workflow-report-body" v-show="mode == 'report'">
|
||||
<markdown-editor ref="report-editor" initial-markdown="" :onupdate="onReportUpdate" :toolbar="false" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from "jquery";
|
||||
import WorkflowView from "mvc/workflow/workflow-view";
|
||||
import MarkdownEditor from "components/Markdown/MarkdownEditor";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { showReportHelp } from "./reportHelp";
|
||||
import { make_popupmenu } from "ui/popupmenu";
|
||||
|
||||
export default {
|
||||
components: { MarkdownEditor },
|
||||
props: {
|
||||
editorConfig: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mode: "canvas"
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.workflowView = new WorkflowView(this.editorConfig, this.$refs["report-editor"]);
|
||||
make_popupmenu($(this.$refs["save-button"]), {
|
||||
"Save As": () => this.workflowView.workflow_save_as(),
|
||||
"Edit Attributes": () => {
|
||||
this.workflowView.workflow.clear_active_node();
|
||||
},
|
||||
"Auto Re-layout": () => this.workflowView.layout_editor(),
|
||||
Download: {
|
||||
url: `${getAppRoot()}api/workflows/${this.editorConfig.id}/download?format=json-download`,
|
||||
action: function() {}
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setMode(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
onReportUpdate(markdown) {
|
||||
this.workflowView.report_changed(markdown);
|
||||
},
|
||||
navigateToRun() {
|
||||
window.location = `${getAppRoot()}workflows/run?id=${this.editorConfig.id}`;
|
||||
},
|
||||
save() {
|
||||
this.workflowView.save_current_workflow();
|
||||
},
|
||||
showReportHelp() {
|
||||
showReportHelp();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#center {
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.workflow-report-body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.workflow-overview-body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: solid gray 1px;
|
||||
border-left: solid grey 1px;
|
||||
}
|
||||
|
||||
#canvas-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#workflow-parameters-box {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
border: solid grey 1px;
|
||||
padding: 5px;
|
||||
background: #eeeeee;
|
||||
z-index: 20000;
|
||||
overflow: auto;
|
||||
max-width: 300px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.workflow-parameters-box-title {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#overview {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#overview-canvas {
|
||||
background: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#overview-viewport {
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border: solid blue 1px;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
canvas.dragging {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
@@ -3,12 +3,12 @@
|
||||
*/
|
||||
import Vue from "vue";
|
||||
import SidePanel from "components/Panels/SidePanel";
|
||||
import WorkflowEditor from "./WorkflowEditor";
|
||||
import Index from "./Index";
|
||||
import WorkflowPanel from "./WorkflowPanel";
|
||||
|
||||
export const mountWorkflowEditor = editorConfig => {
|
||||
const propsData = { editorConfig };
|
||||
const component = Vue.extend(WorkflowEditor);
|
||||
const component = Vue.extend(Index);
|
||||
return new component({ propsData: propsData, el: "#center" });
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ workflow_display()
|
||||
|
||||
<pre>
|
||||
\`\`\`galaxy
|
||||
job_parameters(step=qc)
|
||||
job_parameters(step="qc")
|
||||
\`\`\`
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<template v-slot:row-details="row">
|
||||
<b-card>
|
||||
<!-- set provideContext to false, since the table itself provides this information -->
|
||||
<workflow-invocation-state :invocationId="row.item.id" :provideContext="false" />
|
||||
<workflow-invocation-state :invocation-id="row.item.id" :provide-context="false" />
|
||||
</b-card>
|
||||
</template>
|
||||
<template v-slot:cell(details)="data">
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { Services } from "./services.js";
|
||||
import { Services } from "./services";
|
||||
export default {
|
||||
props: ["workflow"],
|
||||
data() {
|
||||
|
||||
@@ -88,7 +88,7 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { Services } from "./services.js";
|
||||
import { Services } from "./services";
|
||||
import Tags from "components/Common/Tags";
|
||||
import WorkflowDropdown from "./WorkflowDropdown";
|
||||
import UtcDate from "components/UtcDate";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<markdown :markdown-config="markdownConfig" v-if="!edit"></markdown>
|
||||
<markdown-editor
|
||||
:initialMarkdown="markdownConfig.invocation_markdown"
|
||||
:initial-markdown="markdownConfig.invocation_markdown"
|
||||
:onupdate="onupdate"
|
||||
v-else
|
||||
></markdown-editor>
|
||||
|
||||
+16
-16
@@ -4,7 +4,7 @@
|
||||
<b>Workflow Invocation State</b>
|
||||
<span v-if="createdTime"> (invoked at {{ createdTime }})</span>
|
||||
</span>
|
||||
<div v-bind:class="{ 'context-wrapped': provideContext }">
|
||||
<div :class="{ 'context-wrapped': provideContext }">
|
||||
<div>
|
||||
Step Scheduling
|
||||
<span
|
||||
@@ -15,25 +15,25 @@
|
||||
@click="cancelWorkflowScheduling"
|
||||
></span>
|
||||
<div v-if="!stepCount">
|
||||
<progress-bar note="Loading step state summary" :loading="true" :infoProgress="1" />
|
||||
<progress-bar note="Loading step state summary" :loading="true" :info-progress="1" />
|
||||
</div>
|
||||
<div v-else-if="invocationState == 'cancelled'">
|
||||
<progress-bar
|
||||
note="Invocation scheduling cancelled - expected jobs and outputs may not be generated"
|
||||
:errorProgress="1"
|
||||
:error-progress="1"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="invocationState == 'failed'">
|
||||
<progress-bar
|
||||
note="Invocation scheduling failed - Galaxy administrator may have additional details in logs"
|
||||
:errorProgress="1"
|
||||
:error-progress="1"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<progress-bar
|
||||
:note="stepStatesStr"
|
||||
:okProgress="stepScheduledPercent"
|
||||
:newProgress="stepOtherPercent"
|
||||
:ok-progress="stepScheduledPercent"
|
||||
:new-progress="stepOtherPercent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,22 +42,22 @@
|
||||
<div v-if="jobCount">
|
||||
<progress-bar
|
||||
:note="jobStatesStr"
|
||||
:okProgress="okPercent"
|
||||
:runningProgress="runningPercent"
|
||||
:newProgress="otherPercent"
|
||||
:errorProgress="errorPercent"
|
||||
:okMessage="okMessage"
|
||||
:runningMessage="runningMessage"
|
||||
:errorMessage="errorMessage"
|
||||
:ok-progress="okPercent"
|
||||
:running-progress="runningPercent"
|
||||
:new-progress="otherPercent"
|
||||
:error-progress="errorPercent"
|
||||
:ok-message="okMessage"
|
||||
:running-message="runningMessage"
|
||||
:error-message="errorMessage"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<progress-bar note="Loading job summary" :loading="true" :infoProgress="1" />
|
||||
<progress-bar note="Loading job summary" :loading="true" :info-progress="1" />
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="invocationSchedulingTerminal && jobStatesTerminal">
|
||||
<a v-bind:href="invocationLink">View Invocation Report</a>
|
||||
<a class="fa fa-print" v-bind:href="invocationPdfLink"></a>
|
||||
<a :href="invocationLink">View Invocation Report</a>
|
||||
<a class="fa fa-print" :href="invocationPdfLink"></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<invocations
|
||||
:invocationItems="invocationItems"
|
||||
:invocation-items="invocationItems"
|
||||
:loading="loading"
|
||||
headerMessage="Workflow invocations that are still being scheduled are displayed on this page."
|
||||
noInvocationsMessage="There are no scheduling workflow invocations to show currently."
|
||||
:ownerGrid="false"
|
||||
header-message="Workflow invocations that are still being scheduled are displayed on this page."
|
||||
no-invocations-message="There are no scheduling workflow invocations to show currently."
|
||||
:owner-grid="false"
|
||||
>
|
||||
</invocations>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<base-grid :is-loaded="isLoaded" :columns="columns" id="data-tables-grid">
|
||||
<template v-slot:title>
|
||||
Current data table registry contains {{ rows.length }} data tables
|
||||
</template>
|
||||
<template v-slot:title> Current data table registry contains {{ rows.length }} data tables </template>
|
||||
<template slot:rows v-for="(row, index) in rows">
|
||||
<tr :key="row.id" :class="[index % 2 === 0 ? 'tr' : 'odd_row']">
|
||||
<td>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-if="status !== 'error'"
|
||||
:columns="columns"
|
||||
:rows="dataTypes"
|
||||
:isLoaded="isDataLoaded"
|
||||
:is-loaded="isDataLoaded"
|
||||
id="data-types-grid"
|
||||
>
|
||||
<template v-slot:title>
|
||||
|
||||
@@ -102,7 +102,7 @@ import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import DependencyIndexMixin from "./DependencyIndexMixin";
|
||||
import ContainerResolutionDetails from "./ContainerResolutionDetails";
|
||||
import { getContainerResolutionToolbox, resolveContainersWithInstall } from "../AdminServices.js";
|
||||
import { getContainerResolutionToolbox, resolveContainersWithInstall } from "../AdminServices";
|
||||
import { DESCRIPTION } from "./ContainerResolver";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-nav tabs>
|
||||
<b-nav-item v-bind:active="mode == 'dependencies'" @click="setMode('dependencies')"
|
||||
>Dependencies</b-nav-item
|
||||
>
|
||||
<b-nav-item v-bind:active="mode == 'containers'" @click="setMode('containers')">Containers</b-nav-item>
|
||||
<b-nav-item v-bind:active="mode == 'unused'" @click="setMode('unused')">Unused</b-nav-item>
|
||||
<b-nav-item :active="mode == 'dependencies'" @click="setMode('dependencies')">Dependencies</b-nav-item>
|
||||
<b-nav-item :active="mode == 'containers'" @click="setMode('containers')">Containers</b-nav-item>
|
||||
<b-nav-item :active="mode == 'unused'" @click="setMode('unused')">Unused</b-nav-item>
|
||||
</b-nav>
|
||||
<resolution-index v-if="mode == 'dependencies'" />
|
||||
<container-index v-else-if="mode == 'containers'" />
|
||||
|
||||
@@ -85,7 +85,7 @@ import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import DependencyIndexMixin from "./DependencyIndexMixin";
|
||||
import ResolutionDetails from "./ResolutionDetails";
|
||||
import { getToolboxDependencies, installDependencies, uninstallDependencies } from "../AdminServices.js";
|
||||
import { getToolboxDependencies, installDependencies, uninstallDependencies } from "../AdminServices";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import DependencyIndexWrapper from "./DependencyIndexWrapper";
|
||||
import { getDependencyUnusedPaths, deletedUnusedPaths } from "../AdminServices.js";
|
||||
import { getDependencyUnusedPaths, deletedUnusedPaths } from "../AdminServices";
|
||||
|
||||
export default {
|
||||
components: { DependencyIndexWrapper },
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import BaseList from "./BaseList";
|
||||
import { getDisplayApplications, reloadDisplayApplications } from "./AdminServices.js";
|
||||
import { getDisplayApplications, reloadDisplayApplications } from "./AdminServices";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getErrorStack } from "./AdminServices.js";
|
||||
import { getErrorStack } from "./AdminServices";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<h5>Command Line</h5>
|
||||
<pre class="text-white bg-dark"><code class="break-word">{{ commandLine }}</code></pre>
|
||||
<h5>Job Parameters</h5>
|
||||
<job-parameters :jobId="jobId" :includeTitle="false" />
|
||||
<job-parameters :job-id="jobId" :include-title="false" />
|
||||
<h5>Job Metrics</h5>
|
||||
<job-metrics :jobId="jobId" :includeTitle="false" />
|
||||
<job-metrics :job-id="jobId" :include-title="false" />
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
Unfinished Jobs: These jobs are unfinished and have had their state updated in the previous
|
||||
{{ cutoffDisplay }} seconds.
|
||||
</template>
|
||||
<template v-slot:head(selected)="{ rowSelected }">
|
||||
<template v-slot:head(selected)>
|
||||
<b-form-checkbox
|
||||
v-model="allSelected"
|
||||
:indeterminate="indeterminate"
|
||||
@@ -128,12 +128,12 @@
|
||||
></b-form-checkbox>
|
||||
</template>
|
||||
<template v-slot:cell(job_info)="data">
|
||||
<b-link :href="data.value.info_url" v-on:click.prevent="clickJobInfo(data.value.id)">
|
||||
<b-link :href="data.value.info_url" @click.prevent="clickJobInfo(data.value.id)">
|
||||
{{ data.value.id }}
|
||||
</b-link>
|
||||
</template>
|
||||
<template v-slot:row-details="row">
|
||||
<job-details :commandLine="row.item.command_line" :jobId="row.item.jobId" />
|
||||
<job-details :command-line="row.item.command_line" :job-id="row.item.jobId" />
|
||||
</template>
|
||||
</b-table>
|
||||
<b-alert v-if="!recentJobsItemsComputed.length" variant="secondary" show>
|
||||
@@ -155,7 +155,7 @@
|
||||
Recent Jobs: These jobs have completed in the previous {{ cutoffDisplay }} seconds.
|
||||
</template>
|
||||
<template v-slot:cell(job_info)="data">
|
||||
<b-link :href="data.value.info_url" v-on:click.prevent="clickJobInfo(data.value.id)">
|
||||
<b-link :href="data.value.info_url" @click.prevent="clickJobInfo(data.value.id)">
|
||||
{{ data.value.id }}
|
||||
</b-link>
|
||||
</template>
|
||||
@@ -163,7 +163,7 @@
|
||||
<utc-date :date="data.value" mode="elapsed" />
|
||||
</template>
|
||||
<template v-slot:row-details="row">
|
||||
<job-details :commandLine="row.item.command_line" :jobId="row.item.id" />
|
||||
<job-details :command-line="row.item.command_line" :job-id="row.item.id" />
|
||||
</template>
|
||||
</b-table>
|
||||
<b-modal ref="job-info-modal" scrollable hide-header ok-only @hidden="resetModalContents">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import BaseList from "./BaseList";
|
||||
import { getInstalledRepositories, resetRepositoryMetadata } from "./AdminServices.js";
|
||||
import { getInstalledRepositories, resetRepositoryMetadata } from "./AdminServices";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
<div v-for="idp in oidc_idps" :key="idp" style="margin:0.5em">
|
||||
<span v-if="oidc_idps_icons[idp]">
|
||||
<b-button variant="link" class="d-block mt-3" @click="submitOIDCLogin(idp)">
|
||||
<img v-bind:src="oidc_idps_icons[idp]" height="45" v-bind:alt="idp" />
|
||||
<img :src="oidc_idps_icons[idp]" height="45" :alt="idp" />
|
||||
</b-button>
|
||||
</span>
|
||||
<span v-else>
|
||||
<b-button class="d-block mt-3" @click="submitOIDCLogin(idp)">
|
||||
<i v-bind:class="oidc_idps[idp]" /> Sign in with
|
||||
<i :class="oidc_idps[idp]" /> Sign in with
|
||||
{{ idp.charAt(0).toUpperCase() + idp.slice(1) }}
|
||||
</b-button>
|
||||
</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</h4>
|
||||
</template>
|
||||
<table>
|
||||
<tr v-for="(xref, index) in xrefs" v-bind:key="index">
|
||||
<tr v-for="(xref, index) in xrefs" :key="index">
|
||||
<th>- {{ xref.reftype }}:</th>
|
||||
<td>
|
||||
<template v-if="xref.reftype == 'bio.tools'">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import Upload from "mvc/upload/upload-view";
|
||||
import UploadModal from "components/Upload/UploadModal";
|
||||
import _l from "utils/localization";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { getAppRoot } from "onload";
|
||||
import Vue from "vue";
|
||||
import ToolBox from "../../components/Panels/ToolBox.vue";
|
||||
import SidePanel from "../../components/Panels/SidePanel.vue";
|
||||
import ToolBox from "../../components/Panels/ToolBox";
|
||||
import SidePanel from "../../components/Panels/SidePanel";
|
||||
import { mountVueComponent } from "../../utils/mountVueComponent";
|
||||
|
||||
const ToolPanel = Backbone.View.extend({
|
||||
@@ -13,14 +14,23 @@ const ToolPanel = Backbone.View.extend({
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const appRoot = getAppRoot();
|
||||
|
||||
// add uploader button to Galaxy object
|
||||
Galaxy.upload = new Upload({
|
||||
upload_path: Galaxy.config.nginx_upload_path || `${appRoot}api/tools`,
|
||||
chunk_upload_size: Galaxy.config.chunk_upload_size,
|
||||
ftp_upload_site: Galaxy.config.ftp_upload_site,
|
||||
default_genome: Galaxy.config.default_genome,
|
||||
default_extension: Galaxy.config.default_extension
|
||||
});
|
||||
// add upload modal
|
||||
const modalInstance = Vue.extend(UploadModal);
|
||||
const propsData = {
|
||||
uploadPath: Galaxy.config.nginx_upload_path || `${appRoot}api/tools`,
|
||||
chunkUploadSize: Galaxy.config.chunk_upload_size,
|
||||
ftpUploadSite: Galaxy.config.ftp_upload_site,
|
||||
defaultGenome: Galaxy.config.default_genome,
|
||||
defaultExtension: Galaxy.config.default_extension
|
||||
};
|
||||
const vm = document.createElement("div");
|
||||
$("body").append(vm);
|
||||
const upload = new modalInstance({
|
||||
propsData: propsData
|
||||
}).$mount(vm);
|
||||
|
||||
// attach upload entrypoint to Galaxy object
|
||||
Galaxy.upload = upload;
|
||||
|
||||
// components for panel definition
|
||||
this.model = new Backbone.Model({
|
||||
|
||||
@@ -14,12 +14,26 @@ function logoutClick() {
|
||||
const galaxy = getGalaxyInstance();
|
||||
const session_csrf_token = galaxy.session_csrf_token;
|
||||
const url = `${galaxy.root}user/logout?session_csrf_token=${session_csrf_token}`;
|
||||
axios.get(url).then(() => {
|
||||
if (galaxy.user) {
|
||||
galaxy.user.clearSessionStorage();
|
||||
}
|
||||
window.top.location.href = `${galaxy.root}login?logout=true`;
|
||||
});
|
||||
axios
|
||||
.get(url)
|
||||
.then(() => {
|
||||
if (galaxy.user) {
|
||||
galaxy.user.clearSessionStorage();
|
||||
}
|
||||
// Check if we need to logout of OIDC IDP
|
||||
if (galaxy.config.enable_oidc) {
|
||||
return axios.get(`${galaxy.root}authnz/logout`);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.data && response.data.redirect_uri) {
|
||||
window.top.location.href = response.data.redirect_uri;
|
||||
} else {
|
||||
window.top.location.href = `${galaxy.root}root/login?is_logout_redirect=true`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const Collection = Backbone.Collection.extend({
|
||||
|
||||
@@ -198,7 +198,7 @@ var SearchableModelMixin = {
|
||||
var attrVal = this.get(attrKey);
|
||||
//this.debug( 'searchAttribute', attrKey, attrVal, searchFor );
|
||||
// bail if empty searchFor or unsearchable values
|
||||
if (!searchFor || (attrVal === undefined || attrVal === null)) {
|
||||
if (!searchFor || attrVal === undefined || attrVal === null) {
|
||||
return false;
|
||||
}
|
||||
// pass to sep. fn for deep search of array attributes
|
||||
|
||||
@@ -195,7 +195,10 @@ export default Backbone.Model.extend({
|
||||
_fieldBoolean: function(input_def) {
|
||||
return new Ui.RadioButton.View({
|
||||
id: `field-${input_def.id}`,
|
||||
data: [{ label: "Yes", value: "true" }, { label: "No", value: "false" }],
|
||||
data: [
|
||||
{ label: "Yes", value: "true" },
|
||||
{ label: "No", value: "false" }
|
||||
],
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@ export default Backbone.View.extend({
|
||||
|
||||
/** Checks if the limit has been reached */
|
||||
_done: function() {
|
||||
var done = _.size(this.content_list) > this.max;
|
||||
var done = _.size(this.content_list) >= this.max;
|
||||
this.$message[done ? "show" : "hide"]();
|
||||
return done;
|
||||
},
|
||||
|
||||
@@ -176,7 +176,10 @@ const View = Backbone.View.extend({
|
||||
display: "radio",
|
||||
ignore: "__ignore__",
|
||||
value: "__ignore__",
|
||||
options: [["Yes", options.job_id], ["No", "__ignore__"]],
|
||||
options: [
|
||||
["Yes", options.job_id],
|
||||
["No", "__ignore__"]
|
||||
],
|
||||
help: help
|
||||
});
|
||||
}
|
||||
@@ -199,7 +202,10 @@ const View = Backbone.View.extend({
|
||||
display: "radio",
|
||||
ignore: "__ignore__",
|
||||
value: "__ignore__",
|
||||
options: [["No", false], ["Yes", true]]
|
||||
options: [
|
||||
["No", false],
|
||||
["Yes", true]
|
||||
]
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -77,9 +77,12 @@ var View = Backbone.View.extend({
|
||||
saveRulesFn: rules => this._handleRulesSave(rules),
|
||||
initialRules: value
|
||||
};
|
||||
ListCollectionCreator.ruleBasedCollectionCreatorModal(elements, elementsType, importType, options).done(
|
||||
() => {}
|
||||
);
|
||||
ListCollectionCreator.ruleBasedCollectionCreatorModal(
|
||||
elements,
|
||||
elementsType,
|
||||
importType,
|
||||
options
|
||||
).done(() => {});
|
||||
},
|
||||
|
||||
_handleRulesSave: function(rules) {
|
||||
|
||||
@@ -211,18 +211,17 @@ var View = Backbone.View.extend({
|
||||
return `
|
||||
${_.escape(result.text)}
|
||||
<div>
|
||||
${_.reduce(
|
||||
filteredTags.slice(0, 5),
|
||||
(memo, tag) => {
|
||||
const tagColors = keyedColorScheme(tag.slice(5));
|
||||
return `${memo} <div style="background-color: ${tagColors.primary}; color: ${
|
||||
tagColors.contrasting
|
||||
}; border: 1px solid ${
|
||||
tagColors.darker
|
||||
}" class="badge badge-primary badge-tags">${_.escape(tag)}</div>`;
|
||||
},
|
||||
""
|
||||
)}
|
||||
${filteredTags.slice(0, 5).reduce((memo, tag) => {
|
||||
const tagColors = keyedColorScheme(tag);
|
||||
const isNametag = tag.indexOf("name:") === 0;
|
||||
const tagDisplayText = isNametag ? `#${tag.slice(5)}` : tag;
|
||||
const styleBlock = isNametag
|
||||
? `style="background-color: ${tagColors.primary}; color: ${tagColors.contrasting}; border: 1px solid ${tagColors.darker}"`
|
||||
: "";
|
||||
return `${memo} <div ${styleBlock} class="badge badge-primary badge-tags">${_.escape(
|
||||
tagDisplayText
|
||||
)}</div>`;
|
||||
}, "")}
|
||||
${extraTagWarning}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
/** Renders the collection uploader rows */
|
||||
import _l from "utils/localization";
|
||||
import $ from "jquery";
|
||||
import _ from "underscore";
|
||||
import Backbone from "backbone";
|
||||
import Utils from "utils/utils";
|
||||
import UploadSettings from "mvc/upload/upload-settings";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
export default Backbone.View.extend({
|
||||
/** Dictionary of upload states and associated icons */
|
||||
status_classes: {
|
||||
init: "upload-icon-button fa fa-trash-o",
|
||||
queued: "upload-icon fa fa-spinner fa-spin",
|
||||
running: "upload-icon fa fa-spinner fa-spin",
|
||||
success: "upload-icon-button fa fa-check",
|
||||
error: "upload-icon-button fa fa-exclamation-triangle"
|
||||
},
|
||||
|
||||
import UploadBoxRow from "mvc/upload/uploadbox-row";
|
||||
export default UploadBoxRow.extend({
|
||||
initialize: function(app, options) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
@@ -32,17 +20,7 @@ export default Backbone.View.extend({
|
||||
this.$progress_bar = this.$(".upload-progress-bar");
|
||||
this.$percentage = this.$(".upload-percentage");
|
||||
|
||||
// append popup to settings icon
|
||||
this.settings = new Popover({
|
||||
title: _l("Upload configuration"),
|
||||
container: this.$(".upload-settings"),
|
||||
placement: "bottom"
|
||||
});
|
||||
|
||||
// identify default genome and extension values
|
||||
// TODO: These appear unused.
|
||||
//var default_genome = this.app.select_genome.value();
|
||||
//var default_extension = this.app.select_extension.value();
|
||||
this._setupSettings();
|
||||
|
||||
// handle click event
|
||||
this.$symbol.on("click", () => {
|
||||
@@ -58,18 +36,7 @@ export default Backbone.View.extend({
|
||||
});
|
||||
|
||||
// model events
|
||||
this.listenTo(this.model, "change:percentage", () => {
|
||||
self._refreshPercentage();
|
||||
});
|
||||
this.listenTo(this.model, "change:status", () => {
|
||||
self._refreshStatus();
|
||||
});
|
||||
this.listenTo(this.model, "change:info", () => {
|
||||
self._refreshInfo();
|
||||
});
|
||||
this.listenTo(this.model, "change:file_size", () => {
|
||||
self._refreshFileSize();
|
||||
});
|
||||
this._setupUploadBoxListeners();
|
||||
this.listenTo(this.model, "remove", () => {
|
||||
self.remove();
|
||||
});
|
||||
@@ -102,23 +69,6 @@ export default Backbone.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh info text */
|
||||
_refreshInfo: function() {
|
||||
var info = this.model.get("info");
|
||||
if (info) {
|
||||
this.$info_text.html(`<strong>Failed: </strong>${info}`).show();
|
||||
} else {
|
||||
this.$info_text.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh percentage status */
|
||||
_refreshPercentage: function() {
|
||||
var percentage = parseInt(this.model.get("percentage"));
|
||||
this.$progress_bar.css({ width: `${percentage}%` });
|
||||
this.$percentage.html(percentage != 100 ? `${percentage}%` : "Adding to history...");
|
||||
},
|
||||
|
||||
/** Refresh status */
|
||||
_refreshStatus: function() {
|
||||
var status = this.model.get("status");
|
||||
@@ -129,31 +79,7 @@ export default Backbone.View.extend({
|
||||
this.model.set("enabled", status == "init");
|
||||
var enabled = this.model.get("enabled");
|
||||
this.$text_content.attr("disabled", !enabled);
|
||||
if (status == "success") {
|
||||
this.$el.addClass("table-success");
|
||||
this.$percentage.html("100%");
|
||||
}
|
||||
if (status == "error") {
|
||||
this.$el.addClass("table-danger");
|
||||
this.$info_progress.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh file size */
|
||||
_refreshFileSize: function() {
|
||||
this.$size.html(Utils.bytesToString(this.model.get("file_size")));
|
||||
},
|
||||
|
||||
/** Remove row */
|
||||
_removeRow: function() {
|
||||
if (["init", "success", "error"].indexOf(this.model.get("status")) !== -1) {
|
||||
this.app.collection.remove(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
/** Attach file info popup */
|
||||
_showSettings: function() {
|
||||
this.settings.show(new UploadSettings(this).$el);
|
||||
this._renderStatusType(status);
|
||||
},
|
||||
|
||||
/** View template */
|
||||
|
||||
@@ -1,497 +0,0 @@
|
||||
/** Renders contents of the collection uploader */
|
||||
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import UploadModel from "mvc/upload/upload-model";
|
||||
import UploadRow from "mvc/upload/collection/collection-row";
|
||||
import UploadFtp from "mvc/upload/upload-ftp";
|
||||
import UploadExtension from "mvc/upload/upload-extension";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
import Select from "mvc/ui/ui-select";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import "utils/uploadbox";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
// current upload size in bytes
|
||||
upload_size: 0,
|
||||
|
||||
// contains upload row models
|
||||
collection: new UploadModel.Collection(),
|
||||
|
||||
// keeps track of the current uploader state
|
||||
counter: {
|
||||
announce: 0,
|
||||
success: 0,
|
||||
error: 0,
|
||||
running: 0,
|
||||
reset: function() {
|
||||
this.announce = this.success = this.error = this.running = 0;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function(app) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.options = app.options;
|
||||
this.list_extensions = app.list_extensions;
|
||||
this.list_genomes = app.list_genomes;
|
||||
this.ui_button = app.ui_button;
|
||||
this.ftp_upload_site = app.currentFtp();
|
||||
this.setElement(this._template());
|
||||
|
||||
// append buttons to dom
|
||||
this.btnLocal = new Ui.Button({
|
||||
id: "btn-local",
|
||||
title: _l("Choose local files"),
|
||||
onclick: function() {
|
||||
self.uploadbox.select();
|
||||
},
|
||||
icon: "fa fa-laptop"
|
||||
});
|
||||
this.btnFtp = new Ui.Button({
|
||||
id: "btn-ftp",
|
||||
title: _l("Choose FTP files"),
|
||||
onclick: function() {
|
||||
self._eventFtp();
|
||||
},
|
||||
icon: "fa fa-folder-open-o"
|
||||
});
|
||||
this.btnCreate = new Ui.Button({
|
||||
id: "btn-new",
|
||||
title: "Paste/Fetch data",
|
||||
onclick: function() {
|
||||
self._eventCreate();
|
||||
},
|
||||
icon: "fa fa-edit"
|
||||
});
|
||||
this.btnStart = new Ui.Button({
|
||||
id: "btn-start",
|
||||
title: _l("Start"),
|
||||
onclick: function() {
|
||||
self._eventStart();
|
||||
}
|
||||
});
|
||||
this.btnBuild = new Ui.Button({
|
||||
id: "btn-build",
|
||||
title: _l("Build"),
|
||||
onclick: function() {
|
||||
self._eventBuild();
|
||||
}
|
||||
});
|
||||
this.btnStop = new Ui.Button({
|
||||
id: "btn-stop",
|
||||
title: _l("Pause"),
|
||||
onclick: function() {
|
||||
self._eventStop();
|
||||
}
|
||||
});
|
||||
this.btnReset = new Ui.Button({
|
||||
id: "btn-reset",
|
||||
title: _l("Reset"),
|
||||
onclick: function() {
|
||||
self._eventReset();
|
||||
}
|
||||
});
|
||||
this.btnClose = new Ui.Button({
|
||||
id: "btn-close",
|
||||
title: _l("Close"),
|
||||
onclick: function() {
|
||||
self.app.modal.hide();
|
||||
}
|
||||
});
|
||||
_.each(
|
||||
[
|
||||
this.btnLocal,
|
||||
this.btnFtp,
|
||||
this.btnCreate,
|
||||
this.btnStop,
|
||||
this.btnReset,
|
||||
this.btnStart,
|
||||
this.btnBuild,
|
||||
this.btnClose
|
||||
],
|
||||
button => {
|
||||
self.$(".upload-buttons").prepend(button.$el);
|
||||
}
|
||||
);
|
||||
|
||||
// file upload
|
||||
this.uploadbox = this.$(".upload-box").uploadbox({
|
||||
url: this.app.options.upload_path,
|
||||
announce: function(index, file) {
|
||||
self._eventAnnounce(index, file);
|
||||
},
|
||||
initialize: function(index) {
|
||||
return self.app.toData([self.collection.get(index)], self.history_id);
|
||||
},
|
||||
progress: function(index, percentage) {
|
||||
self._eventProgress(index, percentage);
|
||||
},
|
||||
success: function(index, message) {
|
||||
self._eventSuccess(index, message);
|
||||
},
|
||||
error: function(index, message) {
|
||||
self._eventError(index, message);
|
||||
},
|
||||
complete: function() {
|
||||
self._eventComplete();
|
||||
},
|
||||
ondragover: function() {
|
||||
self.$(".upload-box").addClass("highlight");
|
||||
},
|
||||
ondragleave: function() {
|
||||
self.$(".upload-box").removeClass("highlight");
|
||||
}
|
||||
});
|
||||
|
||||
// add ftp file viewer
|
||||
this.ftp = new Popover({
|
||||
title: _l("FTP files"),
|
||||
container: this.btnFtp.$el
|
||||
});
|
||||
|
||||
// select extension
|
||||
this.select_extension = new Select.View({
|
||||
container: this.$(".upload-footer-extension"),
|
||||
data: _.filter(this.list_extensions, ext => !ext.composite_files),
|
||||
value: this.options.default_extension,
|
||||
onchange: function(extension) {
|
||||
self.updateExtension(extension);
|
||||
}
|
||||
});
|
||||
|
||||
this.collectionType = "list";
|
||||
this.select_collection = new Select.View({
|
||||
container: this.$(".upload-footer-collection-type"),
|
||||
data: [
|
||||
{ id: "list", text: "List" },
|
||||
{ id: "paired", text: "Paired" },
|
||||
{ id: "list:paired", text: "List of Pairs" }
|
||||
],
|
||||
value: "list",
|
||||
onchange: function(collectionType) {
|
||||
self.updateCollectionType(collectionType);
|
||||
}
|
||||
});
|
||||
|
||||
// handle extension info popover
|
||||
this.$(".upload-footer-extension-info")
|
||||
.on("click", e => {
|
||||
new UploadExtension({
|
||||
$el: $(e.target),
|
||||
title: self.select_extension.text(),
|
||||
extension: self.select_extension.value(),
|
||||
list: self.list_extensions,
|
||||
placement: "top"
|
||||
});
|
||||
})
|
||||
.on("mousedown", e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// genome extension
|
||||
this.select_genome = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".upload-footer-genome"),
|
||||
data: this.list_genomes,
|
||||
value: this.options.default_genome,
|
||||
onchange: function(genome) {
|
||||
self.updateGenome(genome);
|
||||
}
|
||||
});
|
||||
|
||||
// events
|
||||
this.collection.on("remove", model => {
|
||||
self._eventRemove(model);
|
||||
});
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
/** A new file has been dropped/selected through the uploadbox plugin */
|
||||
_eventAnnounce: function(index, file) {
|
||||
this.counter.announce++;
|
||||
var new_model = new UploadModel.Model({
|
||||
id: index,
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
file_mode: file.mode || "local",
|
||||
file_path: file.path,
|
||||
file_data: file,
|
||||
extension: this.select_extension.value(),
|
||||
genome: this.select_genome.value()
|
||||
});
|
||||
this.collection.add(new_model);
|
||||
var upload_row = new UploadRow(this, { model: new_model });
|
||||
this.$(".upload-table > tbody:first").append(upload_row.$el);
|
||||
this._updateScreen();
|
||||
upload_row.render();
|
||||
},
|
||||
|
||||
/** Progress */
|
||||
_eventProgress: function(index, percentage) {
|
||||
var it = this.collection.get(index);
|
||||
it.set("percentage", percentage);
|
||||
this.ui_button.model.set("percentage", this._uploadPercentage(percentage, it.get("file_size")));
|
||||
},
|
||||
|
||||
/** Success */
|
||||
_eventSuccess: function(index, message) {
|
||||
// var hdaId = message["outputs"][0]["id"];
|
||||
var hids = _.pluck(message["outputs"], "hid");
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "success", hids: hids });
|
||||
this.ui_button.model.set("percentage", this._uploadPercentage(100, it.get("file_size")));
|
||||
this.upload_completed += it.get("file_size") * 100;
|
||||
this.counter.announce--;
|
||||
this.counter.success++;
|
||||
this._updateScreen();
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
/** Error */
|
||||
_eventError: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "error", info: message });
|
||||
this.ui_button.model.set({
|
||||
percentage: this._uploadPercentage(100, it.get("file_size")),
|
||||
status: "danger"
|
||||
});
|
||||
this.upload_completed += it.get("file_size") * 100;
|
||||
this.counter.announce--;
|
||||
this.counter.error++;
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
/** Queue is done */
|
||||
_eventComplete: function() {
|
||||
this.collection.each(model => {
|
||||
model.get("status") == "queued" && model.set("status", "init");
|
||||
});
|
||||
this.counter.running = 0;
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
_eventBuild: function() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
var allHids = [];
|
||||
_.forEach(this.collection.models, upload => {
|
||||
allHids.push.apply(allHids, upload.get("hids"));
|
||||
});
|
||||
var models = _.map(allHids, hid => Galaxy.currHistoryPanel.collection.getByHid(hid));
|
||||
var selection = new Galaxy.currHistoryPanel.collection.constructor(models);
|
||||
// I'm building the selection wrong because I need to set this historyId directly.
|
||||
selection.historyId = Galaxy.currHistoryPanel.collection.historyId;
|
||||
Galaxy.currHistoryPanel.buildCollection(this.collectionType, selection, true);
|
||||
this.counter.running = 0;
|
||||
this._updateScreen();
|
||||
this._eventReset();
|
||||
this.app.modal.hide();
|
||||
},
|
||||
|
||||
/** Remove model from upload list */
|
||||
_eventRemove: function(model) {
|
||||
var status = model.get("status");
|
||||
if (status == "success") {
|
||||
this.counter.success--;
|
||||
} else if (status == "error") {
|
||||
this.counter.error--;
|
||||
} else {
|
||||
this.counter.announce--;
|
||||
}
|
||||
this.uploadbox.remove(model.id);
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
//
|
||||
// events triggered by this view
|
||||
//
|
||||
|
||||
/** Show/hide ftp popup */
|
||||
_eventFtp: function() {
|
||||
var self = this;
|
||||
this.ftp.show(
|
||||
new UploadFtp({
|
||||
collection: this.collection,
|
||||
ftp_upload_site: this.ftp_upload_site,
|
||||
onadd: function(ftp_file) {
|
||||
return self.uploadbox.add([
|
||||
{
|
||||
mode: "ftp",
|
||||
name: ftp_file.path,
|
||||
size: ftp_file.size,
|
||||
path: ftp_file.path
|
||||
}
|
||||
]);
|
||||
},
|
||||
onremove: function(model_index) {
|
||||
self.collection.remove(model_index);
|
||||
}
|
||||
}).$el
|
||||
);
|
||||
},
|
||||
|
||||
/** Create a new file */
|
||||
_eventCreate: function() {
|
||||
this.uploadbox.add([{ name: "New File", size: 0, mode: "new" }]);
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
if (this.counter.announce == 0 || this.counter.running > 0) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
this.upload_size = 0;
|
||||
this.upload_completed = 0;
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "init") {
|
||||
model.set("status", "queued");
|
||||
self.upload_size += model.get("file_size");
|
||||
}
|
||||
});
|
||||
this.ui_button.model.set({ percentage: 0, status: "success" });
|
||||
this.counter.running = this.counter.announce;
|
||||
this.history_id = this.app.currentHistory();
|
||||
this.uploadbox.start();
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
/** Pause upload process */
|
||||
_eventStop: function() {
|
||||
if (this.counter.running > 0) {
|
||||
this.ui_button.model.set("status", "info");
|
||||
$(".upload-top-info").html("Queue will pause after completing the current file...");
|
||||
this.uploadbox.stop();
|
||||
}
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.counter.running == 0) {
|
||||
this.collection.reset();
|
||||
this.counter.reset();
|
||||
this.uploadbox.reset();
|
||||
this.select_extension.value(this.options.default_extension);
|
||||
this.select_genome.value(this.options.default_genome);
|
||||
this.ui_button.model.set("percentage", 0);
|
||||
this._updateScreen();
|
||||
}
|
||||
},
|
||||
|
||||
/** Update extension for all models */
|
||||
updateExtension: function(extension, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("extension") == self.options.default_extension || !defaults_only)
|
||||
) {
|
||||
model.set("extension", extension);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Update collection type */
|
||||
updateCollectionType: function(collectionType) {
|
||||
this.collectionType = collectionType;
|
||||
},
|
||||
|
||||
/** Update genome for all models */
|
||||
updateGenome: function(genome, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("genome") == self.options.default_genome || !defaults_only)
|
||||
) {
|
||||
model.set("genome", genome);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Set screen */
|
||||
_updateScreen: function() {
|
||||
var message = "";
|
||||
if (this.counter.announce == 0) {
|
||||
if (this.uploadbox.compatible()) {
|
||||
message = " ";
|
||||
} else {
|
||||
message =
|
||||
"Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.";
|
||||
}
|
||||
} else {
|
||||
if (this.counter.running == 0) {
|
||||
message = `You added ${this.counter.announce} file(s) to the queue. Add more files or click 'Start' to proceed.`;
|
||||
} else {
|
||||
message = `Please wait...${this.counter.announce} out of ${this.counter.running} remaining.`;
|
||||
}
|
||||
}
|
||||
this.$(".upload-top-info").html(message);
|
||||
var enable_reset =
|
||||
this.counter.running == 0 && this.counter.announce + this.counter.success + this.counter.error > 0;
|
||||
var enable_start = this.counter.running == 0 && this.counter.announce > 0;
|
||||
var enable_build =
|
||||
this.counter.running == 0 &&
|
||||
this.counter.announce == 0 &&
|
||||
this.counter.success > 0 &&
|
||||
this.counter.error == 0;
|
||||
var enable_sources = this.counter.running == 0;
|
||||
var show_table = this.counter.announce + this.counter.success + this.counter.error > 0;
|
||||
this.btnReset[enable_reset ? "enable" : "disable"]();
|
||||
this.btnStart[enable_start ? "enable" : "disable"]();
|
||||
this.btnStart.$el[enable_start ? "addClass" : "removeClass"]("btn-primary");
|
||||
this.btnBuild[enable_build ? "enable" : "disable"]();
|
||||
this.btnBuild.$el[enable_build ? "addClass" : "removeClass"]("btn-primary");
|
||||
this.btnStop[this.counter.running > 0 ? "enable" : "disable"]();
|
||||
this.btnLocal[enable_sources ? "enable" : "disable"]();
|
||||
this.btnFtp[enable_sources ? "enable" : "disable"]();
|
||||
this.btnCreate[enable_sources ? "enable" : "disable"]();
|
||||
this.btnFtp.$el[this.ftp_upload_site ? "show" : "hide"]();
|
||||
this.$(".upload-table")[show_table ? "show" : "hide"]();
|
||||
this.$(".upload-helper")[show_table ? "hide" : "show"]();
|
||||
},
|
||||
|
||||
/** Calculate percentage of all queued uploads */
|
||||
_uploadPercentage: function(percentage, size) {
|
||||
return (this.upload_completed + percentage * size) / this.upload_size;
|
||||
},
|
||||
|
||||
/** Template */
|
||||
_template: function() {
|
||||
return `<div class="upload-view-default">
|
||||
<div class="upload-top">
|
||||
<div class="upload-top-info"/>
|
||||
</div>
|
||||
<div class="upload-box">
|
||||
<div class="upload-helper">
|
||||
<i class="fa fa-files-o"/>Drop files here
|
||||
</div>
|
||||
<table class="upload-table ui-table-striped" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody/>
|
||||
</table>
|
||||
</div>
|
||||
<div class="upload-footer">
|
||||
<span class="upload-footer-title">Collection Type:</span>
|
||||
<span class="upload-footer-collection-type"/>
|
||||
<span class="upload-footer-title">File Type:</span>
|
||||
<span class="upload-footer-extension"/>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search"/>
|
||||
<span class="upload-footer-title">Genome (set all):</span>
|
||||
<span class="upload-footer-genome"/>
|
||||
</div>
|
||||
<div class="upload-buttons"/>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
@@ -1,217 +0,0 @@
|
||||
import _ from "underscore";
|
||||
import Backbone from "backbone";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import _l from "utils/localization";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import Select from "mvc/ui/ui-select";
|
||||
import UploadUtils from "mvc/upload/upload-utils";
|
||||
import axios from "axios";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
initialize: function(app) {
|
||||
this.app = app;
|
||||
this.options = app.options;
|
||||
this.ftpFiles = [];
|
||||
this.ftpUploadSite = app.currentFtp();
|
||||
this.setElement(this._template());
|
||||
this.btnBuild = new Ui.Button({
|
||||
id: "btn-build",
|
||||
title: _l("Build"),
|
||||
onclick: () => {
|
||||
this._eventBuild();
|
||||
}
|
||||
});
|
||||
this.btnBuild.$el.addClass("btn-primary");
|
||||
this.btnReset = new Ui.Button({
|
||||
id: "btn-reset",
|
||||
title: _l("Reset"),
|
||||
onclick: () => this._eventReset()
|
||||
});
|
||||
this.btnClose = new Ui.Button({
|
||||
id: "btn-close",
|
||||
title: _l("Close"),
|
||||
onclick: () => this.app.modal.hide()
|
||||
});
|
||||
_.each([this.btnReset, this.btnBuild, this.btnClose], button => {
|
||||
this.$(".upload-buttons").prepend(button.$el);
|
||||
});
|
||||
const dataTypeOptions = [{ id: "datasets", text: "Datasets" }, { id: "collections", text: "Collection(s)" }];
|
||||
this.dataType = "datasets";
|
||||
this.dataTypeView = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".rule-data-type"),
|
||||
data: dataTypeOptions,
|
||||
value: this.dataType,
|
||||
onchange: value => {
|
||||
this.dataType = value;
|
||||
// this._renderSelectedType();
|
||||
}
|
||||
});
|
||||
|
||||
const selectionTypeOptions = [
|
||||
{ id: "paste", text: "Pasted Table" },
|
||||
{ id: "dataset", text: "History Dataset" }
|
||||
];
|
||||
if (this.ftpUploadSite) {
|
||||
selectionTypeOptions.push({ id: "ftp", text: "FTP Directory" });
|
||||
}
|
||||
this.selectionType = "paste";
|
||||
this.selectionTypeView = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".rule-select-type"),
|
||||
data: selectionTypeOptions,
|
||||
value: this.selectionType,
|
||||
onchange: value => {
|
||||
this.selectionType = value;
|
||||
this._renderSelectedType();
|
||||
}
|
||||
});
|
||||
this.selectedDatasetId = null;
|
||||
|
||||
this.$sourceContent = this.$(".upload-rule-source-content");
|
||||
this.$sourceContent.on("change keyup paste", () => {
|
||||
this._updateBuildState();
|
||||
});
|
||||
this._renderSelectedType();
|
||||
},
|
||||
|
||||
_renderSelectedType: function() {
|
||||
const selectionType = this.selectionType;
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (selectionType == "dataset") {
|
||||
if (!this.datasetSelectorView) {
|
||||
this.selectedDatasetId = null;
|
||||
const history = Galaxy && Galaxy.currHistoryPanel && Galaxy.currHistoryPanel.model;
|
||||
const historyContentModels = history.contents.models;
|
||||
const options = [];
|
||||
for (const historyContentModel of historyContentModels) {
|
||||
const attr = historyContentModel.attributes;
|
||||
if (attr.history_content_type !== "dataset") {
|
||||
continue;
|
||||
}
|
||||
options.push({ id: attr.id, text: `${attr.hid}: ${_.escape(attr.name)}` });
|
||||
}
|
||||
this.datasetSelectorView = new Select.View({
|
||||
container: this.$(".dataset-selector"),
|
||||
data: options,
|
||||
placeholder: _l("Select a dataset"),
|
||||
onchange: val => {
|
||||
this._onDataset(val);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.datasetSelectorView.value(null);
|
||||
}
|
||||
} else if (selectionType == "ftp") {
|
||||
UploadUtils.getRemoteFiles(ftp_files => {
|
||||
this._setPreview(ftp_files.map(file => file["path"]).join("\n"));
|
||||
this.ftpFiles = ftp_files;
|
||||
});
|
||||
}
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
_onDataset: function(selectedDatasetId) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.selectedDatasetId = selectedDatasetId;
|
||||
if (!selectedDatasetId) {
|
||||
this._setPreview("");
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(
|
||||
`${getAppRoot()}api/histories/${Galaxy.currHistoryPanel.model.id}/contents/${selectedDatasetId}/display`
|
||||
)
|
||||
.then(response => {
|
||||
this._setPreview(response.data);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.datasetSelectorView) {
|
||||
this.datasetSelectorView.value(null);
|
||||
}
|
||||
this.$sourceContent.val("");
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
_eventBuild: function() {
|
||||
const selection = this.$sourceContent.val();
|
||||
this._buildSelection(selection);
|
||||
},
|
||||
|
||||
_buildSelection: function(content) {
|
||||
const selectionType = this.selectionType;
|
||||
const selection = {};
|
||||
const Galaxy = getGalaxyInstance();
|
||||
if (selectionType == "dataset" || selectionType == "paste") {
|
||||
selection.selectionType = "raw";
|
||||
selection.content = content;
|
||||
} else if (selectionType == "ftp") {
|
||||
selection.selectionType = "ftp";
|
||||
selection.elements = this.ftpFiles;
|
||||
selection.ftpUploadSite = this.ftpUploadSite;
|
||||
}
|
||||
selection.dataType = this.dataType;
|
||||
Galaxy.currHistoryPanel.buildCollection("rules", selection, true);
|
||||
this.app.modal.hide();
|
||||
},
|
||||
|
||||
_setPreview: function(content) {
|
||||
this.$sourceContent.val(content);
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
_updateScreen: function() {
|
||||
this._updateBuildState();
|
||||
const selectionType = this.selectionType;
|
||||
this.$("#upload-rule-dataset-option")[selectionType == "dataset" ? "show" : "hide"]();
|
||||
this.$sourceContent.attr("disabled", selectionType !== "paste");
|
||||
},
|
||||
|
||||
_updateBuildState: function() {
|
||||
const selection = this.$sourceContent.val();
|
||||
this.btnBuild[selection ? "enable" : "disable"]();
|
||||
this.btnBuild.$el[selection ? "addClass" : "removeClass"]("btn-primary");
|
||||
},
|
||||
|
||||
_template: function() {
|
||||
return `
|
||||
<div class="upload-view-default">
|
||||
<div class="upload-top">
|
||||
<div class="upload-top-info">
|
||||
Tabular source data to extract collection files and metadata from
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-box" style="height: 335px;">
|
||||
<span style="width: 25%; display: inline; height: 100%" class="float-left">
|
||||
<div class="upload-rule-option">
|
||||
<div class="upload-rule-option-title">${_l("Upload data as")}:</div>
|
||||
<div class="rule-data-type" />
|
||||
</div>
|
||||
<div class="upload-rule-option">
|
||||
<div class="upload-rule-option-title">${_l("Load tabular data from")}:</div>
|
||||
<div class="rule-select-type" />
|
||||
</div>
|
||||
<div id="upload-rule-dataset-option" class="upload-rule-option">
|
||||
<div class="upload-rule-option-title">${_l("Select dataset to load")}:</div>
|
||||
<div class="dataset-selector" />
|
||||
</div>
|
||||
</span>
|
||||
<span style="display: inline; float: right; width: 75%; height: 300px">
|
||||
<textarea class="upload-rule-source-content form-control" style="height: 100%"></textarea>
|
||||
</span>
|
||||
</div>
|
||||
<div class="clear" />
|
||||
<!--
|
||||
<div class="upload-footer">
|
||||
</div>
|
||||
-->
|
||||
<div class="upload-buttons"/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
@@ -72,7 +72,7 @@ export default Backbone.View.extend({
|
||||
self.uploadinput.dialog();
|
||||
}
|
||||
});
|
||||
if (this.app.ftp_upload_site) {
|
||||
if (this.app.ftpUploadSite) {
|
||||
this.button_menu.addMenu({
|
||||
icon: "fa-folder-open-o",
|
||||
title: _l("Choose FTP file"),
|
||||
@@ -261,7 +261,7 @@ export default Backbone.View.extend({
|
||||
var self = this;
|
||||
this.ftp.show(
|
||||
new UploadFtp({
|
||||
ftp_upload_site: this.app.ftp_upload_site,
|
||||
ftp_upload_site: this.app.ftpUploadSite,
|
||||
onchange: function(ftp_file) {
|
||||
self.ftp.hide();
|
||||
if (self.model.get("status") != "running" && ftp_file) {
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
/** Renders contents of the composite uploader */
|
||||
import _l from "utils/localization";
|
||||
import _ from "underscore";
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import { getGalaxyInstance } from "app";
|
||||
// import Utils from "utils/utils";
|
||||
import UploadModel from "mvc/upload/upload-model";
|
||||
import UploadRow from "mvc/upload/composite/composite-row";
|
||||
import UploadExtension from "mvc/upload/upload-extension";
|
||||
import Select from "mvc/ui/ui-select";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
collection: new UploadModel.Collection(),
|
||||
initialize: function(app) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.options = app.options;
|
||||
this.list_extensions = app.list_extensions;
|
||||
this.list_genomes = app.list_genomes;
|
||||
this.ftp_upload_site = app.currentFtp();
|
||||
this.setElement(this._template());
|
||||
|
||||
// create button section
|
||||
this.btnReset = new Ui.Button({
|
||||
id: "btn-reset",
|
||||
title: _l("Reset"),
|
||||
onclick: function() {
|
||||
self._eventReset();
|
||||
}
|
||||
});
|
||||
this.btnStart = new Ui.Button({
|
||||
title: _l("Start"),
|
||||
onclick: function() {
|
||||
self._eventStart();
|
||||
}
|
||||
});
|
||||
this.btnClose = new Ui.Button({
|
||||
title: _l("Close"),
|
||||
onclick: function() {
|
||||
self.app.modal.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// append buttons to dom
|
||||
_.each([this.btnReset, this.btnStart, this.btnClose], button => {
|
||||
self.$(".upload-buttons").prepend(button.$el);
|
||||
});
|
||||
|
||||
// select extension
|
||||
this.select_extension = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".upload-footer-extension"),
|
||||
data: _.filter(this.list_extensions, ext => ext.composite_files),
|
||||
onchange: function(extension) {
|
||||
self.collection.reset();
|
||||
var details = _.findWhere(self.list_extensions, {
|
||||
id: extension
|
||||
});
|
||||
if (details && details.composite_files) {
|
||||
_.each(details.composite_files, item => {
|
||||
self.collection.add({
|
||||
id: self.collection.size(),
|
||||
file_desc: item.description || item.name,
|
||||
optional: item.optional
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// handle extension info popover
|
||||
this.$(".upload-footer-extension-info")
|
||||
.on("click", e => {
|
||||
new UploadExtension({
|
||||
$el: $(e.target),
|
||||
title: self.select_extension.text(),
|
||||
extension: self.select_extension.value(),
|
||||
list: self.list_extensions,
|
||||
placement: "top"
|
||||
});
|
||||
})
|
||||
.on("mousedown", e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// genome extension
|
||||
this.select_genome = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".upload-footer-genome"),
|
||||
data: this.list_genomes,
|
||||
value: this.options.default_genome
|
||||
});
|
||||
|
||||
// listener for collection triggers on change in composite datatype and extension selection
|
||||
this.listenTo(this.collection, "add", model => {
|
||||
self._eventAnnounce(model);
|
||||
});
|
||||
this.listenTo(this.collection, "change add", () => {
|
||||
self.render();
|
||||
});
|
||||
this.select_extension.options.onchange(this.select_extension.value());
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var model = this.collection.first();
|
||||
if (model && model.get("status") == "running") {
|
||||
this.select_genome.disable();
|
||||
this.select_extension.disable();
|
||||
} else {
|
||||
this.select_genome.enable();
|
||||
this.select_extension.enable();
|
||||
}
|
||||
if (
|
||||
this.collection.where({ status: "ready" }).length + this.collection.where({ optional: true }).length ==
|
||||
this.collection.length &&
|
||||
this.collection.length > 0
|
||||
) {
|
||||
this.btnStart.enable();
|
||||
this.btnStart.$el.addClass("btn-primary");
|
||||
} else {
|
||||
this.btnStart.disable();
|
||||
this.btnStart.$el.removeClass("btn-primary");
|
||||
}
|
||||
this.$(".upload-table")[this.collection.length > 0 ? "show" : "hide"]();
|
||||
},
|
||||
|
||||
//
|
||||
// upload events / process pipeline
|
||||
//
|
||||
|
||||
/** Builds the basic ui with placeholder rows for each composite data type file */
|
||||
_eventAnnounce: function(model) {
|
||||
var upload_row = new UploadRow(this, { model: model });
|
||||
this.$(".upload-table > tbody:first").append(upload_row.$el);
|
||||
this.$(".upload-table")[this.collection.length > 0 ? "show" : "hide"]();
|
||||
upload_row.render();
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
var self = this;
|
||||
this.collection.each(model => {
|
||||
model.set({
|
||||
genome: self.select_genome.value(),
|
||||
extension: self.select_extension.value()
|
||||
});
|
||||
});
|
||||
$.uploadpost({
|
||||
url: this.app.options.upload_path,
|
||||
data: this.app.toData(this.collection.filter()),
|
||||
success: function(message) {
|
||||
self._eventSuccess(message);
|
||||
},
|
||||
error: function(message) {
|
||||
self._eventError(message);
|
||||
},
|
||||
progress: function(percentage) {
|
||||
self._eventProgress(percentage);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.collection.where({ status: "running" }).length == 0) {
|
||||
this.collection.reset();
|
||||
this.select_extension.value(this.options.default_extension);
|
||||
this.select_genome.value(this.options.default_genome);
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh progress state */
|
||||
_eventProgress: function(percentage) {
|
||||
this.collection.each(it => {
|
||||
it.set("percentage", percentage);
|
||||
});
|
||||
},
|
||||
|
||||
/** Refresh success state */
|
||||
_eventSuccess: function(message) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.collection.each(it => {
|
||||
it.set("status", "success");
|
||||
});
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
/** Refresh error state */
|
||||
_eventError: function(message) {
|
||||
this.collection.each(it => {
|
||||
it.set({ status: "error", info: message });
|
||||
});
|
||||
},
|
||||
|
||||
/** Load html template */
|
||||
_template: function() {
|
||||
return `<div class="upload-view-composite">
|
||||
<div class="upload-top">
|
||||
<div class="upload-top-info"> </div>
|
||||
</div>
|
||||
<div class="upload-box">
|
||||
<table class="upload-table ui-table-striped" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th/>
|
||||
<th/>
|
||||
<th>Description</th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Settings</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody/>
|
||||
</table>
|
||||
</div>
|
||||
<div class="upload-footer">
|
||||
<span class="upload-footer-title">Composite Type:</span>
|
||||
<span class="upload-footer-extension"/>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search"/>
|
||||
<span class="upload-footer-title">Genome/Build:</span>
|
||||
<span class="upload-footer-genome"/>
|
||||
</div>
|
||||
<div class="upload-buttons"/>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
@@ -2,27 +2,15 @@
|
||||
import $ from "jquery";
|
||||
import _ from "underscore";
|
||||
import Backbone from "backbone";
|
||||
import _l from "utils/localization";
|
||||
import Utils from "utils/utils";
|
||||
import UploadSettings from "mvc/upload/upload-settings";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
import UploadExtension from "mvc/upload/upload-extension";
|
||||
import UploadBoxRow from "mvc/upload/uploadbox-row";
|
||||
import Select from "mvc/ui/ui-select";
|
||||
export default Backbone.View.extend({
|
||||
/** Dictionary of upload states and associated icons */
|
||||
status_classes: {
|
||||
init: "upload-icon-button fa fa-trash-o",
|
||||
queued: "upload-icon fa fa-spinner fa-spin",
|
||||
running: "upload-icon fa fa-spinner fa-spin",
|
||||
warning: "upload-icon fa fa-spinner fa-spin",
|
||||
success: "upload-icon-button fa fa-check",
|
||||
error: "upload-icon-button fa fa-exclamation-triangle"
|
||||
},
|
||||
|
||||
export default UploadBoxRow.extend({
|
||||
initialize: function(app, options) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.list_extensions = app.list_extensions;
|
||||
this.list_extensions = app.listExtensions;
|
||||
this.model = options.model;
|
||||
this.setElement(this._template(options.model));
|
||||
this.$mode = this.$(".upload-mode");
|
||||
@@ -37,21 +25,16 @@ export default Backbone.View.extend({
|
||||
this.$progress_bar = this.$(".upload-progress-bar");
|
||||
this.$percentage = this.$(".upload-percentage");
|
||||
|
||||
// append popup to settings icon
|
||||
this.settings = new Popover({
|
||||
title: _l("Upload configuration"),
|
||||
container: this.$(".upload-settings"),
|
||||
placement: "bottom"
|
||||
});
|
||||
this._setupSettings();
|
||||
|
||||
// identify default genome and extension values
|
||||
var default_genome = this.app.select_genome.value();
|
||||
var default_extension = this.app.select_extension.value();
|
||||
var default_genome = this.app.genome;
|
||||
var default_extension = this.app.extension;
|
||||
|
||||
// create select genomes
|
||||
this.select_genome = new Select.View({
|
||||
css: "upload-genome",
|
||||
data: self.app.list_genomes,
|
||||
data: self.app.listGenomes,
|
||||
container: this.$(".upload-genome"),
|
||||
value: default_genome,
|
||||
onchange: function(genome) {
|
||||
@@ -62,7 +45,7 @@ export default Backbone.View.extend({
|
||||
// create select extension
|
||||
this.select_extension = new Select.View({
|
||||
css: "upload-extension",
|
||||
data: _.filter(this.list_extensions, ext => !ext.composite_files),
|
||||
data: self.app.extensions,
|
||||
container: this.$(".upload-extension"),
|
||||
value: default_extension,
|
||||
onchange: function(extension) {
|
||||
@@ -123,24 +106,13 @@ export default Backbone.View.extend({
|
||||
});
|
||||
|
||||
// model events
|
||||
this.listenTo(this.model, "change:percentage", () => {
|
||||
self._refreshPercentage();
|
||||
});
|
||||
this.listenTo(this.model, "change:status", () => {
|
||||
self._refreshStatus();
|
||||
});
|
||||
this.listenTo(this.model, "change:info", () => {
|
||||
self._refreshInfo();
|
||||
});
|
||||
this._setupUploadBoxListeners();
|
||||
this.listenTo(this.model, "change:genome", () => {
|
||||
self._refreshGenome();
|
||||
});
|
||||
this.listenTo(this.model, "change:extension", () => {
|
||||
self._refreshExtension();
|
||||
});
|
||||
this.listenTo(this.model, "change:file_size", () => {
|
||||
self._refreshFileSize();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@@ -195,23 +167,6 @@ export default Backbone.View.extend({
|
||||
this.select_genome.value(this.model.get("genome"));
|
||||
},
|
||||
|
||||
/** Refresh info text */
|
||||
_refreshInfo: function() {
|
||||
var info = this.model.get("info");
|
||||
if (info) {
|
||||
this.$info_text.html(`<strong>Warning: </strong>${info}`).show();
|
||||
} else {
|
||||
this.$info_text.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh percentage status */
|
||||
_refreshPercentage: function() {
|
||||
var percentage = parseInt(this.model.get("percentage"));
|
||||
this.$progress_bar.css({ width: `${percentage}%` });
|
||||
this.$percentage.html(percentage != 100 ? `${percentage}%` : "Adding to history...");
|
||||
},
|
||||
|
||||
/** Refresh status */
|
||||
_refreshStatus: function() {
|
||||
var status = this.model.get("status");
|
||||
@@ -232,33 +187,7 @@ export default Backbone.View.extend({
|
||||
}
|
||||
this.$info_progress.show();
|
||||
this.$el.removeClass().addClass("upload-row");
|
||||
if (status == "success") {
|
||||
this.$el.addClass("table-success");
|
||||
this.$percentage.html("100%");
|
||||
} else if (status == "error") {
|
||||
this.$el.addClass("table-danger");
|
||||
this.$info_progress.hide();
|
||||
} else if (status == "warning") {
|
||||
this.$el.addClass("table-warning");
|
||||
this.$info_progress.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh file size */
|
||||
_refreshFileSize: function() {
|
||||
this.$size.html(Utils.bytesToString(this.model.get("file_size")));
|
||||
},
|
||||
|
||||
/** Remove row */
|
||||
_removeRow: function() {
|
||||
if (["init", "success", "error"].indexOf(this.model.get("status")) !== -1) {
|
||||
this.app.collection.remove(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
/** Attach file info popup */
|
||||
_showSettings: function() {
|
||||
this.settings.show(new UploadSettings(this).$el);
|
||||
this._renderStatusType(status);
|
||||
},
|
||||
|
||||
/** Make extension popover */
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
/** Renders contents of the default uploader */
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import _ from "underscore";
|
||||
import _l from "utils/localization";
|
||||
import UploadModel from "mvc/upload/upload-model";
|
||||
import UploadRow from "mvc/upload/default/default-row";
|
||||
import UploadFtp from "mvc/upload/upload-ftp";
|
||||
import UploadExtension from "mvc/upload/upload-extension";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
import Select from "mvc/ui/ui-select";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import LazyLimited from "mvc/lazy/lazy-limited";
|
||||
import "utils/uploadbox";
|
||||
import { getGalaxyInstance } from "app";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
// current upload size in bytes
|
||||
upload_size: 0,
|
||||
|
||||
// contains upload row models
|
||||
collection: new UploadModel.Collection(),
|
||||
|
||||
// keeps track of the current uploader state
|
||||
counter: {
|
||||
announce: 0,
|
||||
success: 0,
|
||||
error: 0,
|
||||
running: 0,
|
||||
reset: function() {
|
||||
this.announce = this.success = this.error = this.running = 0;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function(app) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.options = app.options;
|
||||
this.list_extensions = app.list_extensions;
|
||||
this.list_genomes = app.list_genomes;
|
||||
this.ui_button = app.ui_button;
|
||||
this.ftp_upload_site = app.currentFtp();
|
||||
|
||||
// build template
|
||||
this.setElement(this._template());
|
||||
this.$uploadbox = this.$(".upload-box");
|
||||
this.$uploadtable = this.$(".upload-table");
|
||||
|
||||
// append buttons to dom
|
||||
this.btnLocal = new Ui.Button({
|
||||
id: "btn-local",
|
||||
title: _l("Choose local file"),
|
||||
onclick: function() {
|
||||
self.uploadbox.select();
|
||||
},
|
||||
icon: "fa fa-laptop"
|
||||
});
|
||||
this.btnFtp = new Ui.Button({
|
||||
id: "btn-ftp",
|
||||
title: _l("Choose FTP file"),
|
||||
onclick: function() {
|
||||
self._eventFtp();
|
||||
},
|
||||
icon: "fa fa-folder-open-o"
|
||||
});
|
||||
this.btnCreate = new Ui.Button({
|
||||
id: "btn-new",
|
||||
title: "Paste/Fetch data",
|
||||
onclick: function() {
|
||||
self._eventCreate();
|
||||
},
|
||||
icon: "fa fa-edit"
|
||||
});
|
||||
this.btnStart = new Ui.Button({
|
||||
id: "btn-start",
|
||||
title: _l("Start"),
|
||||
onclick: function() {
|
||||
self._eventStart();
|
||||
}
|
||||
});
|
||||
this.btnStop = new Ui.Button({
|
||||
id: "btn-stop",
|
||||
title: _l("Pause"),
|
||||
onclick: function() {
|
||||
self._eventStop();
|
||||
}
|
||||
});
|
||||
this.btnReset = new Ui.Button({
|
||||
id: "btn-reset",
|
||||
title: _l("Reset"),
|
||||
onclick: function() {
|
||||
self._eventReset();
|
||||
}
|
||||
});
|
||||
this.btnClose = new Ui.Button({
|
||||
id: "btn-close",
|
||||
title: _l("Close"),
|
||||
onclick: function() {
|
||||
self.app.modal.hide();
|
||||
}
|
||||
});
|
||||
_.each(
|
||||
[this.btnLocal, this.btnFtp, this.btnCreate, this.btnStop, this.btnReset, this.btnStart, this.btnClose],
|
||||
button => {
|
||||
self.$(".upload-buttons").prepend(button.$el);
|
||||
}
|
||||
);
|
||||
|
||||
// file upload
|
||||
this.uploadbox = this.$uploadbox.uploadbox({
|
||||
url: this.app.options.upload_path,
|
||||
announce: function(index, file) {
|
||||
self._eventAnnounce(index, file);
|
||||
},
|
||||
initialize: function(index) {
|
||||
return self.app.toData([self.collection.get(index)], self.history_id);
|
||||
},
|
||||
progress: function(index, percentage) {
|
||||
self._eventProgress(index, percentage);
|
||||
},
|
||||
success: function(index, message) {
|
||||
self._eventSuccess(index, message);
|
||||
},
|
||||
error: function(index, message) {
|
||||
self._eventError(index, message);
|
||||
},
|
||||
warning: function(index, message) {
|
||||
self._eventWarning(index, message);
|
||||
},
|
||||
complete: function() {
|
||||
self._eventComplete();
|
||||
},
|
||||
ondragover: function() {
|
||||
self.$uploadbox.addClass("highlight");
|
||||
},
|
||||
ondragleave: function() {
|
||||
self.$uploadbox.removeClass("highlight");
|
||||
}
|
||||
});
|
||||
|
||||
// add ftp file viewer
|
||||
this.ftp = new Popover({
|
||||
title: _l("FTP files"),
|
||||
container: this.btnFtp.$el
|
||||
});
|
||||
|
||||
// select extension
|
||||
this.select_extension = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".upload-footer-extension"),
|
||||
data: _.filter(this.list_extensions, ext => !ext.composite_files),
|
||||
value: this.options.default_extension,
|
||||
onchange: function(extension) {
|
||||
self._changeExtension(extension);
|
||||
}
|
||||
});
|
||||
|
||||
// handle extension info popover
|
||||
this.$(".upload-footer-extension-info")
|
||||
.on("click", e => {
|
||||
new UploadExtension({
|
||||
$el: $(e.target),
|
||||
title: self.select_extension.text(),
|
||||
extension: self.select_extension.value(),
|
||||
list: self.list_extensions,
|
||||
placement: "top"
|
||||
});
|
||||
})
|
||||
.on("mousedown", e => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// genome extension
|
||||
this.select_genome = new Select.View({
|
||||
css: "upload-footer-selection",
|
||||
container: this.$(".upload-footer-genome"),
|
||||
data: this.list_genomes,
|
||||
value: this.options.default_genome,
|
||||
onchange: function(genome) {
|
||||
self._changeGenome(genome);
|
||||
}
|
||||
});
|
||||
|
||||
// Lazy load helper
|
||||
this.loader = new LazyLimited({
|
||||
$container: this.$uploadbox,
|
||||
collection: this.collection,
|
||||
new_content: function(model) {
|
||||
var upload_row = new UploadRow(self, { model: model });
|
||||
self.$uploadtable.find("> tbody:first").append(upload_row.$el);
|
||||
upload_row.render();
|
||||
return upload_row;
|
||||
}
|
||||
});
|
||||
|
||||
// events
|
||||
this.collection.on("remove", model => {
|
||||
self._eventRemove(model);
|
||||
});
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var message = "";
|
||||
if (this.counter.announce === 0) {
|
||||
if (this.uploadbox.compatible()) {
|
||||
message = " ";
|
||||
} else {
|
||||
message =
|
||||
"Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.";
|
||||
}
|
||||
} else {
|
||||
if (this.counter.running === 0) {
|
||||
message = `You added ${this.counter.announce} file(s) to the queue. Add more files or click 'Start' to proceed.`;
|
||||
} else {
|
||||
message = `Please wait...${this.counter.announce} out of ${this.counter.running} remaining.`;
|
||||
}
|
||||
}
|
||||
this.$(".upload-top-info").html(message);
|
||||
var enable_reset =
|
||||
this.counter.running === 0 && this.counter.announce + this.counter.success + this.counter.error > 0;
|
||||
var enable_start = this.counter.running === 0 && this.counter.announce > 0;
|
||||
var enable_sources = this.counter.running === 0;
|
||||
var show_table = this.counter.announce + this.counter.success + this.counter.error > 0;
|
||||
this.btnReset[enable_reset ? "enable" : "disable"]();
|
||||
this.btnStart[enable_start ? "enable" : "disable"]();
|
||||
this.btnStart.$el[enable_start ? "addClass" : "removeClass"]("btn-primary");
|
||||
this.btnStop[this.counter.running > 0 ? "enable" : "disable"]();
|
||||
this.btnLocal[enable_sources ? "enable" : "disable"]();
|
||||
this.btnFtp[enable_sources ? "enable" : "disable"]();
|
||||
this.btnCreate[enable_sources ? "enable" : "disable"]();
|
||||
this.btnFtp.$el[this.ftp_upload_site ? "show" : "hide"]();
|
||||
this.$(".upload-table")[show_table ? "show" : "hide"]();
|
||||
this.$(".upload-helper")[show_table ? "hide" : "show"]();
|
||||
},
|
||||
|
||||
/** A new file has been dropped/selected through the uploadbox plugin */
|
||||
_eventAnnounce: function(index, file) {
|
||||
this.counter.announce++;
|
||||
var new_model = new UploadModel.Model({
|
||||
id: index,
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
file_mode: file.mode || "local",
|
||||
file_path: file.path,
|
||||
file_data: file
|
||||
});
|
||||
this.render();
|
||||
this.collection.add(new_model);
|
||||
},
|
||||
|
||||
/** Progress */
|
||||
_eventProgress: function(index, percentage) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: percentage, status: "running", info: "" });
|
||||
this.ui_button.model.set("percentage", this._uploadPercentage(percentage, it.get("file_size")));
|
||||
},
|
||||
|
||||
/** Success */
|
||||
_eventSuccess: function(index, message) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "success" });
|
||||
this.ui_button.model.set("percentage", this._uploadPercentage(100, it.get("file_size")));
|
||||
this.upload_completed += it.get("file_size") * 100;
|
||||
this.counter.announce--;
|
||||
this.counter.success++;
|
||||
this.render();
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
/** Warning */
|
||||
_eventWarning: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ status: "warning", info: message });
|
||||
},
|
||||
|
||||
/** Error */
|
||||
_eventError: function(index, message) {
|
||||
var it = this.collection.get(index);
|
||||
it.set({ percentage: 100, status: "error", info: message });
|
||||
this.ui_button.model.set({
|
||||
percentage: this._uploadPercentage(100, it.get("file_size")),
|
||||
status: "danger"
|
||||
});
|
||||
this.upload_completed += it.get("file_size") * 100;
|
||||
this.counter.announce--;
|
||||
this.counter.error++;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/** Queue is done */
|
||||
_eventComplete: function() {
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "queued") {
|
||||
model.set("status", "init");
|
||||
}
|
||||
});
|
||||
this.counter.running = 0;
|
||||
this.render();
|
||||
},
|
||||
|
||||
/** Remove model from upload list */
|
||||
_eventRemove: function(model) {
|
||||
var status = model.get("status");
|
||||
if (status == "success") {
|
||||
this.counter.success--;
|
||||
} else if (status == "error") {
|
||||
this.counter.error--;
|
||||
} else {
|
||||
this.counter.announce--;
|
||||
}
|
||||
this.uploadbox.remove(model.id);
|
||||
this.render();
|
||||
},
|
||||
|
||||
//
|
||||
// events triggered by this view
|
||||
//
|
||||
|
||||
/** Show/hide ftp popup */
|
||||
_eventFtp: function() {
|
||||
var self = this;
|
||||
this.ftp.show(
|
||||
new UploadFtp({
|
||||
collection: this.collection,
|
||||
ftp_upload_site: this.ftp_upload_site,
|
||||
onadd: function(ftp_file) {
|
||||
return self.uploadbox.add([
|
||||
{
|
||||
mode: "ftp",
|
||||
name: ftp_file.path,
|
||||
size: ftp_file.size,
|
||||
path: ftp_file.path
|
||||
}
|
||||
]);
|
||||
},
|
||||
onremove: function(model_index) {
|
||||
self.collection.remove(model_index);
|
||||
}
|
||||
}).$el
|
||||
);
|
||||
},
|
||||
|
||||
/** Create a new file */
|
||||
_eventCreate: function() {
|
||||
this.uploadbox.add([{ name: "New File", size: 0, mode: "new" }]);
|
||||
},
|
||||
|
||||
/** Start upload process */
|
||||
_eventStart: function() {
|
||||
if (this.counter.announce !== 0 && this.counter.running === 0) {
|
||||
// prepare upload process
|
||||
var self = this;
|
||||
this.upload_size = 0;
|
||||
this.upload_completed = 0;
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "init") {
|
||||
model.set("status", "queued");
|
||||
self.upload_size += model.get("file_size");
|
||||
}
|
||||
});
|
||||
this.ui_button.model.set({
|
||||
percentage: 0,
|
||||
status: "success"
|
||||
});
|
||||
this.counter.running = this.counter.announce;
|
||||
this.history_id = this.app.currentHistory();
|
||||
|
||||
// package ftp files separately, and remove them from queue
|
||||
this._uploadFtp();
|
||||
|
||||
// queue remaining files
|
||||
const Galaxy = getGalaxyInstance();
|
||||
this.uploadbox.start({
|
||||
id: Galaxy.user.id,
|
||||
chunk_upload_size: this.app.options.chunk_upload_size
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
/** Pause upload process */
|
||||
_eventStop: function() {
|
||||
if (this.counter.running > 0) {
|
||||
this.ui_button.model.set("status", "info");
|
||||
$(".upload-top-info").html("Queue will pause after completing the current file...");
|
||||
this.uploadbox.stop();
|
||||
}
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
_eventReset: function() {
|
||||
if (this.counter.running === 0) {
|
||||
this.collection.reset();
|
||||
this.counter.reset();
|
||||
this.uploadbox.reset();
|
||||
this.select_extension.value(this.options.default_extension);
|
||||
this.select_genome.value(this.options.default_genome);
|
||||
this.ui_button.model.set("percentage", 0);
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
/** Update extension for all models */
|
||||
_changeExtension: function(extension, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("extension") == self.options.default_extension || !defaults_only)
|
||||
) {
|
||||
model.set("extension", extension);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Update genome for all models */
|
||||
_changeGenome: function(genome, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(model => {
|
||||
if (
|
||||
model.get("status") == "init" &&
|
||||
(model.get("genome") == self.options.default_genome || !defaults_only)
|
||||
) {
|
||||
model.set("genome", genome);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Package and upload ftp files in a single request */
|
||||
_uploadFtp: function() {
|
||||
var self = this;
|
||||
var list = [];
|
||||
this.collection.each(model => {
|
||||
if (model.get("status") == "queued" && model.get("file_mode") == "ftp") {
|
||||
self.uploadbox.remove(model.id);
|
||||
list.push(model);
|
||||
}
|
||||
});
|
||||
if (list.length > 0) {
|
||||
$.uploadpost({
|
||||
data: this.app.toData(list),
|
||||
url: this.app.options.upload_path,
|
||||
success: function(message) {
|
||||
_.each(list, model => {
|
||||
self._eventSuccess(model.id);
|
||||
});
|
||||
},
|
||||
error: function(message) {
|
||||
_.each(list, model => {
|
||||
self._eventError(model.id, message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** Calculate percentage of all queued uploads */
|
||||
_uploadPercentage: function(percentage, size) {
|
||||
return (this.upload_completed + percentage * size) / this.upload_size;
|
||||
},
|
||||
|
||||
/** Template */
|
||||
_template: function() {
|
||||
return `<div class="upload-view-default">
|
||||
<div class="upload-top">
|
||||
<div class="upload-top-info"/>
|
||||
</div>
|
||||
<div class="upload-box">
|
||||
<div class="upload-helper">
|
||||
<i class="fa fa-files-o"/>Drop files here
|
||||
</div>
|
||||
<table class="upload-table ui-table-striped" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Type</th>
|
||||
<th>Genome</th>
|
||||
<th>Settings</th>
|
||||
<th>Status</th>
|
||||
<th/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody/>
|
||||
</table>
|
||||
</div>
|
||||
<div class="upload-footer">
|
||||
<span class="upload-footer-title">Type (set all):</span>
|
||||
<span class="upload-footer-extension"/>
|
||||
<span class="upload-footer-extension-info upload-icon-button fa fa-search"/>
|
||||
<span class="upload-footer-title">Genome (set all):</span>
|
||||
<span class="upload-footer-genome"/>
|
||||
</div>
|
||||
<div class="upload-buttons"/>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
/** View for upload/progress bar button */
|
||||
import $ from "jquery";
|
||||
import Backbone from "backbone";
|
||||
import _l from "utils/localization";
|
||||
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.model =
|
||||
(options && options.model) ||
|
||||
new Backbone.Model({
|
||||
tooltip: _l("Download from URL or upload files from disk"),
|
||||
label: "Load Data",
|
||||
percentage: 0,
|
||||
status: "",
|
||||
onunload: function() {},
|
||||
onclick: function() {}
|
||||
}).set(options);
|
||||
this.setElement(this._template());
|
||||
this.$progress = this.$(".progress-bar");
|
||||
this.listenTo(this.model, "change", this.render, this);
|
||||
this.render();
|
||||
$(window).on("beforeunload", () => self.model.get("onunload")());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var options = this.model.attributes;
|
||||
this.$el
|
||||
.off("click")
|
||||
.on("click", e => {
|
||||
options.onclick(e);
|
||||
})
|
||||
.tooltip({
|
||||
title: this.model.get("tooltip"),
|
||||
placement: "bottom",
|
||||
trigger: "hover"
|
||||
});
|
||||
this.$progress
|
||||
.removeClass()
|
||||
.addClass("progress-bar")
|
||||
.addClass("progress-bar-notransition")
|
||||
.addClass(options.status != "" && `progress-bar-${options.status}`)
|
||||
.css({ width: `${options.percentage}%` });
|
||||
},
|
||||
|
||||
/** Template */
|
||||
_template: function() {
|
||||
return `<div class="upload-button">
|
||||
<div class="progress">
|
||||
<div class="progress-bar"/>
|
||||
<a class="upload-button-link" href="javascript:void(0)" id="tool-panel-upload-button">
|
||||
<span class="fa fa-upload"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
export default { View: View };
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from "jquery";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import Utils from "utils/utils";
|
||||
import axios from "axios";
|
||||
|
||||
const AUTO_EXTENSION = {
|
||||
id: "auto",
|
||||
@@ -12,9 +12,11 @@ const DEFAULT_GENOME = "?";
|
||||
const DEFAULT_EXTENSION = "auto";
|
||||
|
||||
function getUploadDatatypes(callback, datatypesDisableAuto, auto) {
|
||||
Utils.get({
|
||||
url: `${getAppRoot()}api/datatypes?extension_only=False`,
|
||||
success: function(datatypes) {
|
||||
const url = `${getAppRoot()}api/datatypes?extension_only=False`;
|
||||
axios
|
||||
.get(url)
|
||||
.then(response => {
|
||||
const datatypes = response.data;
|
||||
const listExtensions = [];
|
||||
for (var key in datatypes) {
|
||||
listExtensions.push({
|
||||
@@ -34,14 +36,18 @@ function getUploadDatatypes(callback, datatypesDisableAuto, auto) {
|
||||
listExtensions.unshift(auto);
|
||||
}
|
||||
callback(listExtensions);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(errorMessage => {
|
||||
console.log(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function getUploadGenomes(callback, defaultGenome) {
|
||||
Utils.get({
|
||||
url: `${getAppRoot()}api/genomes`,
|
||||
success: function(genomes) {
|
||||
const url = `${getAppRoot()}api/genomes`;
|
||||
axios
|
||||
.get(url)
|
||||
.then(response => {
|
||||
const genomes = response.data;
|
||||
const listGenomes = [];
|
||||
|
||||
for (var key in genomes) {
|
||||
@@ -60,8 +66,10 @@ function getUploadGenomes(callback, defaultGenome) {
|
||||
return a.text > b.text ? 1 : a.text < b.text ? -1 : 0;
|
||||
});
|
||||
callback(listGenomes);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(errorMessage => {
|
||||
console.log(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function getRemoteFiles(success, error) {
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
/** Upload app contains the upload progress button and upload modal, compiles model data for API request **/
|
||||
import _l from "utils/localization";
|
||||
import Backbone from "backbone";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import Utils from "utils/utils";
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
import Tabs from "mvc/ui/ui-tabs";
|
||||
import UploadUtils from "mvc/upload/upload-utils";
|
||||
import UploadButton from "mvc/upload/upload-button";
|
||||
import UploadViewDefault from "mvc/upload/default/default-view";
|
||||
import UploadViewComposite from "mvc/upload/composite/composite-view";
|
||||
import UploadViewCollection from "mvc/upload/collection/collection-view";
|
||||
import UploadViewRuleBased from "mvc/upload/collection/rules-input-view";
|
||||
|
||||
export default Backbone.View.extend({
|
||||
options: {
|
||||
ftp_upload_site: "n/a",
|
||||
default_genome: UploadUtils.DEFAULT_GENOME,
|
||||
default_extension: UploadUtils.DEFAULT_EXTENSION,
|
||||
height: 500,
|
||||
width: 900,
|
||||
auto: UploadUtils.AUTO_EXTENSION
|
||||
},
|
||||
|
||||
// contains all available dataset extensions/types
|
||||
list_extensions: [],
|
||||
|
||||
// contains all available genomes
|
||||
list_genomes: [],
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = Utils.merge(options, this.options);
|
||||
|
||||
// create view for upload/progress button
|
||||
this.ui_button = new UploadButton.View({
|
||||
onclick: e => {
|
||||
e.preventDefault();
|
||||
this.show();
|
||||
},
|
||||
onunload: () => {
|
||||
var percentage = this.ui_button.model.get("percentage", 0);
|
||||
if (percentage > 0 && percentage < 100) {
|
||||
return "Several uploads are queued.";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// set element to button view
|
||||
this.setElement(this.ui_button.$el);
|
||||
|
||||
// load extensions
|
||||
UploadUtils.getUploadDatatypes(
|
||||
list_extensions => {
|
||||
this.list_extensions = list_extensions;
|
||||
},
|
||||
this.options.datatypes_disable_auto,
|
||||
this.options.auto
|
||||
);
|
||||
|
||||
// load genomes
|
||||
UploadUtils.getUploadGenomes(list_genomes => {
|
||||
this.list_genomes = list_genomes;
|
||||
}, this.default_genome);
|
||||
},
|
||||
|
||||
/** Show/hide upload dialog */
|
||||
show: function() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
var self = this;
|
||||
if (!Galaxy.currHistoryPanel || !Galaxy.currHistoryPanel.model) {
|
||||
window.setTimeout(() => {
|
||||
self.show();
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
this.current_user = Galaxy.user.id;
|
||||
if (!this.modal) {
|
||||
this.tabs = new Tabs.View();
|
||||
this.default_view = new UploadViewDefault(this);
|
||||
this.tabs.add({
|
||||
id: "regular",
|
||||
title: _l("Regular"),
|
||||
$el: this.default_view.$el
|
||||
});
|
||||
this.composite_view = new UploadViewComposite(this);
|
||||
this.tabs.add({
|
||||
id: "composite",
|
||||
title: _l("Composite"),
|
||||
$el: this.composite_view.$el
|
||||
});
|
||||
this.collection_view = new UploadViewCollection(this);
|
||||
this.tabs.add({
|
||||
id: "collection",
|
||||
title: _l("Collection"),
|
||||
$el: this.collection_view.$el
|
||||
});
|
||||
this.rule_based_view = new UploadViewRuleBased(this);
|
||||
this.tabs.add({
|
||||
id: "rule-based",
|
||||
title: _l("Rule-based"),
|
||||
$el: this.rule_based_view.$el
|
||||
});
|
||||
this.modal = new Modal.View({
|
||||
title: _l("Download from web or upload from disk"),
|
||||
body: this.tabs.$el,
|
||||
height: this.options.height,
|
||||
width: this.options.width,
|
||||
closing_events: true,
|
||||
title_separator: false
|
||||
});
|
||||
}
|
||||
this.modal.show();
|
||||
},
|
||||
|
||||
/** Refresh user and current history */
|
||||
currentHistory: function() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
return this.current_user && Galaxy.currHistoryPanel.model.get("id");
|
||||
},
|
||||
|
||||
/** Get ftp configuration */
|
||||
currentFtp: function() {
|
||||
return this.current_user && this.options.ftp_upload_site;
|
||||
},
|
||||
|
||||
/**
|
||||
* Package API data from array of models
|
||||
* @param{Array} items - Upload items/rows filtered from a collection
|
||||
*/
|
||||
toData: function(items, history_id) {
|
||||
// create dictionary for data submission
|
||||
var data = {
|
||||
payload: {
|
||||
tool_id: "upload1",
|
||||
history_id: history_id || this.currentHistory(),
|
||||
inputs: {}
|
||||
},
|
||||
files: [],
|
||||
error_message: null
|
||||
};
|
||||
// add upload tools input data
|
||||
if (items && items.length > 0) {
|
||||
var inputs = {
|
||||
file_count: items.length,
|
||||
dbkey: items[0].get("genome", "?"),
|
||||
// sometimes extension set to "" in automated testing after first upload of
|
||||
// a session. https://github.com/galaxyproject/galaxy/issues/5169
|
||||
file_type: items[0].get("extension") || "auto"
|
||||
};
|
||||
for (var index in items) {
|
||||
var it = items[index];
|
||||
it.set("status", "running");
|
||||
if (it.get("file_size") > 0) {
|
||||
var prefix = `files_${index}|`;
|
||||
inputs[`${prefix}type`] = "upload_dataset";
|
||||
if (it.get("file_name") != "New File") {
|
||||
inputs[`${prefix}NAME`] = it.get("file_name");
|
||||
}
|
||||
inputs[`${prefix}space_to_tab`] = (it.get("space_to_tab") && "Yes") || null;
|
||||
inputs[`${prefix}to_posix_lines`] = (it.get("to_posix_lines") && "Yes") || null;
|
||||
inputs[`${prefix}dbkey`] = it.get("genome", null);
|
||||
inputs[`${prefix}file_type`] = it.get("extension", null);
|
||||
switch (it.get("file_mode")) {
|
||||
case "new":
|
||||
inputs[`${prefix}url_paste`] = it.get("url_paste");
|
||||
break;
|
||||
case "ftp":
|
||||
inputs[`${prefix}ftp_files`] = it.get("file_path");
|
||||
break;
|
||||
case "local":
|
||||
data.files.push({
|
||||
name: `${prefix}file_data`,
|
||||
file: it.get("file_data")
|
||||
});
|
||||
}
|
||||
} else if (it.get("optional")) {
|
||||
continue;
|
||||
} else {
|
||||
data.error_message = "Upload content incomplete.";
|
||||
it.set("status", "error");
|
||||
it.set("info", data.error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
data.payload.inputs = JSON.stringify(inputs);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* shared functionality between default-row and collection-row.
|
||||
*/
|
||||
import _l from "utils/localization";
|
||||
import Backbone from "backbone";
|
||||
import Utils from "utils/utils";
|
||||
import Popover from "mvc/ui/ui-popover";
|
||||
import UploadSettings from "mvc/upload/upload-settings";
|
||||
export default Backbone.View.extend({
|
||||
/** Dictionary of upload states and associated icons */
|
||||
status_classes: {
|
||||
init: "upload-icon-button fa fa-trash-o",
|
||||
queued: "upload-icon fa fa-spinner fa-spin",
|
||||
running: "upload-icon fa fa-spinner fa-spin",
|
||||
success: "upload-icon-button fa fa-check",
|
||||
error: "upload-icon-button fa fa-exclamation-triangle"
|
||||
},
|
||||
|
||||
_setupSettings: function() {
|
||||
// append popup to settings icon
|
||||
this.settings = new Popover({
|
||||
title: _l("Upload configuration"),
|
||||
container: this.$(".upload-settings"),
|
||||
placement: "bottom"
|
||||
});
|
||||
},
|
||||
|
||||
_renderStatusType: function(status) {
|
||||
if (status == "success") {
|
||||
this.$el.addClass("table-success");
|
||||
this.$percentage.html("100%");
|
||||
} else if (status == "error") {
|
||||
this.$el.addClass("table-danger");
|
||||
this.$info_progress.hide();
|
||||
} else if (status == "warning") {
|
||||
this.$el.addClass("table-warning");
|
||||
this.$info_progress.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_setupUploadBoxListeners: function() {
|
||||
this.listenTo(this.model, "change:percentage", () => {
|
||||
this._refreshPercentage();
|
||||
});
|
||||
this.listenTo(this.model, "change:status", () => {
|
||||
this._refreshStatus();
|
||||
});
|
||||
this.listenTo(this.model, "change:info", () => {
|
||||
this._refreshInfo();
|
||||
});
|
||||
this.listenTo(this.model, "change:file_size", () => {
|
||||
this._refreshFileSize();
|
||||
});
|
||||
},
|
||||
|
||||
_refreshInfo: function() {
|
||||
var info = this.model.get("info");
|
||||
if (info) {
|
||||
this.$info_text.html(`<strong>Warning: </strong>${info}`).show();
|
||||
} else {
|
||||
this.$info_text.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_refreshPercentage: function() {
|
||||
var percentage = parseInt(this.model.get("percentage"));
|
||||
this.$progress_bar.css({ width: `${percentage}%` });
|
||||
this.$percentage.html(percentage != 100 ? `${percentage}%` : "Adding to history...");
|
||||
},
|
||||
|
||||
_refreshFileSize: function() {
|
||||
this.$size.html(Utils.bytesToString(this.model.get("file_size")));
|
||||
},
|
||||
|
||||
_removeRow: function() {
|
||||
if (["init", "success", "error"].indexOf(this.model.get("status")) !== -1) {
|
||||
this.app.collection.remove(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
_showSettings: function() {
|
||||
this.settings.show(new UploadSettings(this).$el);
|
||||
}
|
||||
});
|
||||
@@ -14,9 +14,16 @@ var requestCharts = function(chart, module) {
|
||||
var group_index = 0;
|
||||
for (var key in chart.settings.attributes) {
|
||||
var settings_value = chart.settings.get(key);
|
||||
_.each([[" ", " "], [",", ","], [":", ":"]], function(pair) {
|
||||
settings_value = settings_value.replace(new RegExp(pair[0], "g"), pair[1]);
|
||||
});
|
||||
_.each(
|
||||
[
|
||||
[" ", " "],
|
||||
[",", ","],
|
||||
[":", ":"]
|
||||
],
|
||||
function(pair) {
|
||||
settings_value = settings_value.replace(new RegExp(pair[0], "g"), pair[1]);
|
||||
}
|
||||
);
|
||||
settings_string += key + ":" + settings_value + ", ";
|
||||
}
|
||||
settings_string = settings_string.substring(0, settings_string.length - 2);
|
||||
|
||||
@@ -44,10 +44,7 @@ const currentConfig$ = new BehaviorSubject(defaultConfigs);
|
||||
mergedConfigs.subscribe(currentConfig$);
|
||||
|
||||
// debounce, filter out initial null and other invalid stuff
|
||||
export const config$ = currentConfig$.asObservable().pipe(
|
||||
filter(Boolean),
|
||||
debounceTime(100)
|
||||
);
|
||||
export const config$ = currentConfig$.asObservable().pipe(filter(Boolean), debounceTime(100));
|
||||
|
||||
/**
|
||||
* Adds config objects into the global configuration.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user