Merge branch 'dev' into iml_dataset_branch

This commit is contained in:
davelopez
2022-01-14 17:29:26 +01:00
998 changed files with 32033 additions and 16390 deletions
+20 -4
View File
@@ -1,16 +1,32 @@
#!/bin/sh
case "$1" in
galaxy|"")
SCRIPT=./run.sh
PORT=8080
LOGFILE=galaxy.log
;;
reports)
SCRIPT=./run_reports.sh
PORT=9001
LOGFILE=reports_webapp.log
;;
*)
echo "ERROR: Unrecognized app"
exit 1
;;
esac
TRIES=120
URL=http://localhost:8080
URL=http://localhost:$PORT
EXIT_CODE=1
i=0
echo "Testing for correct startup:"
bash run.sh --daemon && \
$SCRIPT --daemon && \
while [ "$i" -le $TRIES ]; do
curl --max-time 1 "$URL" && EXIT_CODE=0 && break
sleep 1
i=$((i + 1))
done
bash run.sh --skip-wheels --stop-daemon
$SCRIPT --skip-wheels --stop-daemon
echo "exit code:$EXIT_CODE, showing startup log:"
cat galaxy.log
cat "$LOGFILE"
exit $EXIT_CODE
+1
View File
@@ -15,5 +15,6 @@ lib/galaxy/util/jstree.py
lib/galaxy/web/proxy/js/node_modules
static/maps
static/scripts
test/functional/tools/cwl_tools/v1.?/
build
dist
+4 -13
View File
@@ -27,7 +27,7 @@ variables:
jobs:
get_code:
docker:
- image: circleci/python:3.6
- image: circleci/python:3.7
<<: *set_workdir
steps:
# Replace standard code checkout with shallow clone to speed things up.
@@ -85,7 +85,7 @@ jobs:
- ~/repo
validate_test_tools:
docker:
- image: circleci/python:3.6
- image: circleci/python:3.7
<<: *set_workdir
steps:
- *restore_repo_cache
@@ -93,18 +93,9 @@ jobs:
- run: sudo apt-get install -y libxml2-utils
- *install_tox
- run: tox -e validate_test_tools
test_galaxy_release:
docker:
- image: circleci/python:3.6
<<: *set_workdir
steps:
- *restore_repo_cache
- *install_tox
- *install_deb_reqs
- run: tox -e test_galaxy_release
test_galaxy_packages:
docker:
- image: circleci/python:3.6
- image: circleci/python:3.7
<<: *set_workdir
steps:
- *restore_repo_cache
@@ -113,7 +104,7 @@ jobs:
- run: tox -e test_galaxy_packages
js_lint:
docker:
- image: circleci/node:14-browsers
- image: cimg/node:14.15
<<: *set_workdir
steps:
- *restore_yarn_cache
+1
View File
@@ -1,3 +1,4 @@
.k8s_ci.Dockerfile
.venv
database
node_modules
+21 -10
View File
@@ -3,7 +3,7 @@ on:
push:
branches:
- 'release*'
- anvil
- dev
concurrency:
group: docker-build-${{ github.ref }}
cancel-in-progress: true
@@ -16,24 +16,35 @@ jobs:
- uses: actions/checkout@v2
# https://stackoverflow.com/questions/59810838/how-to-get-the-short-sha-for-the-github-workflow
- name: Set outputs
id: vars
id: commit
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Set branch name
id: branch
run: echo "::set-output name=name::$(BRANCH_NAME=${GITHUB_REF##*/}; echo ${BRANCH_NAME/release_/}-auto)"
- run: docker build . -t galaxy/galaxy-min:${{ steps.vars.outputs.sha_short }} -f .k8s_ci.Dockerfile
- run: docker tag galaxy/galaxy-min:${{ steps.vars.outputs.sha_short }} quay.io/galaxy-k8s/galaxy:${{ steps.branch.outputs.name }} && docker tag galaxy/galaxy-min:${{ steps.vars.outputs.sha_short }} quay.io/galaxy-k8s/galaxy:${{ steps.vars.outputs.sha_short }}
run: echo "::set-output name=name::$(BRANCH_NAME=${GITHUB_REF##*/}; echo ${BRANCH_NAME/release_/})"
- name: Build container image
run: docker build . --build-arg GIT_COMMIT=$(git rev-parse HEAD) --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') --build-arg IMAGE_TAG=${{ steps.branch.outputs.name }} -t galaxyproject/galaxy-min:${{ steps.branch.outputs.name }} -t quay.io/galaxyproject/galaxy-min:${{ steps.branch.outputs.name }} -f .k8s_ci.Dockerfile
- name: Create auto-expiring one for per-commit auto repository
run: echo "FROM galaxyproject/galaxy-min:${{ steps.branch.outputs.name }}" | docker build --label "quay.expires-after"="90d" -t "quay.io/galaxyproject/galaxy-k8s-auto:${{ steps.commit.outputs.sha_short }}" -
- name: Login to quay.io
uses: actions-hub/docker/login@master
env:
DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
DOCKER_REGISTRY_URL: quay.io
- name: Push to quay.io with commit ID
uses: actions-hub/docker@master
with:
args: push quay.io/galaxy-k8s/galaxy:${{ steps.vars.outputs.sha_short }}
- name: Push to quay.io with branch name
uses: actions-hub/docker@master
with:
args: push quay.io/galaxy-k8s/galaxy:${{ steps.branch.outputs.name }}
args: push quay.io/galaxyproject/galaxy-min:${{ steps.branch.outputs.name }}
- name: Push to quay.io with commit hash
uses: actions-hub/docker@master
with:
args: push quay.io/galaxyproject/galaxy-k8s-auto:${{ steps.commit.outputs.sha_short }}
- name: Login to DockerHub
uses: actions-hub/docker/login@master
env:
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to DockerHub with branch name
uses: actions-hub/docker@master
with:
args: push galaxyproject/galaxy-min:${{ steps.branch.outputs.name }}
+70
View File
@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ dev ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ dev ]
schedule:
- cron: '16 6 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['javascript', 'python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+65
View File
@@ -0,0 +1,65 @@
name: CWL conformance
on:
push:
paths-ignore:
- 'client/**'
- 'doc/**'
pull_request:
paths-ignore:
- 'client/**'
- 'doc/**'
env:
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
concurrency:
group: cwl-conformance-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
if: ${{ false }}
runs-on: ubuntu-latest
continue-on-error: ${{ startsWith(matrix.marker, 'red') }}
strategy:
fail-fast: false
matrix:
python-version: ['3.7']
marker: ['green', 'red and required', 'red and not required']
conformance-version: ['cwl_conformance_v1_0'] #, 'cwl_conformance_v1_1', 'cwl_conformance_v1_2']
services:
postgres:
image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v2
with:
path: 'galaxy root'
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Get full Python version
id: full-python-version
shell: bash
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
- name: Cache pip dir
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('galaxy root/requirements.txt') }}
- name: Cache galaxy venv
uses: actions/cache@v2
with:
path: .venv
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}
- name: Run tests
run: ./run_tests.sh --skip_flakey_fails -cwl lib/galaxy_test/api/cwl -- -m "${{ matrix.marker }} and ${{ matrix.conformance-version }}"
working-directory: 'galaxy root'
- uses: actions/upload-artifact@v2
if: failure()
with:
name: CWL conformance test results (${{ matrix.python-version }}, ${{ matrix.marker }}, ${{ matrix.conformance-version }})
path: 'galaxy root/run_cwl_tests.html'
+2 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.6']
python-version: ['3.7']
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
@@ -27,6 +27,7 @@ jobs:
- name: Create pull request
uses: peter-evans/create-pull-request@v3
with:
author: galaxybot <galaxybot@users.noreply.github.com>
token: ${{ secrets.GALAXYBOT_PAT }}
commit-message: Update Python dependencies
branch: dev_auto_update_dependencies
+2 -2
View File
@@ -19,10 +19,10 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.6', '3.9']
python-version: ['3.7', '3.10']
webserver: ['uwsgi']
include:
- python-version: '3.9'
- python-version: '3.10'
webserver: 'dev'
defaults:
run:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.6', '3.9']
python-version: ['3.7', '3.10']
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
+2 -2
View File
@@ -29,8 +29,8 @@ jobs:
id: pip-cache
with:
path: ~/Library/Caches/pip
# scripts/common_startup.sh creates a conda env for Galaxy containing Python 3.6
key: pip-cache-3.6-${{ hashFiles('galaxy root/requirements.txt') }}
# scripts/common_startup.sh creates a conda env for Galaxy containing Python 3.7
key: pip-cache-3.7-${{ hashFiles('galaxy root/requirements.txt') }}
- name: Cache tox env
uses: actions/cache@v2
with:
+56
View File
@@ -0,0 +1,56 @@
name: Reports startup
on:
push:
paths-ignore:
- 'doc/**'
pull_request:
paths-ignore:
- 'doc/**'
env:
YARN_INSTALL_OPTS: --frozen-lockfile
concurrency:
group: reports-startup-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Reports startup test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.10']
defaults:
run:
shell: bash -l {0}
steps:
- uses: actions/checkout@v2
with:
path: 'galaxy root'
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Get full Python version
id: full-python-version
shell: bash
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
- name: Cache pip dir
uses: actions/cache@v1
id: pip-cache
with:
path: ~/.cache/pip
key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('galaxy root/requirements.txt') }}
- name: Cache tox env
uses: actions/cache@v2
with:
path: .tox
key: tox-cache-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-reports-startup
- uses: mvdbeek/gha-yarn-cache@master
with:
yarn-lock-file: 'galaxy root/client/yarn.lock'
- name: Install tox
run: pip install tox
- name: run tests
run: tox -e reports_startup
working-directory: 'galaxy root'
+1
View File
@@ -7,6 +7,7 @@ on:
paths-ignore:
- 'doc/**'
env:
GALAXY_CONFIG_GALAXY_URL_PREFIX: '/galaxypf'
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
GALAXY_TEST_SELENIUM_RETRIES: 1
+1
View File
@@ -7,6 +7,7 @@ on:
paths-ignore:
- 'doc/**'
env:
GALAXY_CONFIG_GALAXY_URL_PREFIX: '/galaxypf'
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
GALAXY_TEST_SELENIUM_RETRIES: 1
@@ -0,0 +1,25 @@
name: Test Galaxy release script
on:
push:
paths:
- lib/galaxy/version.py
- scripts/release.sh
- test/release.sh
pull_request:
paths:
- lib/galaxy/version.py
- scripts/release.sh
- test/release.sh
concurrency:
group: test-galaxy-release-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run tests
run: ./test/release.sh
+6 -1
View File
@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.6', '3.9']
python-version: ['3.7', '3.10']
steps:
- uses: actions/checkout@v2
with:
@@ -47,3 +47,8 @@ jobs:
- name: Run tests
run: tox -e unit
working-directory: 'galaxy root'
- uses: actions/upload-artifact@v2
if: failure()
with:
name: Unit test results (${{ matrix.python-version }})
path: 'galaxy root/run_unit_tests.html'
+11 -1
View File
@@ -125,12 +125,13 @@ tool-data/**/*.fa
.pytest_cache/
assets/
test-data-cache
run_api_tests.html
run_cwl_tests.html
run_framework_tests.html
run_functional_tests.html
run_integration_tests.html
run_selenium_tests.html
run_toolshed_tests.html
run_api_tests.html
test/tool_shed/tmp/*
.coverage
htmlcov
@@ -187,3 +188,12 @@ config/plugins/**/static/dist
config/plugins/**/static/script.js
config/plugins/**/static/main.css
config/plugins/**/static/plugin_build_hash.txt
config/plugins/**/static/*.map
# viz-specific build artifacts to ignore (until these are removed from codebase)
config/plugins/visualizations/annotate_image/static/jquery.contextMenu.css
config/plugins/visualizations/nvd3/nvd3_bar/static/nvd3.js
# CWL conformance tests
lib/galaxy_test/api/cwl/test_cwl_conformance_v1_?.py
test/functional/tools/cwl_tools/v1.?/
+20
View File
@@ -23,6 +23,10 @@ ARG FINAL_STAGE_BASE=$STAGE1_BASE
ARG GALAXY_USER=galaxy
ARG GALAXY_PLAYBOOK_REPO=https://github.com/galaxyproject/galaxy-docker-k8s
ARG GIT_COMMIT=unspecified
ARG BUILD_DATE=unspecified
ARG IMAGE_TAG=unspecified
#======================================================
# Stage 1 - Setup common requirements for build
#======================================================
@@ -114,6 +118,22 @@ ARG ROOT_DIR
ARG SERVER_DIR
ARG GALAXY_USER
ARG GIT_COMMIT
ARG BUILD_DATE
ARG IMAGE_TAG
LABEL org.opencontainers.image.title="Galaxy Minimal Image" \
org.opencontainers.image.description="A size optimized image for Galaxy targeting k8s and ci applications" \
org.opencontainers.image.authors="galaxyproject.org" \
org.opencontainers.image.vendor="Galaxy Project" \
org.opencontainers.image.documentation="https://github.com/galaxyproject/galaxy-docker-k8s" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.version="$IMAGE_TAG" \
org.opencontainers.image.url="https://github.com/galaxyproject/galaxy-docker-k8s" \
org.opencontainers.image.source="https://github.com/galaxyproject/galaxy.git" \
org.opencontainers.image.revision=$GIT_COMMIT \
org.opencontainers.image.created=$BUILD_DATE
# Init Env
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
+28
View File
@@ -4,6 +4,7 @@ VENV?=.venv
IN_VENV=if [ -f "$(VENV)/bin/activate" ]; then . "$(VENV)/bin/activate"; fi;
RELEASE_CURR:=22.01
RELEASE_UPSTREAM:=upstream
TARGET_BRANCH=$(RELEASE_UPSTREAM)/dev
CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/config/config_manage.py
PROJECT_URL?=https://github.com/galaxyproject/galaxy
DOCS_DIR=doc
@@ -13,6 +14,12 @@ OPEN_RESOURCE=bash -c 'open $$0 || xdg-open $$0'
SLIDESHOW_TO_PDF?=bash -c 'docker run --rm -v `pwd`:/cwd astefanutti/decktape /cwd/$$0 /cwd/`dirname $$0`/`basename -s .html $$0`.pdf'
YARN := $(shell command -v yarn 2> /dev/null)
YARN_INSTALL_OPTS=--network-timeout 300000 --check-files
CWL_TARGETS := test/functional/tools/cwl_tools/v1.0/conformance_tests.yaml \
test/functional/tools/cwl_tools/v1.1/conformance_tests.yaml \
test/functional/tools/cwl_tools/v1.2/conformance_tests.yaml \
lib/galaxy_test/api/cwl/test_cwl_conformance_v1_0.py \
lib/galaxy_test/api/cwl/test_cwl_conformance_v1_1.py \
lib/galaxy_test/api/cwl/test_cwl_conformance_v1_2.py
all: help
@echo "This makefile is used for building Galaxy's JS client, documentation, and drive the release process. A sensible all target is not implemented."
@@ -31,6 +38,9 @@ docs-develop: ## Fast doc generation and more warnings (for development)
setup-venv:
if [ ! -f $(VENV)/bin/activate ]; then bash scripts/common_startup.sh --dev-wheels; fi
diff-format:
$(IN_VENV) darker -r $(TARGET_BRANCH)
list-dependency-updates: setup-venv
$(IN_VENV) pip list --outdated --format=columns
@@ -130,6 +140,24 @@ update-lint-requirements:
update-dependencies: update-lint-requirements ## update pinned and dev dependencies
$(IN_VENV) ./lib/galaxy/dependencies/update.sh
$(CWL_TARGETS):
./scripts/update_cwl_conformance_tests.sh
generate-cwl-conformance-tests: $(CWL_TARGETS) ## Initialise CWL conformance tests
clean-cwl-conformance-tests: ## Clean CWL conformance tests
for f in $(CWL_TARGETS); do \
if [ $$(basename "$$f") = conformance_tests.yaml ]; then \
rm -rf $$(dirname "$$f"); \
else \
rm -f "$$f"; \
fi \
done
update-cwl-conformance-tests: ## update CWL conformance tests
$(MAKE) clean-cwl-conformance-tests
$(MAKE) generate-cwl-conformance-tests
node-deps: ## Install NodeJS dependencies.
ifndef YARN
@echo "Could not find yarn, which is required to build the Galaxy client.\nTo install yarn, please visit \033[0;34mhttps://yarnpkg.com/en/docs/install\033[0m for instructions, and package information for all platforms.\n"
+6
View File
@@ -13,16 +13,22 @@ const glob = require("glob");
const STATIC_PLUGIN_BUILD_IDS = [
"annotate_image",
"chiraviz",
"cytoscape",
"drawrna",
"editor",
"example",
"heatmap/heatmap_default",
"hyphyvision",
"jqplot/jqplot_bar",
"media_player",
"mvpapp",
"ngl",
"nvd3/nvd3_bar",
"openlayers",
"openseadragon",
"pv",
"nora",
"venn",
];
const DIST_PLUGIN_BUILD_IDS = ["new_user"];
+4 -1
View File
@@ -27,6 +27,7 @@
"@johmun/vue-tags-input": "^2.1.0",
"@sentry/browser": "^5.20.1",
"axios": "^0.21.1",
"babel-runtime": "^6.26.0",
"backbone": "1.4.0",
"bootstrap": "4.5.0",
"bootstrap-vue": "^2.21.2",
@@ -89,6 +90,7 @@
"vue-infinite-scroll": "^2.0.2",
"vue-multiselect": "^2.1.0",
"vue-observe-visibility": "^1.0.0",
"vue-prismjs": "^1.2.0",
"vue-router": "^3.5.2",
"vue-rx": "^6.2.0",
"vue-scrollto": "^2.20.0",
@@ -97,7 +99,8 @@
"vuex": "^3.4.0",
"vuex-cache": "^3.2.0",
"vuex-persist": "^3.1.3",
"vuex-persistedstate": "^4.1.0"
"vuex-persistedstate": "^4.1.0",
"xml-beautifier": "^0.5.0"
},
"scripts": {
"watch": "gulp && yarn run save-build-hash && yarn run webpack-watch",
+2 -1
View File
@@ -1,4 +1,5 @@
module.exports = {
tabWidth: 4,
printWidth: 120
printWidth: 120,
bracketSameLine: true
};
-15
View File
@@ -17,7 +17,6 @@ export { TracksterUI } from "viz/trackster";
import Circster from "viz/circster";
export { PhylovizView as phyloviz } from "viz/phyloviz";
export { SweepsterVisualization, SweepsterVisualizationView } from "viz/sweepster";
import GalaxyLibrary from "galaxy.library";
export { createTabularDatasetChunkedView } from "mvc/dataset/data";
import { HistoryCollection } from "mvc/history/history-model";
export { History } from "mvc/history/history-model";
@@ -44,10 +43,6 @@ export function circster(options) {
new Circster.GalaxyApp(options);
}
export function library(options) {
new GalaxyLibrary.GalaxyApp(options);
}
export function multiHistory(options) {
const histories = new HistoryCollection([], {
includeDeleted: options.includingDeleted,
@@ -76,20 +71,10 @@ export { default as async_save_text } from "utils/async-save-text";
// Previously "chart"
import Client from "mvc/visualization/chart/chart-client";
import Datasets from "mvc/visualization/chart/utilities/datasets";
import Series from "mvc/visualization/chart/utilities/series";
import Jobs from "mvc/visualization/chart/utilities/jobs";
export function chart(options) {
return new Client(options);
}
export const chartUtilities = {
Datasets: Datasets,
Jobs: Jobs,
Series: Series,
};
export { initMasthead } from "components/Masthead/initMasthead";
export { panelManagement } from "onload/globalInits/panelManagement";
export { mountMakoTags } from "components/Tags";
+2 -4
View File
@@ -7,8 +7,7 @@
v-b-tooltip.hover="{ boundary: 'viewport', placement: tooltipPlacement }"
:title="'Edit annotation...' | localize"
:placeholder="'Edit annotation...' | localize"
v-slot="{ toggleEdit, placeholder, stateValidator }"
>
v-slot="{ toggleEdit, placeholder, stateValidator }">
<debounced-input v-model="annotation" :delay="1000" v-slot="inputScope">
<b-form-textarea
size="sm"
@@ -19,8 +18,7 @@
@input="inputScope.input"
@blur="toggleEdit(false)"
:placeholder="placeholder"
:state="stateValidator(inputScope.value, annotation)"
></b-form-textarea>
:state="stateValidator(inputScope.value, annotation)"></b-form-textarea>
</debounced-input>
</click-to-edit>
</template>
+3 -6
View File
@@ -26,8 +26,7 @@
:citation="citation"
:output-format="outputFormat"
v-for="(citation, index) in citations"
:key="index"
/>
:key="index" />
</div>
</b-card>
<div v-else-if="citations.length">
@@ -38,16 +37,14 @@
@show="$emit('show')"
@shown="$emit('shown')"
@hide="$emit('hide')"
@hidden="$emit('hidden')"
>
@hidden="$emit('hidden')">
<b-card>
<Citation
class="formatted-reference"
:citation="citation"
:output-format="outputFormat"
v-for="(citation, index) in citations"
:key="index"
/>
:key="index" />
</b-card>
</b-collapse>
</div>
+2 -4
View File
@@ -11,8 +11,7 @@
:toggleEdit="toggleEdit"
:editing="editing"
:placeholder="placeholder"
:stateValidator="stateValidator"
>
:stateValidator="stateValidator">
<debounced-input v-model="localValue" :delay="debounceDelay">
<template v-slot="{ value: debouncedValue, input }">
<b-form-input
@@ -22,8 +21,7 @@
@blur="toggleEdit(false)"
:autofocus="true"
:placeholder="placeholder"
:state="stateValidator(debouncedValue, localValue)"
/>
:state="stateValidator(debouncedValue, localValue)" />
</template>
</debounced-input>
</slot>
@@ -65,8 +65,7 @@
:oncancel="oncancel"
:hide-source-items="hideSourceItems"
@onUpdateHideSourceItems="onUpdateHideSourceItems"
@clicked-create="clickedCreate"
>
@clicked-create="clickedCreate">
<template v-slot:help-content>
<p>
{{
@@ -114,11 +113,18 @@
{{ l("link.") }}
</li>
<li>
{{ l("Click the") }}
{{ l("Click ") }}
<i data-target=".reset">
{{ l("Start over") }}
<font-awesome-icon icon="undo" />
</i>
{{ l("link to begin again as if you had just opened the interface.") }}
{{ l("to begin again as if you had just opened the interface.") }}
</li>
<li>
{{ l("Click ") }}
<i data-target=".sort-items">
<font-awesome-icon icon="sort-alpha-down" />
</i>
{{ l("to sort datasets alphabetically.") }}
</li>
<li>
{{ l("Click the") }}
@@ -143,23 +149,19 @@
</template>
<template v-slot:middle-content>
<div class="collection-elements-controls">
<a
class="reset"
href="javascript:void(0);"
role="button"
:title="titleUndoButton"
@click="reset"
>
{{ l("Start over") }}
</a>
<b-button class="reset" :title="titleUndoButton" @click="reset">
<font-awesome-icon icon="undo" />
</b-button>
<b-button class="sort-items" :title="titleSortButton" @click="sortByName">
<font-awesome-icon icon="sort-alpha-down" />
</b-button>
<a
class="clear-selected"
v-if="atLeastOneDatasetIsSelected"
href="javascript:void(0);"
role="button"
:title="titleDeselectButton"
@click="clickClearAll"
>
@click="clickClearAll">
{{ l("Clear selected") }}
</a>
</div>
@@ -167,8 +169,7 @@
v-model="workingElements"
class="collection-elements scroll-container flex-row drop-zone"
@start="drag = true"
@end="drag = false"
>
@end="drag = false">
<div v-if="noMoreValidDatasets">
<b-alert show variant="warning" dismissible>
{{ discardedElementsHeader }}
@@ -186,8 +187,7 @@
@element-is-discarded="elementDiscarded"
@onRename="(name) => (element.name = name)"
:class="{ selected: getSelectedDatasetElems.includes(element.id) }"
:element="element"
/>
:element="element" />
</draggable>
</template>
</collection-creator>
@@ -205,6 +205,11 @@ import "ui/hoverhighlight";
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import draggable from "vuedraggable";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faSortAlphaDown, faUndo } from "@fortawesome/free-solid-svg-icons";
library.add(faSortAlphaDown, faUndo);
Vue.use(BootstrapVue);
export default {
@@ -214,11 +219,12 @@ export default {
this._elementsSetUp();
this.saveOriginalNames();
},
components: { DatasetCollectionElementView, draggable },
components: { DatasetCollectionElementView, draggable, FontAwesomeIcon },
data: function () {
return {
state: "build", //error
titleUndoButton: _l("Undo all reordering and discards"),
titleSortButton: _l("Sort datasets by name"),
titleDeselectButton: _l("De-select all selected datasets"),
noElementsHeader: _l("No datasets were selected"),
discardedElementsHeader: _l("No elements left. Would you like to"),
@@ -407,6 +413,18 @@ export default {
this._instanceSetUp();
this.getOriginalNames();
},
sortByName: function () {
this.workingElements.sort(this.compareNames);
},
compareNames: function (a, b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
},
/** string rep */
toString: function () {
return "ListCollectionCreator";
@@ -82,8 +82,7 @@
@onUpdateHideSourceItems="onUpdateHideSourceItems"
@clicked-create="clickedCreate"
@remove-extensions-toggle="removeExtensionsToggle"
:suggested-name="initialSuggestedName"
>
:suggested-name="initialSuggestedName">
<template v-slot:help-content>
<p>
{{
@@ -130,8 +129,7 @@
class="swap"
href="javascript:void(0);"
title="l('Swap forward and reverse datasets')"
@click="swapButton"
>
@click="swapButton">
{{ l("Swap") }}
</a>
</div>
@@ -139,8 +137,7 @@
<div
v-for="(element, index) in workingElements"
:key="element.id"
class="collection-element"
>
class="collection-element">
{{ index == 0 ? l("forward") : l("reverse") }}: {{ element.name }}
</div>
</div>
@@ -104,8 +104,7 @@
@onUpdateHideSourceItems="onUpdateHideSourceItems"
@clicked-create="clickedCreate"
@remove-extensions-toggle="removeExtensionsToggle"
:render-extensions-toggle="true"
>
:render-extensions-toggle="true">
<template v-slot:help-content>
<p>
{{
@@ -186,8 +185,7 @@
{{ l("Entering regular expressions to match dataset names. See:") }}
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"
target="_blank"
>
target="_blank">
{{ l("MDN's JavaScript Regular Expression Tutorial") }}</a
>
{{ l("Note: forward slashes (\\) are not needed.") }}
@@ -256,22 +254,19 @@
float-left
search-input search-query
input-group
"
>
">
<input
type="text"
:placeholder="filterTextPlaceholder"
v-model="forwardFilter"
title="filterTextTitle"
/>
title="filterTextTitle" />
<div class="input-group-append" :title="chooseFilterTitle">
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
></button>
aria-expanded="false"></button>
<div class="dropdown-menu">
<a class="dropdown-item" @click="changeFilters('illumina')">_1</a>
<a class="dropdown-item" @click="changeFilters('Rs')">_R1</a>
@@ -287,8 +282,7 @@
class="clear-filters-link"
href="javascript:void(0);"
role="button"
@click="clickClearFilters"
>
@click="clickClearFilters">
{{ l("Clear Filters") }}
</a>
<br />
@@ -296,8 +290,7 @@
class="autopair-link"
href="javascript:void(0);"
role="button"
@click="clickAutopair"
>
@click="clickAutopair">
{{ l("Auto-pair") }}
</a>
</div>
@@ -320,22 +313,19 @@
float-left
search-input search-query
input-group
"
>
">
<input
type="text"
:placeholder="filterTextPlaceholder"
v-model="reverseFilter"
title="filterTextTitle"
/>
title="filterTextTitle" />
<div class="input-group-append" :title="chooseFilterTitle">
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
></button>
aria-expanded="false"></button>
<div class="dropdown-menu">
<a class="dropdown-item" @click="changeFilters('illumina')">_2</a>
<a class="dropdown-item" @click="changeFilters('Rs')">_R2</a>
@@ -365,8 +355,7 @@
selectedForwardElement &&
element.id == selectedForwardElement.id,
}"
:element="element"
/>
:element="element" />
</ol>
</div>
<div class="paired-column flex-column no-flex column truncate">
@@ -375,8 +364,7 @@
v-for="(pairableElement, index) in pairableElements"
:key="index"
@click="_pair(pairableElement.forward, pairableElement.reverse)"
class="dataset"
>
class="dataset">
{{ l("Pair these datasets") }}
</li>
</ol>
@@ -392,8 +380,7 @@
selectedReverseElement &&
element.id == selectedReverseElement.id,
}"
:element="element"
/>
:element="element" />
</ol>
</div>
</div>
@@ -407,8 +394,7 @@
class="unpair-all-link"
href="javascript:void(0);"
role="button"
@click="unpairAll"
>
@click="unpairAll">
{{ l("Unpair all") }}
</a>
</div>
@@ -420,8 +406,7 @@
:key="pair.id"
:pair="pair"
:unlink-fn="clickUnpair(pair)"
@onPairRename="(name) => (pair.name = name)"
/>
@onPairRename="(name) => (pair.name = name)" />
</draggable>
</ol>
</div>
@@ -5,8 +5,7 @@
contenteditable
v-model="localValue"
@blur="editable = false"
@keyup.enter="editable = false"
/>
@keyup.enter="editable = false" />
<label v-else @click="editable = true">
{{ localValue }}
</label>
@@ -7,8 +7,7 @@
href="javascript:void(0);"
role="button"
@click="_clickForHelp"
:title="titleForHelp"
>
:title="titleForHelp">
<div v-if="!isExpanded">
<i class="fas fa-chevron-down"></i>
</div>
@@ -24,8 +23,7 @@
href="javascript:void(0);"
role="button"
@click="_clickForHelp"
:title="titleForHelp"
>
:title="titleForHelp">
</a>
</div>
</div>
@@ -42,8 +40,7 @@
class="remove-extensions float-right"
type="checkbox"
@click="$emit('remove-extensions-toggle')"
checked
/>
checked />
</label>
<label class="setting-prompt float-right">
{{ hideOriginalsText }}
@@ -54,8 +51,7 @@
<input
class="collection-name form-control float-right"
:placeholder="placeholderEnterName"
v-model="collectionName"
/>
v-model="collectionName" />
<div class="collection-name-prompt float-right">
{{ l("Name:") }}
</div>
@@ -71,8 +67,7 @@
<button
class="create-collection btn btn-primary"
@click="$emit('clicked-create', collectionName)"
:disabled="!validInput"
>
:disabled="!validInput">
{{ l("Create collection") }}
</button>
</div>
@@ -19,8 +19,7 @@
v-if="item && databaseKeyFromElements"
:database-key-from-elements="databaseKeyFromElements"
:genomes="item"
@clicked-save="clickedSave"
/>
@clicked-save="clickedSave" />
</div>
</GenomeProvider>
</b-tab>
@@ -6,8 +6,7 @@
<button
class="save-collection-edit btn btn-primary"
@click="clickedSave"
:disabled="selectedGenome.id == databaseKeyFromElements"
>
:disabled="selectedGenome.id == databaseKeyFromElements">
{{ l("Save") }}
</button>
</div>
@@ -20,8 +19,7 @@
label="text"
:options="genomes"
:searchable="true"
:allow-empty="false"
>
:allow-empty="false">
{{ selectedGenome.text }}
</multiselect>
</div>
@@ -6,8 +6,7 @@
<button
class="run-tool-collection-edit btn btn-primary"
@click="clickedConvert"
:disabled="selectedConverter == {}"
>
:disabled="selectedConverter == {}">
{{ l("Convert Collection") }}
</button>
</div>
@@ -20,8 +19,7 @@
label="name"
:options="suitableConverters"
:searchable="true"
:allow-empty="true"
>
:allow-empty="true">
</multiselect>
</div>
</template>
@@ -6,8 +6,7 @@
v-model="queryInput"
@input="delayQuery"
@change="setQuery"
@keydown.esc="setQuery()"
/>
@keydown.esc="setQuery()" />
<font-awesome-icon v-if="loading" class="search-clear" icon="spinner" spin />
<font-awesome-icon
v-else
@@ -15,8 +14,7 @@
:title="titleClearSearch"
class="search-clear"
icon="times-circle"
@click="setQuery()"
/>
@click="setQuery()" />
</div>
</template>
<script>
+1 -2
View File
@@ -5,8 +5,7 @@
@keydown.enter="onChange"
@keydown.esc="onCancel"
class="d-inline w-auto h-auto px-1 py-0"
ref="input"
/>
ref="input" />
</template>
<script>
import Vue from "vue";
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<font-awesome-icon :title="title" :icon="['far', 'copy']" @click="copy(text, message)" />
<font-awesome-icon class="cursor-pointer" :title="title" :icon="['far', 'copy']" @click="copy(text, message)" />
</template>
<script>
@@ -3,8 +3,7 @@
:error-message="errorMessage"
:options-show="optionsShow"
:modal-show="modalShow"
:hide-modal="onCancel"
>
:hide-modal="onCancel">
<template v-slot:search>
<data-dialog-search v-model="filter" />
</template>
@@ -15,8 +14,7 @@
:multiple="multiple"
:filter="filter"
@clicked="onClick"
@load="load"
/>
@load="load" />
</template>
<template v-slot:buttons>
<b-btn size="sm" class="float-left" v-if="undoShow" @click="load()">
@@ -33,8 +31,7 @@
class="float-right ml-1"
variant="primary"
@click="onOk"
:disabled="!hasValue"
>
:disabled="!hasValue">
Ok
</b-btn>
</template>
@@ -6,8 +6,7 @@
striped
hover
:fields="fields"
:items="directoryContent"
>
:items="directoryContent">
</b-table>
<div v-if="errorMessage">
<b v-if="path">{{ path }}</b> {{ errorMessage }}
@@ -5,20 +5,17 @@
class="workflow-dropdown font-weight-bold p-2"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
aria-expanded="false">
<span
v-if="isError"
class="dataset-icon error fa fa-times-circle text-danger"
v-b-tooltip.hover
title="An error occurred for this dataset."
/>
title="An error occurred for this dataset." />
<span
v-else-if="isPaused"
class="dataset-icon pause fa fa-pause text-info"
v-b-tooltip.hover
title="The creation of this dataset has been paused."
/>
title="The creation of this dataset has been paused." />
<span v-else class="dataset-icon fa fa-caret-down" />
<span class="name">{{ this.getName }}</span>
</b-link>
@@ -9,8 +9,6 @@ import flushPromises from "flush-promises";
const localVue = getLocalVue();
jest.mock("components/History/caching");
function buildWrapper(conversion_disable = false) {
return mount(DatasetAttributes, {
propsData: {
@@ -17,8 +17,7 @@
id="dataset-attributes-default-save"
@click="submit('attribute', 'attributes')"
variant="primary"
class="mr-1"
>
class="mr-1">
<font-awesome-icon icon="save" class="mr-1" />{{ "Save" | l }}
</b-button>
<b-button @click="submit('attribute', 'autodetect')">
@@ -9,8 +9,7 @@
v-if="!datasetLoading"
:jobid="dataset.creating_job"
v-slot="{ result: jobDetails, loading }"
@error="onError"
>
@error="onError">
<div v-if="!loading">
<div class="page-container edit-attr">
<div class="response-message"></div>
@@ -24,8 +23,7 @@
<DatasetErrorDetails
:tool-stderr="jobDetails.tool_stderr"
:job-stderr="jobDetails.job_stderr"
:job-messages="jobDetails.job_messages"
/>
:job-messages="jobDetails.job_messages" />
<JobProblemProvider :jobid="dataset.creating_job" v-slot="{ result: jobProblems }" @error="onError">
<div v-if="jobProblems && (jobProblems.has_duplicate_inputs || jobProblems.has_empty_inputs)">
<h3 class="common_problems mt-3">Detected Common Potential Problems</h3>
@@ -45,7 +43,9 @@
<br />
Start here:
<b>
<a href="https://galaxyproject.org/support/tool-error/" target="_blank">
<a
href="https://training.galaxyproject.org/training-material/faqs/galaxy/#troubleshooting-errors"
target="_blank">
My job ended with an error. What can I do?
</a>
</b>
@@ -62,20 +62,17 @@
v-if="!jobDetails.user_email"
id="dataset-error-email"
v-model="email"
title="Please provide your email:"
/>
title="Please provide your email:" />
<FormElement
id="dataset-error-message"
v-model="message"
:area="true"
title="Please provide detailed information on the activities leading to this issue:"
/>
title="Please provide detailed information on the activities leading to this issue:" />
<b-button
id="dataset-error-submit"
variant="primary"
@click="submit(dataset, jobDetails.user_email)"
class="mt-3"
>
class="mt-3">
<font-awesome-icon icon="bug" class="mr-1" />Report
</b-button>
</div>
@@ -0,0 +1,16 @@
<template>
<li class="dataset-hash">
<i>{{ hash.hash_value }}</i>
</li>
</template>
<script>
export default {
props: {
hash: {
type: Object,
required: true,
},
},
};
</script>
@@ -0,0 +1,30 @@
<template>
<span class="dataset-hashes">
<ul class="dataset-hashes-list">
<DatasetHash v-for="(hash, index) in hashes" :key="index" :hash="hash" />
</ul>
</span>
</template>
<script>
import DatasetHash from "./DatasetHash";
export default {
components: {
DatasetHash,
},
props: {
hashes: {
type: Array,
required: true,
},
},
};
</script>
<style scoped>
.dataset-hashes-list {
padding-inline-start: 20px;
margin-bottom: 0px;
}
</style>
@@ -7,7 +7,6 @@ import datasetResponse from "./testData/datasetResponse";
import flushPromises from "flush-promises";
import moment from "moment";
jest.mock("components/History/caching");
const HDA_ID = "FOO_HDA_ID";
const mockDatasetProvider = {
@@ -21,7 +20,7 @@ const mockDatasetProvider = {
const localVue = getLocalVue();
describe("DatasetInformation/DatasetInformation.vue", () => {
describe("DatasetInformation/DatasetInformation", () => {
let wrapper;
let datasetInfoTable;
let axiosMock;
@@ -54,6 +54,22 @@
<td>Full Path</td>
<td id="file_name">{{ dataset.file_name }}</td>
</tr>
<tr v-if="dataset.created_from_basename">
<td>Originally Created From a File Named</td>
<td id="created_from_basename">{{ dataset.created_from_basename }}</td>
</tr>
<tr v-if="dataset.sources && dataset.sources.length > 0">
<td>Sources</td>
<td>
<DatasetSources :sources="dataset.sources" />
</td>
</tr>
<tr v-if="dataset.hashes && dataset.hashes.length > 0">
<td>Hashes</td>
<td>
<DatasetHashes :hashes="dataset.hashes" />
</td>
</tr>
</tbody>
</table>
</div>
@@ -65,6 +81,8 @@ import Utils from "utils/utils";
import UtcDate from "components/UtcDate";
import DecodedId from "../DecodedId";
import { DatasetProvider } from "components/providers";
import DatasetSources from "./DatasetSources";
import DatasetHashes from "./DatasetHashes";
export default {
props: {
@@ -74,7 +92,9 @@ export default {
},
},
components: {
DatasetHashes,
DatasetProvider,
DatasetSources,
DecodedId,
UtcDate,
},
@@ -0,0 +1,51 @@
<template>
<li class="dataset-source">
<a v-if="browserCompatUri" :href="sourceUri" target="_blank">
{{ source.source_uri }}
<font-awesome-icon v-b-tooltip.hover title="Dataset Source URL" icon="external-link-alt" />
</a>
<span v-else>
{{ source.source_uri }}
</span>
<font-awesome-icon v-b-tooltip.hover title="Copy URI" icon="copy" style="cursor: pointer" @click="copyLink" />
<br />
<DatasetSourceTransform :transform="source.transform" />
</li>
</template>
<script>
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faCopy, faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
import { copy } from "utils/clipboard";
import DatasetSourceTransform from "./DatasetSourceTransform";
library.add(faCopy, faExternalLinkAlt);
export default {
components: {
DatasetSourceTransform,
FontAwesomeIcon,
},
props: {
source: {
type: Object,
required: true,
},
},
computed: {
browserCompatUri() {
const sourceUri = this.sourceUri;
return sourceUri && (sourceUri.indexOf("http") == 0 || sourceUri.indexOf("ftp") == 0);
},
sourceUri() {
return this.source.source_uri;
},
},
methods: {
copyLink() {
copy(this.sourceUri, "Link copied to the clipboard.");
},
},
};
</script>
@@ -0,0 +1,93 @@
<template>
<span class="dataset-source-transform-display">
<div v-if="transform && transform.length > 0">
Upon ingestion into the Galaxy, the following {{ actions }} were performed that modified the dataset
contents:
<ul>
<li v-for="(transformAction, index) in transform" :key="index">
<span
:title="actionLongDescription(transformAction)"
class="dataset-source-transform-element"
:data-transform-action="transformAction.action"
v-b-tooltip.hover.noninteractive.nofade.bottom>
{{ actionShortDescription(transformAction) }}
</span>
</li>
</ul>
</div>
</span>
</template>
<script>
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
Vue.use(BootstrapVue);
const TRANSFORM_ACTION_DESCRIPTIONS = {
to_posix_lines: {
short: "Normalized new line characters",
long: "Many Galaxy tools expect data to contain 'posix' newline characters in text files and not the newline format used by the Windows operating system. Additionally, most tools expect a newline at the end of plain text files. This file was converted to use these line endings or add a newline to the end of the file.",
},
spaces_to_tabs: {
short: "Normalized spaces to tabs",
long: "In order to convert the referenced data source to tabular data, spaces in the file contents were converted to tab characters to indicate column separations.",
},
datatype_groom: {
short: "Datatype-specific grooming",
long: "The Galaxy datatype class indicated the source data required 'groooming' and Galaxy applied datatype specific cleaning of the supplied data.",
},
};
const DATATYPE_GROOMING_DESCRIPTIONS = {
bam: "The supplied BAM was coordinate-sorted using pysam.",
"qname_sorted.bam": "The supplied BAM was 'queryname' sorted using pysam.",
"qname_input_sorted.bam": "The supplied BAM was 'queryname' sorted using pysam.",
"isa-tab": "The supplied compressed file was converted to an ISA-TAB composite dataset.",
"isa-json": "The supplied compressed file was converted to an ISA-JSON composite dataset.",
};
const UNKNOWN_ACTION_DESCRIPTION = {
short: "Unknown action.",
long: "",
};
export default {
props: {
transform: {
type: Array,
required: false,
},
},
computed: {
actions() {
return this.transform.length > 1 ? "actions" : "action";
},
},
methods: {
actionShortDescription(transformAction) {
return this.actionDescription(transformAction).short || "Unknown action.";
},
actionLongDescription(transformAction) {
let longDescription = this.actionDescription(transformAction).long || "";
if (transformAction.action == "datatype_groom") {
const datatypeDescription = DATATYPE_GROOMING_DESCRIPTIONS[transformAction.datatype_ext];
if (datatypeDescription) {
longDescription += " " + datatypeDescription;
}
}
return longDescription;
},
actionDescription(transformAction) {
return TRANSFORM_ACTION_DESCRIPTIONS[transformAction.action] || UNKNOWN_ACTION_DESCRIPTION;
},
},
};
</script>
<style scoped>
.dataset-source-transform-element {
text-decoration: underline;
text-decoration-style: dashed;
}
</style>
@@ -0,0 +1,30 @@
<template>
<span class="dataset-sources">
<ul class="dataset-sources-list">
<DatasetSource v-for="(source, index) in sources" :key="index" :source="source" />
</ul>
</span>
</template>
<script>
import DatasetSource from "./DatasetSource";
export default {
components: {
DatasetSource,
},
props: {
sources: {
type: Array,
required: true,
},
},
};
</script>
<style scoped>
.dataset-sources-list {
padding-inline-start: 20px;
margin-bottom: 0px;
}
</style>
@@ -2,18 +2,16 @@
<ConfigProvider v-slot="{ config }">
<DatasetProvider
:id="datasetId"
v-slot="{ item: dataset, loading: isDatasetLoading, error: datasetLoadingError }"
>
v-slot="{ item: dataset, loading: isDatasetLoading, error: datasetLoadingError }">
<div>
<LoadingSpan v-if="isDatasetLoading" />
<Alert v-else-if="datasetLoadingError" :message="datasetLoadingError" variant="error" />
<CurrentUser v-else v-slot="{ user }">
<JobDetailsProvider
v-if="!isDatasetLoading"
v-if="!isDatasetLoading && dataset.creating_job !== null"
:jobid="dataset.creating_job"
v-slot="{ result: job, loading: isJobLoading }"
:use-cache="false"
>
:use-cache="false">
<div v-if="!isJobLoading">
<dataset-information class="detail" :hda_id="datasetId" />
<job-parameters class="detail" dataset_type="hda" :dataset-id="datasetId" />
@@ -24,13 +22,11 @@
class="detail"
v-if="config"
:aws_estimate="config.aws_estimate"
:dataset-id="datasetId"
/>
:dataset-id="datasetId" />
<job-destination-params
class="detail"
v-if="user.is_admin"
:job-id="dataset.creating_job"
/>
:job-id="dataset.creating_job" />
<job-dependencies class="detail" :dependencies="job.dependencies"></job-dependencies>
<div class="detail" v-if="dataset.peek">
<h3>Dataset Peek:</h3>
@@ -38,6 +34,17 @@
</div>
</div>
</JobDetailsProvider>
<div v-else-if="!isDatasetLoading">
<dataset-information class="detail" :hda_id="datasetId" />
<dataset-storage class="detail" :dataset-id="datasetId" />
<div>
<h3>Job Not Found</h3>
<p>
No job associated with this dataset is recorded in Galaxy. Galaxy cannot determine full
dataset provenance and history for this dataset.
</p>
</div>
</div>
</CurrentUser>
</div>
</DatasetProvider>
@@ -9,6 +9,7 @@ import axios from "axios";
import BootstrapVue from "bootstrap-vue";
import {
ftpId,
rootId,
directoryId,
subDirectoryId,
@@ -19,6 +20,7 @@ import {
directory2RecursiveResponse,
directory1Response,
subsubdirectoryResponse,
someErrorText,
} from "./testingData";
import { selectionStates } from "components/SelectionDialog/selectionStates";
@@ -44,6 +46,11 @@ const initComponent = async (props) => {
axiosMock.onGet(path).reply(200, response);
}
axiosMock.onGet("/api/remote_files?target=gxfiles://empty-dir").reply(404, {
err_msg: someErrorText,
err_code: 404,
});
const wrapper = shallowMount(FilesDialog, {
localVue,
propsData: props,
@@ -193,6 +200,43 @@ describe("FilesDialog, file mode", () => {
const rootNode = utils.getRenderedDirectory(rootId);
expect(rootNode._rowVariant).toBe(selectionStates.selected);
});
it("should show ftp helper only in ftp directory", async () => {
// open some other directory than ftp
await utils.open_root_folder();
// check that ftp helper is not visible
expect(wrapper.vm.showFTPHelper).toBe(false);
expect(wrapper.find("#helper").exists()).toBe(false);
// back to root folder
await utils.navigateBack();
// open ftp directory
await utils.openDirectory(ftpId);
// check that ftp helper is visible
expect(wrapper.vm.showFTPHelper).toBe(true);
expect(wrapper.find("#helper").exists()).toBe(true);
});
it("should show loading error and can return back", async () => {
expect(wrapper.vm.errorMessage).toBeNull();
// open directory with error
await utils.openDirectory("empty-dir");
// assert that error message is set and showed
expect(wrapper.vm.errorMessage).toBe(someErrorText);
expect(wrapper.html()).toContain(someErrorText);
// assert that OK button is disabled
expect(wrapper.vm.hasValue).toBe(false);
// back to the root folder
await wrapper.find("#back-btn").trigger("click");
await flushPromises();
expect(wrapper.vm.items.length).toBe(rootResponse.length);
// assert that OK button is disabled
expect(wrapper.vm.hasValue).toBe(false);
});
});
describe("FilesDialog, directory mode", () => {
@@ -238,6 +282,27 @@ describe("FilesDialog, directory mode", () => {
//should close modal
expect(wrapper.vm.modalShow).toBe(false);
});
it("should show loading error and can return back", async () => {
expect(wrapper.vm.errorMessage).toBeNull();
// open directory with error
await utils.openDirectory("empty-dir");
// assert that error message is set and showed
expect(wrapper.vm.errorMessage).toBe(someErrorText);
expect(wrapper.html()).toContain(someErrorText);
// assert that OK button is disabled
expect(wrapper.vm.hasValue).toBe(false);
// back to the root folder
await wrapper.find("#back-btn").trigger("click");
await flushPromises();
expect(wrapper.vm.items.length).toBe(rootResponse.length);
// assert that OK button is disabled
expect(wrapper.vm.hasValue).toBe(false);
});
});
/** Util methods **/
@@ -4,10 +4,23 @@
:options-show="optionsShow"
:modal-show="modalShow"
:hide-modal="() => (modalShow = false)"
>
:back-func="load"
:undo-show="undoShow">
<template v-slot:search>
<data-dialog-search v-model="filter" />
</template>
<template v-slot:helper>
<b-alert v-if="showFTPHelper" id="helper" variant="info" show>
This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at
<strong>{{ ftpUploadSite }}</strong> using your Galaxy credentials. For help visit the
<a href="https://galaxyproject.org/ftp-upload/" target="_blank">tutorial</a>.
<span v-if="oidcEnabled"
><br />If you are signed-in to Galaxy using a third-party identity and you
<strong>do not have a Galaxy password</strong> please use the reset password option in the login
form with your email to create a password for your account.</span
>
</b-alert>
</template>
<template v-slot:options>
<data-dialog-table
v-if="optionsShow"
@@ -21,8 +34,7 @@
:is-busy="isBusy"
@clicked="clicked"
@open="open"
@toggleSelectAll="toggleSelectAll"
/>
@toggleSelectAll="toggleSelectAll" />
</template>
<template v-slot:buttons>
<b-btn id="back-btn" size="sm" class="float-left" v-if="undoShow" @click="load()">
@@ -36,8 +48,7 @@
variant="primary"
id="ok-btn"
@click="fileMode ? finalize() : selectLeaf(currentDirectory)"
:disabled="(fileMode && !hasValue) || isBusy || (!fileMode && urlTracker.atRoot())"
>
:disabled="(fileMode && !hasValue) || isBusy || (!fileMode && urlTracker.atRoot())">
{{ fileMode ? "Ok" : "Select this folder" }}
</b-btn>
</template>
@@ -46,6 +57,7 @@
<script>
import Vue from "vue";
import { getGalaxyInstance } from "../../app";
import SelectionDialogMixin from "components/SelectionDialog/SelectionDialogMixin";
import { selectionStates } from "components/SelectionDialog/selectionStates";
import { UrlTracker } from "components/DataDialog/utilities";
@@ -92,7 +104,10 @@ export default {
showDetails: true,
isBusy: false,
currentDirectory: undefined,
showFTPHelper: false,
selectAllIcon: selectionStates.unselected,
ftpUploadSite: getGalaxyInstance()?.config?.ftp_upload_site,
oidcEnabled: getGalaxyInstance()?.config?.enable_oidc,
};
},
created: function () {
@@ -233,7 +248,9 @@ export default {
},
/** check if all objects in this folders are selected **/
checkIfAllSelected() {
const isAllSelected = this.items.every(({ id }) => this.model.exists(id) || this.isDirectorySelected(id));
const isAllSelected =
this.items.length &&
this.items.every(({ id }) => this.model.exists(id) || this.isDirectorySelected(id));
if (isAllSelected && !this.isDirectorySelected(this.currentDirectory.id)) {
// if all selected, select current folder
@@ -255,16 +272,25 @@ export default {
item.isLeaf ? this.model.add(item) : this.selectDirectory(item);
}
}
if (!isUnselectAll && this.items.length !== 0) {
this.selectedDirectories.push(this.currentDirectory);
} else if (this.isDirectorySelected(this.currentDirectory.id)) {
this.selectDirectory(this.currentDirectory);
}
this.hasValue = this.model.count() > 0;
this.formatRows();
},
/** Performs server request to retrieve data records **/
load: function (record) {
this.currentDirectory = this.urlTracker.getUrl(record);
this.showFTPHelper = record?.url === "gxftp://";
this.filter = null;
this.optionsShow = false;
this.undoShow = !this.urlTracker.atRoot();
if (this.urlTracker.atRoot()) {
if (this.urlTracker.atRoot() || this.errorMessage) {
this.errorMessage = null;
this.services
.getFileSources()
.then((items) => {
@@ -1,3 +1,4 @@
export const ftpId = "_ftp";
export const rootId = "pdb-gzip";
export const directoryId = "gxfiles://pdb-gzip/directory1";
export const subDirectoryId = "gxfiles://pdb-gzip/directory1/subdirectory1";
@@ -24,6 +25,16 @@ export const rootResponse = [
requires_roles: null,
requires_groups: null,
},
{
id: "empty-dir",
type: "posix",
uri_root: "gxfiles://empty-dir",
label: "Empty Directory",
doc: "Empty Directory",
writable: true,
requires_roles: null,
requires_groups: null,
},
];
export const pdbResponse = [
@@ -172,3 +183,5 @@ export const subsubdirectoryResponse = [
path: "directory1/subdirectory1/subsubdirectory",
},
];
export const someErrorText = "some error text";
@@ -0,0 +1,40 @@
<template>
<b-row>
<b-col class="form-color-input">
<b-form-input class="cursor-pointer" :id="id" v-model="currentValue" type="color" size="sm" />
</b-col>
<b-col class="pl-0">
<label class="pt-1 cursor-pointer" :for="id">Select a color</label>
</b-col>
</b-row>
</template>
<script>
export default {
props: {
value: {
type: String,
default: "",
},
id: {
type: String,
required: true,
},
},
computed: {
currentValue: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
},
},
},
};
</script>
<style scoped>
.form-color-input {
max-width: 3.6rem;
}
</style>
@@ -1,12 +1,12 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "jest/helpers";
import DirectoryPathEditableBreadcrumb from "./DirectoryPathEditableBreadcrumb";
import FilesDialog from "./FilesDialog";
import FormDirectory from "./FormDirectory";
import FilesDialog from "components/FilesDialog/FilesDialog";
import flushPromises from "flush-promises";
import MockAdapter from "axios-mock-adapter";
import axios from "axios";
import { rootResponse } from "./testingData";
import { rootResponse } from "components/FilesDialog/testingData";
const localVue = getLocalVue();
jest.mock("app");
@@ -46,14 +46,14 @@ describe("DirectoryPathEditableBreadcrumb", () => {
beforeEach(async () => {
const axiosMock = new MockAdapter(axios);
spyOnUrlSet = jest.spyOn(DirectoryPathEditableBreadcrumb.methods, "setUrl");
spyOnAddPath = jest.spyOn(DirectoryPathEditableBreadcrumb.methods, "addPath");
spyOnUpdateURL = jest.spyOn(DirectoryPathEditableBreadcrumb.methods, "updateURL");
spyOnUrlSet = jest.spyOn(FormDirectory.methods, "setUrl");
spyOnAddPath = jest.spyOn(FormDirectory.methods, "addPath");
spyOnUpdateURL = jest.spyOn(FormDirectory.methods, "updateURL");
// register axios paths
axiosMock.onGet("/api/remote_files/plugins").reply(200, rootResponse);
wrapper = mount(DirectoryPathEditableBreadcrumb, {
wrapper = mount(FormDirectory, {
propsData: {
callback: () => {},
},
@@ -1,7 +1,9 @@
<template>
<div>
<div v-if="!url">
<b-button id="select-btn" @click="reset"> <font-awesome-icon icon="folder-open" /> Select folder </b-button>
<b-button id="select-btn" @click="reset">
<font-awesome-icon icon="folder-open" /> {{ selectText }}
</b-button>
<FilesDialog :key="modalKey" mode="directory" :callback="setUrl" :require-writable="true" />
</div>
<b-breadcrumb v-if="url">
@@ -13,8 +15,7 @@
<b-breadcrumb-item
v-for="({ pathChunk, editable }, index) in pathChunks"
:key="index"
class="existent-url-path align-items-center"
>
class="existent-url-path align-items-center">
<b-button class="regular-path-chunk" @click="removePath(index)" :disabled="!editable" variant="dark">
{{ pathChunk }}</b-button
>
@@ -29,8 +30,7 @@
@keydown.8.capture="removeLastPath"
v-model="currentDirectoryName"
placeholder="enter directory name"
trim
/>
trim />
</b-breadcrumb-item>
</b-breadcrumb>
</div>
@@ -41,6 +41,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { faFolder, faFolderOpen } from "@fortawesome/free-solid-svg-icons";
import { FilesDialog } from "components/FilesDialog";
import _l from "utils/localization";
library.add(faFolder, faFolderOpen);
@@ -57,13 +58,7 @@ export default {
FilesDialog,
},
data() {
return { ...getDefaultValues(), modalKey: 0 };
},
props: {
callback: {
type: Function,
required: true,
},
return { ...getDefaultValues(), modalKey: 0, selectText: _l("Select") };
},
computed: {
isValidName() {
@@ -123,7 +118,7 @@ export default {
// create an string of path chunks separated by `/`
url = encodeURI(`${this.url.protocol}//${this.pathChunks.map(({ pathChunk }) => pathChunk).join("/")}`);
}
this.callback(url);
this.$emit("input", url);
},
},
};
@@ -0,0 +1,26 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "jest/helpers";
import FormHidden from "./FormHidden";
const localVue = getLocalVue();
describe("FormHidden", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(FormHidden, {
propsData: {
value: false,
info: "info",
},
localVue,
});
});
it("check initial value and value change", async () => {
expect(wrapper.vm.value).toBe(false);
await wrapper.setProps({ value: true });
expect(wrapper.vm.value).toBe(true);
expect(wrapper.text()).toBe("info");
});
});
@@ -0,0 +1,19 @@
<template>
<div>
{{ info }}
</div>
</template>
<script>
export default {
props: {
value: {
required: true,
},
info: {
type: String,
default: null,
},
},
};
</script>
@@ -2,8 +2,6 @@ import { mount } from "@vue/test-utils";
import { getLocalVue } from "jest/helpers";
import FormInput from "./FormInput";
jest.mock("app");
const localVue = getLocalVue();
describe("FormInput", () => {
@@ -9,8 +9,6 @@ import SelectContent from "mvc/ui/ui-select-content";
import SelectLibrary from "mvc/ui/ui-select-library";
import SelectFtp from "mvc/ui/ui-select-ftp";
import RulesEdit from "mvc/ui/ui-rules-edit";
import FileSource from "mvc/ui/ui-file-source";
import ColorPicker from "mvc/ui/ui-color-picker";
import DataPicker from "mvc/ui/ui-data-picker";
// create form view
@@ -26,25 +24,19 @@ export default Backbone.View.extend({
data_collection: "_fieldData",
integer: "_fieldSlider",
float: "_fieldSlider",
boolean: "_fieldBoolean",
drill_down: "_fieldDrilldown",
color: "_fieldColor",
group_tag: "_fieldSelect",
hidden: "_fieldHidden",
hidden_data: "_fieldHidden",
baseurl: "_fieldHidden",
library_data: "_fieldLibrary",
ftpfile: "_fieldFtp",
upload: "_fieldUpload",
rules: "_fieldRulesEdit",
directory_uri: "_fieldDirectoryUri",
data_dialog: "_fieldDialog",
},
/** Returns an input field for a given field type */
create: function (input_def) {
const Galaxy = getGalaxyInstance();
var fieldClass = this.types[input_def.titleonly ? "hidden" : input_def.type];
var fieldClass = this.types[input_def.type];
this.field = typeof this[fieldClass] === "function" ? this[fieldClass].call(this, input_def) : null;
if (!this.field) {
this.field = input_def.options ? this._fieldSelect(input_def) : this._fieldText(input_def);
@@ -68,6 +60,7 @@ export default Backbone.View.extend({
type: input_def.type,
flavor: input_def.flavor,
data: input_def.options,
tag: input_def.tag,
onchange: input_def.onchange,
});
},
@@ -189,30 +182,6 @@ export default Backbone.View.extend({
});
},
/** Hidden field */
_fieldHidden: function (input_def) {
return new Ui.Hidden({
id: `field-${input_def.id}`,
info: input_def.info,
});
},
/** Boolean field */
_fieldBoolean: function (input_def) {
return new Ui.Switch({
id: `field-${input_def.id}`,
onchange: input_def.onchange,
});
},
/** Color picker field */
_fieldColor: function (input_def) {
return new ColorPicker({
id: `field-${input_def.id}`,
onchange: input_def.onchange,
});
},
/** Data dialog picker field */
_fieldDialog: function (input_def) {
return new DataPicker({
@@ -242,14 +211,6 @@ export default Backbone.View.extend({
});
},
_fieldDirectoryUri: function (input_def) {
return new FileSource.View({
id: `field-${input_def.id}`,
onchange: input_def.onchange,
target: input_def.target,
});
},
_fieldRulesEdit: function (input_def) {
return new RulesEdit.View({
id: `field-${input_def.id}`,
+1 -2
View File
@@ -11,8 +11,7 @@
size="sm"
class="float-right"
v-b-tooltip.hover.bottom
@click="onCollapse"
>
@click="onCollapse">
<font-awesome-icon v-if="expanded" icon="eye-slash" class="fa-fw" />
<font-awesome-icon v-else icon="eye" class="fa-fw" />
</b-button>
+1 -2
View File
@@ -11,8 +11,7 @@
:collapsed-disable-icon="collapsedDisableIcon"
:errors="errors"
:on-change="onChange"
:on-change-form="onChangeForm"
/>
:on-change-form="onChangeForm" />
</template>
<script>
+15 -3
View File
@@ -20,6 +20,10 @@ describe("FormElement", () => {
title: "title_text",
},
localVue,
stubs: {
FormInput: { template: "<div>form-input</div>" },
FormHidden: { template: "<div>form-hidden</div>" },
},
});
});
@@ -43,9 +47,10 @@ describe("FormElement", () => {
await wrapper.setProps({ default_value: "default_value", collapsible_value: "collapsible_value" });
expect(wrapper.find(".ui-form-title-text").text()).toEqual("title_text");
expect(wrapper.findAll("span[title='Disable']").length).toEqual(1);
expect(wrapper.emitted().input[0][0]).toEqual("initial_value");
wrapper.find(".ui-form-collapsible-icon").trigger("click");
expect(wrapper.emitted().input[0][0]).toEqual("collapsible_value");
expect(wrapper.emitted().input[0][1]).toEqual("input");
expect(wrapper.emitted().input[1][0]).toEqual("collapsible_value");
expect(wrapper.emitted().input[1][1]).toEqual("input");
await Vue.nextTick();
await wrapper.setProps({
collapsedEnableText: "Enable Collapsible",
@@ -54,9 +59,16 @@ describe("FormElement", () => {
expect(wrapper.findAll("span[title='Enable Collapsible']").length).toEqual(1);
expect(wrapper.findAll("span[title='Disable Collapsible']").length).toEqual(0);
wrapper.find(".ui-form-collapsible-icon").trigger("click");
expect(wrapper.emitted().input[1][0]).toEqual("default_value");
expect(wrapper.emitted().input[2][0]).toEqual("default_value");
await Vue.nextTick();
expect(wrapper.findAll("span[title='Disable Collapsible']").length).toEqual(1);
expect(wrapper.findAll("span[title='Enable Collapsible']").length).toEqual(0);
});
it("check type matching", async () => {
await wrapper.setProps({ type: "text" });
expect(wrapper.find("div[id='input'").text()).toEqual("form-input");
await wrapper.setProps({ attributes: { titleonly: true } });
expect(wrapper.find("div[id='input'").text()).toEqual("form-hidden");
});
});
+19 -4
View File
@@ -1,5 +1,5 @@
<template>
<div v-show="!hidden" :id="elementId" :class="['ui-form-element section-row', cls]" :tour_id="id">
<div v-show="!isHidden" :id="elementId" :class="['ui-form-element section-row', cls]" :tour_id="id">
<div v-if="hasError" class="ui-form-error">
<span class="fa fa-exclamation mr-1" />
<span class="ui-form-error-text" v-html="error" />
@@ -22,6 +22,9 @@
</div>
<div v-if="showField" class="ui-form-field" :data-label="title">
<FormBoolean v-if="type == 'boolean'" v-model="currentValue" :id="id" />
<FormHidden v-else-if="isHiddenType" v-model="currentValue" :id="id" :info="attrs['info']" />
<FormColor v-else-if="type == 'color'" v-model="currentValue" :id="id" />
<FormDirectory v-else-if="type == 'directory_uri'" v-model="currentValue" />
<FormParameter
v-else-if="backbonejs"
v-model="currentValue"
@@ -29,8 +32,7 @@
:data-label="title"
:type="type"
:attributes="attrs"
ref="params"
/>
ref="params" />
<FormInput v-else v-model="currentValue" :id="id" :area="attrs['area']" />
</div>
<div v-if="showPreview" class="ui-form-preview" v-html="previewText" />
@@ -42,14 +44,20 @@
import _ from "underscore";
import { getElementId } from "./utilities";
import FormBoolean from "./Elements/FormBoolean";
import FormHidden from "./Elements/FormHidden";
import FormInput from "./Elements/FormInput";
import FormParameter from "./Elements/FormParameter";
import FormColor from "./Elements/FormColor";
import FormDirectory from "./Elements/FormDirectory";
export default {
components: {
FormBoolean,
FormHidden,
FormInput,
FormColor,
FormParameter,
FormDirectory,
},
props: {
id: {
@@ -178,9 +186,15 @@ export default {
}
return help;
},
hidden() {
isHidden() {
return this.attrs["hidden"];
},
isHiddenType() {
return (
["hidden", "hidden_data", "baseurl"].includes(this.type) ||
(this.attributes && this.attributes.titleonly)
);
},
previewText() {
return _.escape(this.textValue).replace(/\n/g, "<br>");
},
@@ -207,6 +221,7 @@ export default {
* Determines to wether expand or collapse the input.
*/
initialState() {
this.setValue(this.value);
const collapsibleValue = this.collapsibleValue;
const value = JSON.stringify(this.value);
this.connected = value == JSON.stringify(this.connectedValue);
+5 -10
View File
@@ -12,15 +12,13 @@
:disabled="sustainConditionals"
:attributes="input.test_param"
:backbonejs="true"
@change="onChange"
/>
@change="onChange" />
<div v-for="(caseDetails, caseId) in input.cases" :key="caseId">
<FormNode
v-if="conditionalMatch(input, caseId)"
v-bind="$props"
:inputs="caseDetails.inputs"
:prefix="getPrefix(input.name)"
/>
:prefix="getPrefix(input.name)" />
</div>
</div>
<div v-else-if="input.type == 'repeat'">
@@ -30,8 +28,7 @@
<FormCard
v-for="(cache, cacheId) in input.cache"
:key="cacheId"
:title="repeatTitle(cacheId, input.title)"
>
:title="repeatTitle(cacheId, input.title)">
<template v-slot:operations>
<b-button
v-if="!sustainRepeats"
@@ -40,8 +37,7 @@
size="sm"
class="float-right"
v-b-tooltip.hover.bottom
@click="repeatDelete(input, cacheId)"
>
@click="repeatDelete(input, cacheId)">
<font-awesome-icon icon="trash-alt" />
</b-button>
</template>
@@ -76,8 +72,7 @@
:collapsed-enable-icon="collapsedEnableIcon"
:collapsed-disable-text="collapsedDisableText"
:collapsed-disable-icon="collapsedDisableIcon"
@change="onChange"
/>
@change="onChange" />
</div>
</div>
</template>
+1 -2
View File
@@ -2,8 +2,7 @@
<div
class="galaxy-loader"
:class="[`galaxy-loader_${style}`, { 'galaxy-loader_center': center }]"
:style="{ transform: `scale(${size / 100})` }"
>
:style="{ transform: `scale(${size / 100})` }">
<div class="galaxy-loader_strip-1"></div>
<div class="galaxy-loader_strip-2"></div>
<div class="galaxy-loader_strip-3"></div>
@@ -6,8 +6,7 @@
:value="value"
@input="input"
:placeholder="'Search Filter' | localize"
data-description="filter text input"
/>
data-description="filter text input" />
</DebouncedInput>
<b-input-group-append>
@@ -16,8 +15,7 @@
:pressed="showDeleted"
:variant="showDeleted ? 'info' : 'secondary'"
@click="showDeleted = !showDeleted"
data-description="show deleted filter toggle"
>
data-description="show deleted filter toggle">
{{ "Deleted" | localize }}
</b-button>
<b-button
@@ -25,8 +23,7 @@
:pressed="showHidden"
:variant="showHidden ? 'info' : 'secondary'"
@click="showHidden = !showHidden"
data-description="show hidden filter toggle"
>
data-description="show hidden filter toggle">
{{ "Hidden" | localize }}
</b-button>
</b-input-group-append>
@@ -34,7 +31,7 @@
</template>
<script>
import { SearchParams } from "./model/SearchParams";
import { SearchParams } from "components/providers/History/SearchParams";
import DebouncedInput from "components/DebouncedInput";
export default {
@@ -8,15 +8,14 @@
@delete="onDelete"
@undelete="onUndelete"
@unhide="onUnhide"
@copy-link="onCopyLink"
/>
@copy-link="onCopyLink" />
</template>
<script>
import DatasetUI from "./DatasetUI";
import { Dataset } from "../../model";
import { deleteContent, updateContentFields } from "../../model/queries";
import { cacheContent } from "../../caching";
import { cacheContent } from "components/providers/History/caching";
import { copy as sendToClipboard } from "utils/clipboard";
import { absPath } from "utils/redirect";
@@ -8,8 +8,7 @@
:disabled="dataset.purged || isIn(STATES.UPLOAD, STATES.NEW)"
@click.stop="viewData"
variant="link"
class="px-1 display-btn"
/>
class="px-1 display-btn" />
<IconButton
v-if="writable && notIn(STATES.DISCARDED)"
icon="pen"
@@ -17,8 +16,7 @@
:disabled="dataset.deleted || isIn(STATES.UPLOAD, STATES.NEW)"
@click.stop="$emit('edit')"
variant="link"
class="px-1 edit-btn"
/>
class="px-1 edit-btn" />
<IconButton
v-if="writable && dataset.accessible"
:icon="dataset.deleted ? 'trash-restore' : 'trash'"
@@ -27,8 +25,7 @@
v-b-modal="bsId('delete-modal')"
variant="link"
class="px-1 delete-btn"
@click.stop
/>
@click.stop />
<b-dropdown
right
@@ -38,8 +35,7 @@
toggle-class="p-1 pl-2"
class="flex-grow-0 dataset-operations-dropdown"
v-if="expanded"
boundary="window"
>
boundary="window">
<template v-slot:button-content>
<Icon icon="ellipsis-v" variant="link" />
<span class="sr-only">Dataset Operations</span>
@@ -53,8 +49,7 @@
backboneRoute('datasets/error', {
dataset_id: dataset.id,
})
"
>
">
<Icon icon="exclamation-triangle" class="mr-1" />
<span v-localize>Show Error</span>
</b-dropdown-item>
@@ -62,8 +57,7 @@
<b-dropdown-item
v-if="showDownloads && dataset.getUrl('download')"
title="Copy Link"
@click.stop="$emit('copy-link')"
>
@click.stop="$emit('copy-link')">
<Icon icon="link" class="mr-1" />
<span v-localize>Copy Link</span>
</b-dropdown-item>
@@ -75,8 +69,7 @@
:title="'Download ' + mf.file_type"
:href="prependPath(dataset.getUrl('meta_download') + mf.file_type)"
target="_blank"
download
>
download>
<Icon icon="download" class="mr-1" />
<span v-localize>{{ "Download " + mf.file_type }}</span>
</b-dropdown-item>
@@ -88,8 +81,7 @@
:href="prependPath(dataset.getUrl('download'))"
target="_blank"
class="download-btn"
download
>
download>
<Icon icon="download" class="mr-1" />
<span v-localize>Download</span>
</b-dropdown-item>
@@ -102,8 +94,7 @@
backboneRoute('/', {
job_id: dataset.creating_job,
})
"
>
">
<Icon icon="play" class="mr-1" />
<span v-localize>Run job again</span>
</b-dropdown-item>
@@ -111,8 +102,7 @@
<b-dropdown-item
v-if="showViz && hasViz && isIn(STATES.OK, STATES.FAILED_METADATA)"
title="Visualize Data"
@click.stop.prevent="visualize"
>
@click.stop.prevent="visualize">
<Icon icon="chart-area" class="mr-1" />
<span v-localize>Visualize Data</span>
</b-dropdown-item>
@@ -120,8 +110,7 @@
<b-dropdown-item
v-if="currentUser && currentUser.id && dataset.creating_job"
title="Tool Help"
@click.stop="showToolHelp(dataset.creating_job)"
>
@click.stop="showToolHelp(dataset.creating_job)">
<Icon icon="question" class="mr-1" />
<span v-localize>Tool Help</span>
</b-dropdown-item>
@@ -131,8 +120,7 @@
key="dataset-details"
title="View Dataset Details"
class="params-btn"
@click.stop.prevent="showDetails"
>
@click.stop.prevent="showDetails">
<Icon icon="info-circle" class="mr-1" />
<span v-localize>View Dataset Details</span>
</b-dropdown-item>
@@ -263,8 +251,9 @@ export default {
// wierd iframe navigation
visualize() {
const showDetailsUrl = `/datasets/${this.dataset.id}/details`;
const redirectParams = {
path: this.dataset.getUrl("show_params"),
path: showDetailsUrl,
title: "Dataset details",
tryIframe: false,
};
@@ -276,13 +265,14 @@ export default {
},
showDetails() {
const showDetailsUrl = `/datasets/${this.dataset.id}/details`;
const redirectParams = {
path: this.dataset.getUrl("show_params"),
path: showDetailsUrl,
title: "Dataset details",
tryIframe: false,
};
if (!this.iframeAdd(redirectParams)) {
this.backboneRoute(this.dataset.getUrl("show_params"));
this.backboneRoute(showDetailsUrl);
}
},
@@ -13,8 +13,7 @@ either through the props, and make updates through the events -->
:data-state="dataset.state"
@keydown.arrow-left.self.stop="$emit('update:expanded', false)"
@keydown.arrow-right.self.stop="$emit('update:expanded', true)"
@keydown.space.self.stop.prevent="$emit('update:selected', !selected)"
>
@keydown.space.self.stop.prevent="$emit('update:selected', !selected)">
<!-- name, state buttons, menus -->
<nav class="content-top-menu p-1 d-flex cursor-pointer" @click.stop="$emit('update:expanded', !expanded)">
<div class="d-flex flex-grow-1 overflow-hidden">
@@ -26,8 +25,7 @@ either through the props, and make updates through the events -->
v-if="!collapsed"
class="status-icon px-1"
:state="dataset.state"
@click.stop="onStatusClick"
/>
@click.stop="onStatusClick" />
<IconButton
v-if="!collapsed && !dataset.visible"
@@ -35,8 +33,7 @@ either through the props, and make updates through the events -->
state="hidden"
title="Unhide"
icon="eye-slash"
@click.stop="$emit('unhide')"
/>
@click.stop="$emit('unhide')" />
<IconButton
v-if="!collapsed && dataset.isDeleted && !dataset.purged"
@@ -44,8 +41,7 @@ either through the props, and make updates through the events -->
state="deleted"
title="Undelete"
icon="trash-restore"
@click.stop="$emit('undelete')"
/>
@click.stop="$emit('undelete')" />
<div class="content-title title p-1 overflow-hidden">
<h5 class="text-truncate" v-if="collapsed">
@@ -62,8 +58,7 @@ either through the props, and make updates through the events -->
:expanded="expanded"
:writable="writable"
@edit="edit"
v-on="$listeners"
/>
v-on="$listeners" />
</slot>
</div>
</nav>
@@ -91,16 +86,14 @@ either through the props, and make updates through the events -->
v-model="name"
placeholder="Dataset Name"
trim
max-rows="4"
></b-textarea>
max-rows="4"></b-textarea>
<b-textarea
class="mb-3"
v-model="annotation"
placeholder="Annotation (optional)"
trim
max-rows="4"
></b-textarea>
max-rows="4"></b-textarea>
<StatelessTags class="mt-2" v-model="tags" />
</div>
@@ -115,8 +108,7 @@ either through the props, and make updates through the events -->
:valid="valid"
:dirty="dirty"
@save="save"
@revert="revert"
/>
@revert="revert" />
</div>
<div class="details">
@@ -8,15 +8,14 @@
@update:dsc="onUpdate"
@delete="onDelete"
@undelete="onUndelete"
@unhide="onUnhide"
/>
@unhide="onUnhide" />
</template>
<script>
import DscUI from "./DscUI";
import { DatasetCollection } from "../../model";
import { deleteDatasetCollection, updateContentFields } from "../../model/queries";
import { cacheContent } from "../../caching";
import { cacheContent } from "components/providers/History/caching";
export default {
components: {
@@ -0,0 +1,26 @@
<template>
<div class="description">
{{ dsc.collectionType | localize }} of {{ dsc.totalElements }}
<b v-if="isHomogeneous">{{ homogeneousDatatype }} </b>datasets
</div>
</template>
<script>
import { DatasetCollection } from "../../model/DatasetCollection";
export default {
props: {
dsc: { type: DatasetCollection, required: true },
},
computed: {
/** @return {Boolean} */
isHomogeneous() {
return this.dsc.isHomogeneous;
},
/** @return {String} */
homogeneousDatatype() {
return this.dsc.homogeneousDatatype;
},
},
};
</script>
@@ -8,8 +8,7 @@
title="View Dataset Collection Details"
@click.stop.prevent="backboneRoute(collectionDetailsPath)"
variant="link"
class="px-1"
>
class="px-1">
</IconButton>
<IconButton
v-if="notIn(STATES.DISCARDED)"
@@ -18,8 +17,7 @@
:disabled="dsc.deleted || isIn(STATES.UPLOAD, STATES.NEW)"
@click.stop="backboneRoute('collection/edit/' + dsc.hdca_id)"
variant="link"
class="px-1 dsc-edit-view"
/>
class="px-1 dsc-edit-view" />
<b-dropdown v-if="notIn(STATES.DISCARDED)" no-caret right variant="link" size="sm" boundary="window">
<template v-slot:button-content>
<Icon icon="trash" variant="link" />
@@ -8,8 +8,7 @@
:id="typedId"
:data-state="dsc.state"
@keydown.arrow-right.self.stop="$emit('viewCollection')"
@keydown.space.self.stop.prevent="$emit('update:selected', !selected)"
>
@keydown.space.self.stop.prevent="$emit('update:selected', !selected)">
<nav class="content-top-menu p-1 d-flex cursor-pointer">
<div class="d-flex flex-grow-1 overflow-hidden">
<div class="pl-1" v-if="showSelection">
@@ -17,16 +16,14 @@
class="selector"
:checked="selected"
@click.stop
@change="$emit('update:selected', $event)"
/>
@change="$emit('update:selected', $event)" />
</div>
<StatusIcon
v-if="dsc.state != 'ok'"
class="status-icon px-1"
:state="dsc.state"
@click.stop="onStatusClick"
/>
@click.stop="onStatusClick" />
<IconButton
v-if="!dsc.visible"
@@ -34,8 +31,7 @@
state="hidden"
title="Unhide"
icon="eye-slash"
@click.stop="$emit('unhide')"
/>
@click.stop="$emit('unhide')" />
<IconButton
v-if="dsc.deleted"
@@ -43,26 +39,14 @@
state="deleted"
title="Undelete"
icon="trash-restore"
@click.stop="$emit('undelete')"
/>
<IconButton
class="px-1"
state="ok"
title="Collection"
icon="folder"
@click.stop="$emit('viewCollection')"
variant="link"
/>
@click.stop="$emit('undelete')" />
<div class="content-title title flex-grow-1 overflow-hidden" @click.stop="$emit('viewCollection')">
<h5 class="text-truncate">
<span class="hid">{{ dsc.hid }}</span>
<span class="name">{{ dsc.name }}</span>
<span class="description">
({{ dsc.collectionType | localize }} {{ dsc.collectionCountDescription | localize }})
</span>
</h5>
<DscDescription :dsc="dsc" />
</div>
</div>
@@ -86,6 +70,7 @@
import { DatasetCollection } from "../../model/DatasetCollection";
import StatusIcon from "../../StatusIcon";
import JobStateProgress from "./JobStateProgress";
import DscDescription from "./DscDescription";
import DscMenu from "./DscMenu";
import { Nametag } from "components/Nametags";
import IconButton from "components/IconButton";
@@ -97,6 +82,7 @@ export default {
DscMenu,
Nametag,
IconButton,
DscDescription,
},
props: {
dsc: { type: DatasetCollection, required: true },
@@ -4,8 +4,7 @@
<DatasetProvider
v-if="contentItem.element_type === 'hda'"
:id="contentItem.object.id"
v-slot="{ item, loading }"
>
v-slot="{ item, loading }">
<div>
<loading-span v-if="loading" message="Loading datasets" />
<DatasetUIWrapper v-else :item="item" :element_identifier="contentItem.element_identifier">
@@ -15,8 +14,7 @@
<DatasetCollectionUIWrapper
v-if="contentItem.element_type === 'dataset_collection'"
:item="contentItem"
:element_count="collectionContents.length + 1"
>
:element_count="collectionContents.length + 1">
</DatasetCollectionUIWrapper>
</div>
</div>
@@ -2,10 +2,8 @@ import DatasetCollectionUIWrapper from "./DatasetCollectionUIWrapper";
import DscUI from "components/History/ContentItem/DatasetCollection/DscUI";
import { shallowMount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import datasetCollectionRaw from "components/History/test/json/DatasetCollection.json";
import datasetCollectionContent from "components/History/test/json/DatasetCollection.nested.json";
jest.mock("components/History/caching");
import datasetCollectionRaw from "components/providers/History/test/json/DatasetCollection.json";
import datasetCollectionContent from "components/providers/History/test/json/DatasetCollection.nested.json";
describe("DatasetUIWrapper.vue with Dataset", () => {
let wrapper;
@@ -10,8 +10,7 @@
@hide="onHide(datasetCollection)"
@unhide="onUnhide(datasetCollection)"
@delete="onDelete(datasetCollection, $event)"
@undelete="onUndelete(datasetCollection)"
/>
@undelete="onUndelete(datasetCollection)" />
<DatasetCollectionContentProvider v-if="expand" :id="datasetCollection.contents_url" v-slot="{ item, loading }">
<b-spinner v-if="loading">Loading Dataset Collection...</b-spinner>
<DatasetCollectionContents v-else :collection-contents="item" />
@@ -20,7 +19,7 @@
</template>
<script>
import { deleteDatasetCollection, updateContentFields } from "../../model/queries";
import { cacheContent } from "../../caching";
import { cacheContent } from "components/providers/History/caching";
import { DatasetCollection } from "../../model/DatasetCollection";
import DscUI from "components/History/ContentItem/DatasetCollection/DscUI";
import { DatasetCollectionContentProvider } from "components/providers";
@@ -1,9 +1,7 @@
import DatasetUIWrapper from "./DatasetUIWrapper";
import DatasetUI from "components/History/ContentItem/Dataset/DatasetUI";
import { shallowMount } from "@vue/test-utils";
import raw from "components/History/test/json/Dataset.json";
jest.mock("components/History/caching");
import raw from "components/providers/History/test/json/Dataset.json";
describe("DatasetUIWrapper.vue with Dataset", () => {
let wrapper;
@@ -11,15 +11,14 @@
@delete="onDelete"
@undelete="onUndelete"
@unhide="onUnhide"
@update="onUpdate"
>
@update="onUpdate">
</DatasetUI>
</template>
<script>
import DatasetUI from "components/History/ContentItem/Dataset/DatasetUI";
import { Dataset } from "../../model";
import { deleteContent, updateContentFields } from "../../model/queries";
import { cacheContent } from "../../caching";
import { cacheContent } from "components/providers/History/caching";
export default {
components: {
@@ -10,13 +10,12 @@ cache first to see if we already have the data -->
:dataset="dataset"
:writable="false"
:selectable="false"
@update:expanded="onExpand"
/>
@update:expanded="onExpand" />
</template>
<script>
import { Dataset } from "../model";
import { getContentByTypeId, cacheContent } from "../caching";
import { getContentByTypeId, cacheContent } from "components/providers/History/caching";
import { getContentDetails } from "../model/queries";
import DatasetUI from "./Dataset/DatasetUI";
@@ -7,22 +7,19 @@
icon="check-square"
:disabled="!totalMatches"
:pressed="hasSelection"
@click="toggleSelectAll"
/>
@click="toggleSelectAll" />
<IconButton
title="Filter History Content"
icon="filter"
:disabled="!totalMatches"
:pressed="showFilter"
@click="showFilter = !showFilter"
data-description="content filter toggle"
/>
data-description="content filter toggle" />
<IconButton
title="Collapse Expanded Items"
icon="compress"
:disabled="!expandedCount"
@click="$emit('collapseAllContent')"
/>
@click="$emit('collapseAllContent')" />
</b-button-group>
<b-button-group>
@@ -31,8 +28,7 @@
size="sm"
text="Selection"
:disabled="!hasSelection"
data-description="selected content menu"
>
data-description="selected content menu">
<b-dropdown-text id="history-op-selected-content">
<span v-localize v-if="hasSelection">With {{ numSelected }} selected items...</span>
<span v-localize v-else>You don't have any selected content.</span>
@@ -57,40 +53,35 @@
<b-dropdown-item
aria-describedby="history-op-selected-content"
v-b-modal:purge-selected-content
:disabled="!hasSelection"
>
:disabled="!hasSelection">
<span v-localize>Permanently Delete</span>
</b-dropdown-item>
<b-dropdown-item
aria-describedby="history-op-selected-content"
@click="buildDatasetList"
data-description="build list"
>
data-description="build list">
<span v-localize>Build Dataset List</span>
</b-dropdown-item>
<b-dropdown-item
aria-describedby="history-op-selected-content"
@click="buildDatasetPair"
data-description="build pair"
>
data-description="build pair">
<span v-localize>Build Dataset Pair</span>
</b-dropdown-item>
<b-dropdown-item
aria-describedby="history-op-selected-content"
@click="buildListOfPairs"
data-description="build list of pairs"
>
data-description="build list of pairs">
<span v-localize>Build List of Dataset Pairs</span>
</b-dropdown-item>
<b-dropdown-item
aria-describedby="history-op-selected-content"
@click="buildCollectionFromRules"
data-description="build collection from rules"
>
data-description="build collection from rules">
<span v-localize>Build Collection from Rules</span>
</b-dropdown-item>
</b-dropdown>
@@ -100,8 +91,7 @@
size="sm"
text="History"
:disabled="!totalMatches"
data-description="history action menu"
>
data-description="history action menu">
<b-dropdown-text id="history-op-all-content">
<span v-localize>With entire history...</span>
</b-dropdown-text>
@@ -109,8 +99,7 @@
<b-dropdown-item
aria-describedby="history-op-all-content"
@click="iframeRedirect('/dataset/copy_datasets')"
data-description="copy datasets"
>
data-description="copy datasets">
<span v-localize>Copy Datasets</span>
</b-dropdown-item>
@@ -172,7 +161,7 @@
</template>
<script>
import { SearchParams } from "./model/SearchParams";
import { SearchParams } from "components/providers/History/SearchParams";
import { History } from "./model/History";
import {
hideSelectedContent,
@@ -185,7 +174,7 @@ import {
purgeAllDeletedContent,
} from "./model";
import { createDatasetCollection } from "./model/queries";
import { cacheContent } from "./caching";
import { cacheContent } from "components/providers/History/caching";
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
import { buildCollectionModal } from "./adapters/buildCollectionModal";
import ContentFilters from "./ContentFilters";
@@ -0,0 +1,33 @@
<template>
<section>
<nav class="d-flex justify-content-end">
<b-button-group>
<IconButton
v-if="isRoot"
class="mb-2 mx-3"
icon="download"
title="Download Collection"
:href="downloadCollectionUrl"
download />
</b-button-group>
</nav>
</section>
</template>
<script>
import IconButton from "components/IconButton";
export default {
components: { IconButton },
props: {
isRoot: { type: Boolean, required: true },
collection: { type: Object, required: true },
},
computed: {
/** @return {String} */
downloadCollectionUrl() {
return `${this.collection.url}/download`;
},
},
};
</script>
@@ -7,9 +7,7 @@
{{ dscName || "(Collection Name)" }}
</h3>
<p class="mt-1">
<i class="fas fa-folder"></i>
a {{ dsc.collectionType | localize }}
{{ dsc.collectionCount | localize }}
<DscDescription :dsc="dsc" />
</p>
<div v-if="isEditing" class="mt-3" @keydown.esc="revertAndCancel" data-description="edit form">
@@ -18,8 +16,7 @@
placeholder="Collection Name"
trim
max-rows="4"
data-description="name input"
></b-textarea>
data-description="name input"></b-textarea>
<StatelessTags v-model="tags" class="mt-3 tags" />
</div>
@@ -37,8 +34,7 @@
:valid="valid"
:dirty="dirty"
@save="save"
@revert="revert"
/>
@revert="revert" />
</section>
</template>
@@ -47,12 +43,14 @@ import { DatasetCollection } from "../model";
import { Nametag } from "components/Nametags";
import EditorMenu from "../EditorMenu";
import { StatelessTags } from "components/Tags";
import DscDescription from "components/History/ContentItem/DatasetCollection/DscDescription";
export default {
components: {
Nametag,
EditorMenu,
StatelessTags,
DscDescription,
},
props: {
dsc: { type: DatasetCollection, required: true },
@@ -6,20 +6,19 @@
<ExpandedItems
:scope-key="selectedCollection.id"
:get-item-key="(item) => item.type_id"
v-slot="{ isExpanded, setExpanded }"
>
v-slot="{ isExpanded, setExpanded }">
<Layout class="dataset-collection-panel">
<template v-slot:globalNav>
<TopNav :history="history" :selected-collections="selectedCollections" v-on="$listeners" />
</template>
<template v-slot:localNav>
<IconButton
icon="download"
title="Download Collection"
:href="downloadCollectionUrl"
download
/>
<!-- Empty -->
<div />
</template>
<template v-slot:listcontrols>
<CollectionOperations :collection="selectedCollection" :is-root="isRoot" />
</template>
<template v-slot:details>
@@ -32,8 +31,7 @@
:class="{ loadingBackground: loading }"
v-bind="payload"
@scroll="setScrollPos"
:debug="false"
>
:debug="false">
<template v-slot="{ item, index, rowKey }">
<CollectionContentItem
:item="item"
@@ -45,8 +43,7 @@
@update:expanded="setExpanded(item, $event)"
@viewCollection="$emit('viewCollection', item)"
:data-index="index"
:data-row-key="rowKey"
/>
:data-row-key="rowKey" />
</template>
</Scroller>
</template>
@@ -59,17 +56,18 @@
<script>
import { History } from "../model";
import { updateContentFields } from "../model/queries";
import { cacheContent } from "../caching";
import { cacheContent } from "components/providers/History/caching";
import { DscProvider, CollectionContentProvider, ExpandedItems } from "../providers";
import { DscProvider, CollectionContentProvider } from "components/providers/History";
import ExpandedItems from "../ExpandedItems";
import Layout from "../Layout";
import TopNav from "./TopNav";
import CollectionOperations from "./CollectionOperations.vue";
import Details from "./Details";
import Scroller from "../Scroller";
import { CollectionContentItem } from "../ContentItem";
import { reportPayload } from "../providers/ContentProvider/helpers";
import IconButton from "components/IconButton";
import { reportPayload } from "components/providers/History/ContentProvider/helpers";
export default {
filters: {
@@ -84,7 +82,7 @@ export default {
Scroller,
CollectionContentItem,
ExpandedItems,
IconButton,
CollectionOperations,
},
props: {
history: { type: History, required: true },
@@ -97,7 +95,7 @@ export default {
return selected;
},
isRoot() {
return this.selectedCollection == this.selectedCollections[0];
return this.selectedCollection == this.rootCollection;
},
writable() {
return this.isRoot;
@@ -5,8 +5,7 @@
text="Return to..."
boundary="viewport"
data-description="collection breadcrumbs menu"
no-caret
>
no-caret>
<template v-slot:button-content>
<Icon icon="arrow-up" class="mr-1" />
<b class="text-nowrap" v-localize>Return to...</b>
@@ -27,8 +26,7 @@
size="sm"
title="`Return to: ${history.name}`"
@click="close"
data-description="back to history"
>
data-description="back to history">
<Icon icon="arrow-up" class="mr-1" />
<b class="text-nowrap">Return to: {{ history.name }}</b>
</b-button>
@@ -7,8 +7,7 @@
v-on="handlers"
:histories="histories"
:current-history="currentHistory"
title="Histories"
/>
title="Histories" />
</template>
</HistoryPanel>
@@ -21,7 +20,7 @@
<script>
import CurrentUser from "components/providers/CurrentUser";
import UserHistories from "./providers/UserHistories";
import UserHistories from "components/providers/History/UserHistories";
import HistoryPanel from "./HistoryPanel";
import HistoriesMenu from "./HistoriesMenu";
+4 -8
View File
@@ -6,8 +6,7 @@
:pressed="editing"
icon="pen"
@click="$emit('update:editing', !editing)"
data-description="editor toggle"
/>
data-description="editor toggle" />
<template v-if="editing">
<!-- save -->
@@ -17,8 +16,7 @@
:class="{ disabled: !valid }"
icon="save"
@click="$emit('save')"
data-description="editor save button"
/>
data-description="editor save button" />
<!-- revert -->
<IconButton
@@ -27,8 +25,7 @@
:class="{ disabled: !dirty }"
icon="undo"
@click="$emit('revert')"
data-description="editor revert button"
/>
data-description="editor revert button" />
<!-- form status -->
<IconButton
@@ -37,8 +34,7 @@
:class="{ valid: valid, disabled: !valid }"
:icon="valid ? 'check' : 'exclamation-triangle'"
variant="link"
data-description="editor status"
/>
data-description="editor status" />
</template>
</nav>
</template>
@@ -1,4 +1,4 @@
import { saveSet, loadSet } from "./setCache";
import { saveSet, loadSet } from "utils/setCache";
export default {
props: {
@@ -9,8 +9,7 @@
:text="title | l"
toggle-class="text-decoration-none"
class="histories-operation-menu"
data-description="histories operation menu"
>
data-description="histories operation menu">
<template v-slot:button-content>
<Icon fixed-width class="mr-1" icon="folder" />
<span class="text-nowrap">{{ title | l }}</span>
@@ -39,8 +38,7 @@
<b-dropdown-item
data-description="switch to multi history view"
@click="redirect('/history/view_multiple')"
>
@click="redirect('/history/view_multiple')">
<Icon fixed-width class="mr-1" icon="columns" />
<span v-localize>Show Histories Side-by-Side</span>
</b-dropdown-item>
@@ -58,8 +56,7 @@
id="history-selector-modal"
:histories="histories"
:current-history="currentHistory"
@selectHistory="$emit('setCurrentHistory', $event)"
/>
@selectHistory="$emit('setCurrentHistory', $event)" />
</div>
</template>
+12 -15
View File
@@ -5,13 +5,11 @@
:disable-poll="false"
:debug="false"
:debounce-period="500"
v-slot="{ loading, payload, manualReload, setScrollPos }"
>
v-slot="{ loading, payload, manualReload, setScrollPos }">
<ExpandedItems
:scope-key="history.id"
:get-item-key="(item) => item.type_id"
v-slot="{ expandedCount, isExpanded, setExpanded, collapseAll }"
>
v-slot="{ expandedCount, isExpanded, setExpanded, collapseAll }">
<SelectedItems
:scope-key="history.id"
:get-item-key="(item) => item.type_id"
@@ -23,8 +21,7 @@
isSelected,
setSelected,
resetSelection,
}"
>
}">
<Layout>
<template v-slot:globalNav>
<slot name="globalNav" :history="history"></slot>
@@ -56,8 +53,7 @@
@selectAllContent="selectItems(payload.contents)"
@manualReload="manualReload"
:expanded-count="expandedCount"
@collapseAllContent="collapseAll"
/>
@collapseAllContent="collapseAll" />
</template>
<template v-slot:listing>
@@ -69,8 +65,7 @@
key-field="hid"
v-bind="payload"
:debug="false"
@scroll="setScrollPos"
>
@scroll="setScrollPos">
<template v-slot="{ item, index, rowKey }">
<HistoryContentItem
:item="item"
@@ -84,8 +79,7 @@
@viewCollection="$emit('viewCollection', item)"
:data-hid="item.hid"
:data-index="index"
:data-row-key="rowKey"
/>
:data-row-key="rowKey" />
</template>
</Scroller>
</template>
@@ -100,8 +94,11 @@
</template>
<script>
import { History, SearchParams } from "./model";
import { HistoryContentProvider, ExpandedItems, SelectedItems } from "./providers";
import { History } from "./model";
import { SearchParams } from "components/providers/History/SearchParams";
import { HistoryContentProvider } from "components/providers/History/";
import ExpandedItems from "./ExpandedItems";
import SelectedItems from "./SelectedItems";
import Layout from "./Layout";
import HistoryMessages from "./HistoryMessages";
import HistoryDetails from "./HistoryDetails";
@@ -110,7 +107,7 @@ import ContentOperations from "./ContentOperations";
import ToolHelpModal from "./ToolHelpModal";
import Scroller from "./Scroller";
import { HistoryContentItem } from "./ContentItem";
import { reportPayload } from "./providers/ContentProvider/helpers";
import { reportPayload } from "components/providers/History/ContentProvider/helpers";
import HistoryMenu from "./HistoryMenu";
export default {
@@ -26,8 +26,7 @@ Emit new History model object when done via .sync syntax
placeholder="History Name"
trim
max-rows="4"
data-description="name input"
></b-textarea>
data-description="name input"></b-textarea>
<b-textarea
class="mb-2"
@@ -35,8 +34,7 @@ Emit new History model object when done via .sync syntax
placeholder="Annotation (optional)"
trim
max-rows="4"
data-description="annotation input"
></b-textarea>
data-description="annotation input"></b-textarea>
<StatelessTags class="mt-3 tags" v-model="tags" />
</div>
@@ -51,8 +49,7 @@ Emit new History model object when done via .sync syntax
:valid="valid"
:dirty="dirty"
@save="save"
@revert="revert"
/>
@revert="revert" />
</section>
</template>
@@ -5,8 +5,7 @@
class="history-dropdown font-weight-bold"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
aria-expanded="false">
{{ history.name }}
</b-link>
<div class="dropdown-menu" aria-labelledby="history-dropdown">
@@ -5,8 +5,7 @@
variant="link"
text="Histories"
toggle-class="text-decoration-none"
data-description="history menu"
>
data-description="history menu">
<template v-slot:button-content>
<Icon fixed-width icon="cog" />
<span class="sr-only">Operations on the current history</span>
@@ -82,8 +81,7 @@
id="history-privacy-modal"
title="Make History Private"
title-tag="h2"
@ok="$emit('secureHistory', history)"
>
@ok="$emit('secureHistory', history)">
<p v-localize>
This will make all the data in this history private (excluding library datasets), and will set
permissions such that all new data is created as private. Any datasets within that are currently shared
@@ -99,8 +97,7 @@
id="purge-history-modal"
title="Permanently Delete History?"
title-tag="h2"
@ok="$emit('purgeHistory', history)"
>
@ok="$emit('purgeHistory', history)">
<p v-localize>Really delete the current history permanently? This cannot be undone.</p>
</b-modal>
@@ -6,20 +6,16 @@ selected datset collections -->
v-if="history && !breadcrumbs.length"
:history="history"
v-on="$listeners"
@viewCollection="drillDown"
>
@viewCollection="drillDown">
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
<slot :name="name" v-bind="slotData" :history="history" />
</template>
</HistoryComponent>
<CurrentCollection
v-else-if="breadcrumbs.length"
:history="history"
:selected-collections.sync="breadcrumbs"
@viewCollection="drillDown"
/>
@viewCollection="drillDown" />
<div v-else>
<span class="sr-only">Loading...</span>
</div>
@@ -14,8 +14,7 @@
:current-page="currentPage"
@filtered="onFiltered"
@row-clicked="switchToHistory"
sticky-header="50vh"
>
sticky-header="50vh">
<template v-slot:cell(tags)="row">
<!-- just display tags, don't allow editing -->
<stateless-tags :value="row.item.tags" :disabled="true" />
@@ -19,8 +19,7 @@
:selectable="true"
select-mode="single"
selected-variant="success"
@row-selected="switchToHistory"
>
@row-selected="switchToHistory">
<template v-slot:cell(tags)="row">
<stateless-tags :value="row.item.tags" :disabled="true" />
</template>
+3 -5
View File
@@ -8,8 +8,7 @@
v-for="(item, index) in itemWindow"
:key="getItemKey(item)"
:data-row-index="index"
:data-row-key="getItemKey(item)"
>
:data-row-key="getItemKey(item)">
<slot :item="item" :index="index" :row-key="getItemKey(item)">
<pre>{{ item }}</pre>
</slot>
@@ -25,8 +24,7 @@
ref="scrollSliderContainer"
class="scrollSliderContainer"
:style="scrollSliderContainerStyles"
@scroll="onScroll"
>
@scroll="onScroll">
<div ref="scrollSlider"></div>
</div>
@@ -40,7 +38,7 @@
<script>
import { debounce } from "lodash";
import { SearchParams } from "./model";
import { SearchParams } from "components/providers/History/SearchParams";
import { clamp } from "utils/math";
const clean = (o) => JSON.parse(JSON.stringify(o));
+1 -2
View File
@@ -6,8 +6,7 @@
:icon="stateIcon"
:variant="variant"
v-on="$listeners"
v-bind="$attrs"
/>
v-bind="$attrs" />
</template>
<script>

Some files were not shown because too many files have changed in this diff Show More