mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'dev' of https://github.com/galaxyproject/galaxy into menu-cleanup
This commit is contained in:
@@ -0,0 +1 @@
|
||||
comment: false
|
||||
@@ -10,10 +10,13 @@ on:
|
||||
- 'client/**'
|
||||
- 'doc/**'
|
||||
- 'lib/galaxy_test/selenium/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_DEPENDENCIES_INSTALL_WEASYPRINT: '1'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
|
||||
GALAXY_DEPENDENCIES_INSTALL_WEASYPRINT: '1'
|
||||
GALAXY_CONFIG_SQLALCHEMY_WARN_20: '1'
|
||||
concurrency:
|
||||
group: api-${{ github.ref }}
|
||||
@@ -35,6 +38,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
@@ -53,11 +60,15 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-api
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh --skip_flakey_fails -api
|
||||
run: ./run_tests.sh --coverage --skip_flakey_fails -api
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: api
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
paths-ignore:
|
||||
- 'client/**'
|
||||
- 'doc/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
|
||||
concurrency:
|
||||
@@ -21,6 +24,10 @@ jobs:
|
||||
matrix:
|
||||
python-version: ['3.7']
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
|
||||
@@ -53,11 +53,15 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.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 }}"
|
||||
run: ./run_tests.sh --coverage --skip_flakey_fails -cwl lib/galaxy_test/api/cwl -- -m "${{ matrix.marker }} and ${{ matrix.conformance-version }}"
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: cwl-conformance
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -22,10 +22,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.7', '3.10']
|
||||
webserver: ['uwsgi']
|
||||
include:
|
||||
- python-version: '3.10'
|
||||
webserver: 'dev'
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -l {0}
|
||||
@@ -57,11 +53,6 @@ jobs:
|
||||
yarn-lock-file: 'galaxy root/client/yarn.lock'
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
# Use this job to test the latest migrations
|
||||
- run: wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0141.sqlite
|
||||
- run: mv db_gx_rev_0141.sqlite 'galaxy root'/database/universe.sqlite
|
||||
- name: run tests
|
||||
run: tox -e first_startup
|
||||
working-directory: 'galaxy root'
|
||||
env:
|
||||
APP_WEBSERVER: ${{ matrix.webserver }}
|
||||
|
||||
@@ -10,6 +10,9 @@ on:
|
||||
- 'client/**'
|
||||
- 'doc/**'
|
||||
- 'lib/galaxy_test/selenium/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
|
||||
@@ -33,6 +36,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
@@ -51,11 +58,15 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-framework
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh --framework
|
||||
run: ./run_tests.sh --coverage --framework
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: framework
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -10,6 +10,9 @@ on:
|
||||
- 'client/**'
|
||||
- 'doc/**'
|
||||
- 'lib/galaxy_test/selenium/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_TEST_AMQP_URL: 'amqp://localhost:5672//'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
@@ -41,6 +44,11 @@ jobs:
|
||||
ports:
|
||||
- 5672:5672
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
# Skip outputs_to_working_directory: true in integration tests, doesn't work with pulsar
|
||||
# echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- name: Prune unused docker image, volumes and containers
|
||||
run: docker system prune -a -f
|
||||
- name: Clean dotnet folder for space
|
||||
@@ -74,15 +82,19 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-integration
|
||||
- name: Install ffmpeg
|
||||
run: sudo apt-get update && sudo apt-get -y install ffmpeg
|
||||
- name: Run tests
|
||||
run: |
|
||||
. .ci/minikube-test-setup/start_services.sh
|
||||
./run_tests.sh -integration test/integration -- --num-shards=4 --shard-id=${{ matrix.chunk }}
|
||||
./run_tests.sh --coverage -integration test/integration -- --num-shards=4 --shard-id=${{ matrix.chunk }}
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: integration
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_SKIP_CLIENT_BUILD: '0'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
@@ -13,6 +16,7 @@ env:
|
||||
GALAXY_TEST_SELENIUM_RETRIES: 1
|
||||
YARN_INSTALL_OPTS: --frozen-lockfile
|
||||
GALAXY_CONFIG_SQLALCHEMY_WARN_20: '1'
|
||||
GALAXY_DEPENDENCIES_INSTALL_WEASYPRINT: '1'
|
||||
concurrency:
|
||||
group: integration-selenium-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -33,6 +37,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- name: Prune unused docker image, volumes and containers
|
||||
run: docker system prune -a -f
|
||||
- uses: actions/checkout@v2
|
||||
@@ -53,15 +61,19 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-integration-selenium
|
||||
- uses: mvdbeek/gha-yarn-cache@master
|
||||
with:
|
||||
yarn-lock-file: 'galaxy root/client/yarn.lock'
|
||||
- uses: nanasess/setup-chromedriver@master
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh -integration test/integration_selenium
|
||||
run: ./run_tests.sh --coverage -integration test/integration_selenium
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: integration-selenium
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -3,9 +3,11 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- 'client/**'
|
||||
- '.github/workflows/jest.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'client/**'
|
||||
- '.github/workflows/jest.yaml'
|
||||
concurrency:
|
||||
group: client-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -14,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14]
|
||||
node: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node
|
||||
|
||||
@@ -10,6 +10,9 @@ on:
|
||||
- 'client/**'
|
||||
- 'doc/**'
|
||||
- 'lib/galaxy_test/selenium/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
concurrency:
|
||||
@@ -32,6 +35,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
@@ -50,7 +57,7 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-performance
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh --ci_test_metrics --structured_data_html --structured_data_report_file "test.json" --skip_flakey_fails -api lib/galaxy_test/performance
|
||||
|
||||
@@ -12,7 +12,6 @@ concurrency:
|
||||
group: reports-startup-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
|
||||
test:
|
||||
name: Reports startup test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -41,16 +40,16 @@ jobs:
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('galaxy root/requirements.txt') }}
|
||||
- name: Cache tox env
|
||||
- name: Cache galaxy venv
|
||||
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
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ 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
|
||||
- name: Run tests
|
||||
run: tox -e reports_startup
|
||||
working-directory: 'galaxy root'
|
||||
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_CONFIG_GALAXY_URL_PREFIX: '/galaxypf'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
@@ -36,6 +39,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
@@ -54,15 +61,19 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-selenium
|
||||
- uses: mvdbeek/gha-yarn-cache@master
|
||||
with:
|
||||
yarn-lock-file: 'galaxy root/client/yarn.lock'
|
||||
- uses: nanasess/setup-chromedriver@master
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh -selenium lib/galaxy_test/selenium -- --num-shards=3 --shard-id=${{ matrix.chunk }}
|
||||
run: ./run_tests.sh --coverage -selenium lib/galaxy_test/selenium -- --num-shards=3 --shard-id=${{ matrix.chunk }}
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: selenium
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
schedule:
|
||||
# Run at midnight UTC every Tuesday
|
||||
- cron: '0 0 * * 2'
|
||||
env:
|
||||
GALAXY_CONFIG_GALAXY_URL_PREFIX: '/galaxypf'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
@@ -37,6 +40,10 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- if: github.event_name == 'schedule'
|
||||
run: |
|
||||
echo "GALAXY_CONFIG_OVERRIDE_METADATA_STRATEGY=extended" >> $GITHUB_ENV
|
||||
echo "GALAXY_CONFIG_OVERRIDE_OUTPUTS_TO_WORKING_DIRECTORY=true" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'galaxy root'
|
||||
@@ -55,15 +62,19 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-selenium-beta
|
||||
- uses: mvdbeek/gha-yarn-cache@master
|
||||
with:
|
||||
yarn-lock-file: 'galaxy root/client/yarn.lock'
|
||||
- uses: nanasess/setup-chromedriver@master
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh -selenium lib/galaxy_test/selenium -- --num-shards=3 --shard-id=${{ matrix.chunk }}
|
||||
run: ./run_tests.sh --coverage -selenium lib/galaxy_test/selenium -- --num-shards=3 --shard-id=${{ matrix.chunk }}
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: selenium-beta-history
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .venv
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-toolshed
|
||||
- name: Run tests
|
||||
run: './run_tests.sh -toolshed'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: API (legacy paste) tests
|
||||
name: Unit w/postgres tests
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
@@ -11,24 +11,21 @@ on:
|
||||
- 'doc/**'
|
||||
- 'lib/galaxy_test/selenium/**'
|
||||
env:
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8'
|
||||
GALAXY_TEST_USE_UVICORN: false
|
||||
GALAXY_TEST_RAISE_EXCEPTION_ON_HISTORYLESS_HDA: '1'
|
||||
GALAXY_CONFIG_SQLALCHEMY_WARN_20: '1'
|
||||
GALAXY_TEST_DBURI: 'postgresql://postgres:postgres@localhost:5432/postgres?client_encoding=utf8' # using postgres as the db
|
||||
concurrency:
|
||||
group: api-legacy-${{ github.ref }}
|
||||
group: py-unit-postgres-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.7']
|
||||
services:
|
||||
postgres:
|
||||
# Run tests on the oldest Galaxy-supported version
|
||||
image: postgres:9.6
|
||||
image: postgres:13
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
@@ -54,13 +51,8 @@ jobs:
|
||||
- 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') }}-api-paste
|
||||
- name: Run tests
|
||||
run: ./run_tests.sh --skip_flakey_fails -api
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-unit-postgres
|
||||
- name: Run migration tests
|
||||
run: ./run_tests.sh -unit test/unit/data/model/migrations/test_migrations.py
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: API (legacy paste) test results (${{ matrix.python-version }})
|
||||
path: 'galaxy root/run_api_tests.html'
|
||||
@@ -37,18 +37,20 @@ jobs:
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('galaxy root/requirements.txt') }}
|
||||
- name: Cache tox env
|
||||
- name: Cache galaxy venv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .tox
|
||||
key: tox-cache-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-unit
|
||||
path: 'galaxy root/.venv'
|
||||
key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-unit
|
||||
- name: Install ffmpeg
|
||||
run: sudo apt-get update && sudo apt-get -y install ffmpeg
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
- name: Run tests
|
||||
run: tox -e unit
|
||||
run: ./run_tests.sh --coverage -u
|
||||
working-directory: 'galaxy root'
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: py-unit
|
||||
working-directory: 'galaxy root'
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
|
||||
+7
-1
@@ -22,6 +22,7 @@ ARG STAGE1_BASE=python:3.7-slim
|
||||
ARG FINAL_STAGE_BASE=$STAGE1_BASE
|
||||
ARG GALAXY_USER=galaxy
|
||||
ARG GALAXY_PLAYBOOK_REPO=https://github.com/galaxyproject/galaxy-docker-k8s
|
||||
ARG GALAXY_PLAYBOOK_BRANCH=v2.0.0
|
||||
|
||||
ARG GIT_COMMIT=unspecified
|
||||
ARG BUILD_DATE=unspecified
|
||||
@@ -34,6 +35,7 @@ FROM $STAGE1_BASE AS stage1
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SERVER_DIR
|
||||
ARG GALAXY_PLAYBOOK_REPO
|
||||
ARG GALAXY_PLAYBOOK_BRANCH
|
||||
|
||||
# Init Env
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
@@ -57,7 +59,7 @@ RUN set -xe; \
|
||||
WORKDIR /tmp/ansible
|
||||
RUN rm -rf *
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
RUN git clone --depth 1 $GALAXY_PLAYBOOK_REPO galaxy-docker
|
||||
RUN git clone --depth 1 --branch $GALAXY_PLAYBOOK_BRANCH $GALAXY_PLAYBOOK_REPO galaxy-docker
|
||||
WORKDIR /tmp/ansible/galaxy-docker
|
||||
RUN ansible-galaxy install -r requirements.yml -p roles --force-with-deps
|
||||
|
||||
@@ -176,6 +178,10 @@ COPY --chown=$GALAXY_USER:$GALAXY_USER --from=server_build $ROOT_DIR .
|
||||
COPY --chown=$GALAXY_USER:$GALAXY_USER --from=client_build $SERVER_DIR/static ./server/static
|
||||
|
||||
WORKDIR $SERVER_DIR
|
||||
|
||||
# The data in version.json will be displayed in Galaxy's /api/version endpoint
|
||||
RUN printf "{\n \"git_commit\": \"$(cat GITREVISION)\",\n \"build_date\": \"$BUILD_DATE\",\n \"image_tag\": \"$IMAGE_TAG\"\n}\n" > version.json
|
||||
|
||||
EXPOSE 8080
|
||||
USER $GALAXY_USER
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ 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: ## Format Python code changes
|
||||
$(IN_VENV) darker -r $(TARGET_BRANCH)
|
||||
diff-format: ## Format Python code changes since last commit
|
||||
$(IN_VENV) darker .
|
||||
|
||||
format: ## Format Python code base
|
||||
$(IN_VENV) isort .
|
||||
@@ -64,9 +64,6 @@ open-docs: docs _open-docs ## generate Sphinx HTML documentation and open in bro
|
||||
open-project: ## open project on github
|
||||
$(OPEN_RESOURCE) $(PROJECT_URL)
|
||||
|
||||
uwsgi-rebuild-validation: ## rebuild uwsgi_config.yml kwalify schema against latest uwsgi master.
|
||||
$(CONFIG_MANAGE) build_uwsgi_yaml
|
||||
|
||||
tool-shed-config-validate: ## validate tool shed YAML configuration file
|
||||
$(CONFIG_MANAGE) validate tool_shed
|
||||
|
||||
|
||||
+2
-2
@@ -24,12 +24,12 @@ Community support is available at `Galaxy Help <https://help.galaxyproject.org/>
|
||||
Galaxy Quickstart
|
||||
=================
|
||||
|
||||
Galaxy requires Python 3.6 . To check your Python version, run:
|
||||
Galaxy requires Python 3.7 . To check your Python version, run:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ python -V
|
||||
Python 3.6.10
|
||||
Python 3.7.6
|
||||
|
||||
Start Galaxy:
|
||||
|
||||
|
||||
+50
-31
@@ -35,16 +35,16 @@ const DIST_PLUGIN_BUILD_IDS = ["new_user"];
|
||||
|
||||
const PLUGIN_BUILD_IDS = Array.prototype.concat(DIST_PLUGIN_BUILD_IDS, STATIC_PLUGIN_BUILD_IDS);
|
||||
|
||||
const paths = {
|
||||
node_modules: "./node_modules",
|
||||
plugin_dirs: [
|
||||
const PATHS = {
|
||||
nodeModules: "./node_modules",
|
||||
pluginDirs: [
|
||||
"../config/plugins/{visualizations,interactive_environments,welcome_page}/*/static/**/*",
|
||||
"../config/plugins/{visualizations,interactive_environments,welcome_page}/*/*/static/**/*",
|
||||
],
|
||||
plugin_build_modules: [
|
||||
pluginBuildModules: [
|
||||
`../config/plugins/{visualizations,welcome_page}/{${PLUGIN_BUILD_IDS.join(",")}}/package.json`,
|
||||
],
|
||||
lib_locs: {
|
||||
stagedLibraries: {
|
||||
// This is a stepping stone towards having all this staged
|
||||
// automatically. Eventually, this dictionary and staging step will
|
||||
// not be necessary.
|
||||
@@ -57,13 +57,12 @@ const paths = {
|
||||
requirejs: ["require.js", "require.js"],
|
||||
underscore: ["underscore.js", "underscore.js"],
|
||||
},
|
||||
libs: ["src/libs/**/*.js"],
|
||||
};
|
||||
|
||||
function stageLibs(callback) {
|
||||
Object.keys(paths.lib_locs).forEach((lib) => {
|
||||
var p1 = path.resolve(path.join(paths.node_modules, lib, paths.lib_locs[lib][0]));
|
||||
var p2 = path.resolve(path.join("src", "libs", paths.lib_locs[lib][1]));
|
||||
Object.keys(PATHS.stagedLibraries).forEach((lib) => {
|
||||
var p1 = path.resolve(path.join(PATHS.nodeModules, lib, PATHS.stagedLibraries[lib][0]));
|
||||
var p2 = path.resolve(path.join("src", "libs", PATHS.stagedLibraries[lib][1]));
|
||||
if (fs.existsSync(p1)) {
|
||||
del.sync(p2);
|
||||
fs.createReadStream(p1).pipe(fs.createWriteStream(p2));
|
||||
@@ -78,45 +77,49 @@ function stageLibs(callback) {
|
||||
}
|
||||
|
||||
function fonts() {
|
||||
return src(path.resolve(path.join(paths.node_modules, "font-awesome/fonts/**/*"))).pipe(
|
||||
return src(path.resolve(path.join(PATHS.nodeModules, "font-awesome/fonts/**/*"))).pipe(
|
||||
dest("../static/images/fonts")
|
||||
);
|
||||
}
|
||||
|
||||
function stagePlugins() {
|
||||
return src(paths.plugin_dirs).pipe(dest("../static/plugins/"));
|
||||
return src(PATHS.pluginDirs).pipe(dest("../static/plugins/"));
|
||||
}
|
||||
|
||||
function buildPlugins(callback) {
|
||||
function buildPlugins(callback, forceRebuild) {
|
||||
/*
|
||||
* Walk plugin_build_modules glob and attempt to build modules.
|
||||
* Walk pluginBuildModules glob and attempt to build modules.
|
||||
* */
|
||||
paths.plugin_build_modules.map((build_module) => {
|
||||
PATHS.pluginBuildModules.map((build_module) => {
|
||||
glob(build_module, {}, (er, files) => {
|
||||
files.map((file) => {
|
||||
let skip_build = false;
|
||||
let skipBuild = false;
|
||||
const f = path.join(process.cwd(), file).slice(0, -12);
|
||||
const plugin_name = path.dirname(file).split(path.sep).pop();
|
||||
const hash_file_path = path.join(
|
||||
const pluginName = path.dirname(file).split(path.sep).pop();
|
||||
const hashFilePath = path.join(
|
||||
f,
|
||||
DIST_PLUGIN_BUILD_IDS.indexOf(plugin_name) > -1 ? "dist" : "static",
|
||||
DIST_PLUGIN_BUILD_IDS.indexOf(pluginName) > -1 ? "dist" : "static",
|
||||
"plugin_build_hash.txt"
|
||||
);
|
||||
|
||||
if (fs.existsSync(hash_file_path)) {
|
||||
skip_build =
|
||||
child_process.spawnSync("git", ["diff", "--quiet", `$(cat ${hash_file_path})`, "--", f], {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
}).status === 0;
|
||||
if (!!forceRebuild) {
|
||||
skipBuild = false;
|
||||
} else {
|
||||
console.log(`No build hashfile detected for ${plugin_name}, generating now.`);
|
||||
if (fs.existsSync(hashFilePath)) {
|
||||
skipBuild =
|
||||
child_process.spawnSync("git", ["diff", "--quiet", `$(cat ${hashFilePath})`, "--", f], {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
}).status === 0;
|
||||
} else {
|
||||
console.log(`No build hashfile detected for ${pluginName}, generating now.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (skip_build) {
|
||||
console.log(`No changes detected for ${plugin_name}`);
|
||||
if (skipBuild) {
|
||||
console.log(`No changes detected for ${pluginName}`);
|
||||
} else {
|
||||
console.log(`Installing Dependencies for ${plugin_name}`);
|
||||
console.log(`Installing Dependencies for ${pluginName}`);
|
||||
child_process.spawnSync(
|
||||
"yarn",
|
||||
["install", "--production=false", "--network-timeout=300000", "--check-files"],
|
||||
@@ -126,9 +129,19 @@ function buildPlugins(callback) {
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
console.log(`Building ${plugin_name}`);
|
||||
child_process.spawnSync("yarn", ["build"], { cwd: f, stdio: "inherit", shell: true });
|
||||
child_process.exec(`(git rev-parse HEAD 2>/dev/null || echo \`\`) > ${hash_file_path}`);
|
||||
console.log(`Building ${pluginName}`);
|
||||
|
||||
if (
|
||||
child_process.spawnSync("yarn", ["build"], { cwd: f, stdio: "inherit", shell: true }).status ===
|
||||
0
|
||||
) {
|
||||
console.log(`Successfully built, saving build state to ${hashFilePath}`);
|
||||
child_process.exec(`(git rev-parse HEAD 2>/dev/null || echo \`\`) > ${hashFilePath}`);
|
||||
} else {
|
||||
console.error(
|
||||
`Error building ${pluginName}, not saving build state. Please report this issue to the Galaxy Team.`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -136,12 +149,17 @@ function buildPlugins(callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
function forceBuildPlugins(callback) {
|
||||
return buildPlugins(callback, true);
|
||||
}
|
||||
|
||||
function cleanPlugins() {
|
||||
return del(["../static/plugins/{visualizations,interactive_environments,welcome_page}/*"], { force: true });
|
||||
}
|
||||
|
||||
const client = parallel(fonts, stageLibs);
|
||||
const plugins = series(buildPlugins, cleanPlugins, stagePlugins);
|
||||
const pluginsRebuild = series(forceBuildPlugins, cleanPlugins, stagePlugins);
|
||||
|
||||
function watchPlugins() {
|
||||
const BUILD_PLUGIN_WATCH_GLOB = [
|
||||
@@ -152,5 +170,6 @@ function watchPlugins() {
|
||||
|
||||
module.exports.client = client;
|
||||
module.exports.plugins = plugins;
|
||||
module.exports.pluginsRebuild = pluginsRebuild;
|
||||
module.exports.watchPlugins = watchPlugins;
|
||||
module.exports.default = parallel(client, plugins);
|
||||
|
||||
+5
-10
@@ -20,7 +20,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.6",
|
||||
"@galaxyproject/bootstrap-tour": "^0.12.1",
|
||||
"@galaxyproject/bootstrap-tour": "0.12.2",
|
||||
"@handsontable/vue": "^2.0.0-beta1",
|
||||
"@hirez_io/observer-spy": "^2.1.2",
|
||||
"@johmun/vue-tags-input": "^2.1.0",
|
||||
@@ -57,17 +57,12 @@
|
||||
"jspdf": "^2.5.1",
|
||||
"linkify-html": "^3.0.5",
|
||||
"linkifyjs": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-regexp": "^0.4.0",
|
||||
"moment": "2.29.1",
|
||||
"object-hash": "^2.2.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"pouchdb": "^7.2.2",
|
||||
"pouchdb-adapter-memory": "^7.2.2",
|
||||
"pouchdb-debug": "^7.2.1",
|
||||
"pouchdb-erase": "^1.0.2",
|
||||
"pouchdb-find": "^7.2.2",
|
||||
"pouchdb-upsert": "^2.2.0",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"proper-skip-list": "^4.1.0",
|
||||
"pyre-to-regexp": "^0.0.5",
|
||||
@@ -97,12 +92,12 @@
|
||||
"vue-router": "^3.5.3",
|
||||
"vue-rx": "^6.2.0",
|
||||
"vue-scrollto": "^2.20.0",
|
||||
"vue-virtual-scroll-list": "^2.3.3",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vueisotope": "^3.1.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-cache": "^3.4.0",
|
||||
"vuex-persist": "^3.1.3",
|
||||
"vuex-persistedstate": "^4.1.0",
|
||||
"vuex-persist": "2.3",
|
||||
"xml-beautifier": "^0.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -160,7 +155,7 @@
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jest-transform-yaml": "1.0.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"karma": "^6.3.13",
|
||||
"karma": "^6.3.16",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-polyfill": "^1.1.0",
|
||||
"karma-qunit": "^4.1.2",
|
||||
|
||||
@@ -45,12 +45,11 @@ describe("PairedListCollectionCreator", () => {
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
//change filter to .1.fastq/.2.fastq
|
||||
wrapper.find("div.forward-unpaired-filter > div.input-group-append > button").trigger("click");
|
||||
wrapper
|
||||
await wrapper.find("div.forward-unpaired-filter > div.input-group-append > button").trigger("click");
|
||||
await wrapper
|
||||
.findAll("div.dropdown-menu > a.dropdown-item")
|
||||
.wrappers.find((e) => e.text() == ".1.fastq")
|
||||
.trigger("click");
|
||||
await wrapper.vm.$nextTick();
|
||||
//assert forward filter
|
||||
const forwardFilter = wrapper.find("div.forward-unpaired-filter > input").element.value;
|
||||
expect(forwardFilter).toBe(".1.fastq");
|
||||
@@ -58,8 +57,7 @@ describe("PairedListCollectionCreator", () => {
|
||||
const reverseFilter = wrapper.find("div.reverse-unpaired-filter > input").element.value;
|
||||
expect(reverseFilter).toBe(".2.fastq");
|
||||
// click Autopair
|
||||
wrapper.find("a.autopair-link").trigger("click");
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.find("a.autopair-link").trigger("click");
|
||||
//assert pair-name longer name
|
||||
const pairname = wrapper.find("span.pair-name");
|
||||
expect(pairname.text()).toBe("DP134_1_FS_PSII_FSB_42C_A10");
|
||||
@@ -83,12 +81,11 @@ describe("PairedListCollectionCreator", () => {
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
//change filter to .1.fastq/.2.fastq
|
||||
wrapper.find("div.forward-unpaired-filter > div.input-group-append > button").trigger("click");
|
||||
wrapper
|
||||
await wrapper.find("div.forward-unpaired-filter > div.input-group-append > button").trigger("click");
|
||||
await wrapper
|
||||
.findAll("div.dropdown-menu > a.dropdown-item")
|
||||
.wrappers.find((e) => e.text() == ".1.fastq")
|
||||
.trigger("click");
|
||||
await wrapper.vm.$nextTick();
|
||||
//assert forward filter
|
||||
const forwardFilter = wrapper.find("div.forward-unpaired-filter > input").element.value;
|
||||
expect(forwardFilter).toBe(".1.fastq");
|
||||
@@ -96,8 +93,7 @@ describe("PairedListCollectionCreator", () => {
|
||||
const reverseFilter = wrapper.find("div.reverse-unpaired-filter > input").element.value;
|
||||
expect(reverseFilter).toBe(".2.fastq");
|
||||
// click Autopair
|
||||
wrapper.find("a.autopair-link").trigger("click");
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.find("a.autopair-link").trigger("click");
|
||||
//assert pair-name longer name
|
||||
const pairname = wrapper.find("span.pair-name");
|
||||
expect(pairname.text()).toBe("UII_moo_1");
|
||||
@@ -121,10 +117,8 @@ describe("PairedListCollectionCreator", () => {
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
//change filter to _R1/_R2
|
||||
wrapper.find("div.forward-unpaired-filter > input").setValue("_R1");
|
||||
await wrapper.vm.$nextTick();
|
||||
wrapper.find("div.reverse-unpaired-filter > input").setValue("_R2");
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.find("div.forward-unpaired-filter > input").setValue("_R1");
|
||||
await wrapper.find("div.reverse-unpaired-filter > input").setValue("_R2");
|
||||
//assert forward filter
|
||||
const forwardFilter = wrapper.find("div.forward-unpaired-filter > input").element.value;
|
||||
expect(forwardFilter).toBe("_R1");
|
||||
@@ -132,8 +126,7 @@ describe("PairedListCollectionCreator", () => {
|
||||
const reverseFilter = wrapper.find("div.reverse-unpaired-filter > input").element.value;
|
||||
expect(reverseFilter).toBe("_R2");
|
||||
// click Autopair
|
||||
wrapper.find("a.autopair-link").trigger("click");
|
||||
await wrapper.vm.$nextTick();
|
||||
await wrapper.find("a.autopair-link").trigger("click");
|
||||
//assert all pairs matched
|
||||
expect(wrapper.findAll("li.dataset unpaired").length == 0).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
<b-button v-if="wait" variant="info" disabled>
|
||||
<font-awesome-icon icon="spinner" class="mr-2" spin />Please wait...
|
||||
</b-button>
|
||||
<b-button
|
||||
v-else
|
||||
variant="primary"
|
||||
v-b-tooltip.hover.bottom
|
||||
:title="tooltip"
|
||||
:disabled="disabled"
|
||||
@click="$emit('onClick')">
|
||||
<b-button v-else variant="primary" v-b-tooltip.hover.bottom :title="tooltip" @click="$emit('onClick')">
|
||||
<font-awesome-icon icon="check" class="mr-2" />{{ title }}
|
||||
</b-button>
|
||||
</template>
|
||||
@@ -37,10 +31,6 @@ export default {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Vue from "vue";
|
||||
import axios from "axios";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
@@ -9,8 +8,8 @@ import flushPromises from "flush-promises";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
function buildWrapper(conversion_disable = false) {
|
||||
return mount(DatasetAttributes, {
|
||||
async function buildWrapper(conversion_disable = false) {
|
||||
const wrapper = mount(DatasetAttributes, {
|
||||
propsData: {
|
||||
datasetId: "dataset_id",
|
||||
},
|
||||
@@ -29,14 +28,15 @@ function buildWrapper(conversion_disable = false) {
|
||||
FormElement: false,
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe("DatasetAttributes", () => {
|
||||
it("check rendering", async () => {
|
||||
const axiosMock = new MockAdapter(axios);
|
||||
axiosMock.onPut(`/dataset/set_edit`).reply(200, { message: "success", status: "success" });
|
||||
const wrapper = buildWrapper();
|
||||
await Vue.nextTick();
|
||||
const wrapper = await buildWrapper();
|
||||
expect(wrapper.findAll("button").length).toBe(6);
|
||||
expect(wrapper.findAll("#attribute_text").length).toBe(1);
|
||||
expect(wrapper.findAll("#conversion_text").length).toBe(1);
|
||||
@@ -50,8 +50,7 @@ describe("DatasetAttributes", () => {
|
||||
});
|
||||
|
||||
it("check rendering without conversion option", async () => {
|
||||
const wrapper = buildWrapper(true);
|
||||
await Vue.nextTick();
|
||||
const wrapper = await buildWrapper(true);
|
||||
expect(wrapper.findAll("button").length).toBe(5);
|
||||
expect(wrapper.findAll("#attribute_text").length).toBe(1);
|
||||
expect(wrapper.findAll("#conversion_text").length).toBe(0);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<b-alert v-if="messageText" :variant="messageVariant" show>
|
||||
{{ messageText | l }}
|
||||
</b-alert>
|
||||
<DatasetAttributesProvider :id="datasetId" :use-cache="false" v-slot="{ result, loading }" @error="onError">
|
||||
<DatasetAttributesProvider :id="datasetId" v-slot="{ result, loading }" @error="onError">
|
||||
<div v-if="!loading" class="mt-3">
|
||||
<b-tabs>
|
||||
<b-tab v-if="!result['attribute_disable']">
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
<ConfigProvider v-slot="{ config }">
|
||||
<DatasetProvider
|
||||
:id="datasetId"
|
||||
v-slot="{ item: dataset, loading: isDatasetLoading, error: datasetLoadingError }">
|
||||
v-slot="{ result: dataset, loading: isDatasetLoading, error: datasetLoadingError }">
|
||||
<div>
|
||||
<LoadingSpan v-if="isDatasetLoading" />
|
||||
<Alert v-else-if="datasetLoadingError" :message="datasetLoadingError" variant="error" />
|
||||
@@ -10,8 +10,8 @@
|
||||
<JobDetailsProvider
|
||||
v-if="!isDatasetLoading && dataset.creating_job !== null"
|
||||
:jobid="dataset.creating_job"
|
||||
v-slot="{ result: job, loading: isJobLoading }"
|
||||
:use-cache="false">
|
||||
auto-refresh
|
||||
v-slot="{ result: job, loading: isJobLoading }">
|
||||
<div v-if="!isJobLoading">
|
||||
<dataset-information class="detail" :hda_id="datasetId" />
|
||||
<job-parameters class="detail" dataset_type="hda" :dataset-id="datasetId" />
|
||||
@@ -29,7 +29,6 @@ function buildWrapper(has_duplicate_inputs = true, has_empty_inputs = true, user
|
||||
result: { has_duplicate_inputs: has_duplicate_inputs, has_empty_inputs: has_empty_inputs },
|
||||
}),
|
||||
DatasetProvider: MockProvider({
|
||||
resultLabel: "item",
|
||||
result: { id: "dataset_id", creating_job: "creating_job" },
|
||||
}),
|
||||
FontAwesomeIcon: false,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<h4 class="alert-heading">Failed to access Dataset details.</h4>
|
||||
{{ errorMessage }}
|
||||
</b-alert>
|
||||
<DatasetProvider :id="datasetId" v-slot="{ item: dataset, loading: datasetLoading }">
|
||||
<DatasetProvider :id="datasetId" v-slot="{ result: dataset, loading: datasetLoading }">
|
||||
<JobDetailsProvider
|
||||
v-if="!datasetLoading"
|
||||
:jobid="dataset.creating_job"
|
||||
|
||||
@@ -13,7 +13,7 @@ const mockDatasetProvider = {
|
||||
render() {
|
||||
return this.$scopedSlots.default({
|
||||
loading: false,
|
||||
item: datasetResponse,
|
||||
result: datasetResponse,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<DatasetProvider :id="hda_id" v-slot="{ item: dataset, loading }">
|
||||
<DatasetProvider :id="hda_id" v-slot="{ result: dataset, loading }">
|
||||
<div v-if="!loading">
|
||||
<h3>Dataset Information</h3>
|
||||
<table id="dataset-details" class="tabletip info_data_table">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import FormBoolean from "./FormBoolean";
|
||||
import Vue from "vue";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
@@ -20,12 +19,11 @@ describe("FormBoolean", () => {
|
||||
it("check initial value and value change", async () => {
|
||||
const input = wrapper.find("input");
|
||||
expect(wrapper.vm.currentValue).toBe(false);
|
||||
wrapper.setProps({ value: true });
|
||||
await Vue.nextTick();
|
||||
await wrapper.setProps({ value: true });
|
||||
expect(wrapper.vm.currentValue).toBe(true);
|
||||
input.trigger("click");
|
||||
await input.trigger("click");
|
||||
expect(input.element.checked).toBe(false);
|
||||
input.trigger("click");
|
||||
await input.trigger("click");
|
||||
expect(input.element.checked).toBe(true);
|
||||
expect(wrapper.emitted().input[0][0]).toBe(true);
|
||||
});
|
||||
|
||||
@@ -52,9 +52,8 @@ describe("FormInput", () => {
|
||||
const props = { value: 50, type: "float", min: 10, max: 100 };
|
||||
const wrapper = await mountFormNumber(props);
|
||||
const input = await getInput(wrapper);
|
||||
input.setValue(number);
|
||||
input.trigger("change");
|
||||
await flushPromises();
|
||||
await input.setValue(number);
|
||||
await input.trigger("change");
|
||||
const alert = await getAlert(wrapper);
|
||||
expect(alert.exists()).toBeTruthy();
|
||||
expect(alert.text().includes(`${number} is out`)).toBeTruthy();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "jest/helpers";
|
||||
import Vue from "vue";
|
||||
import FormElement from "./FormElement";
|
||||
|
||||
const localVue = getLocalVue();
|
||||
@@ -46,19 +45,17 @@ describe("FormElement", () => {
|
||||
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");
|
||||
await wrapper.find(".ui-form-collapsible-icon").trigger("click");
|
||||
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",
|
||||
collapsedDisableText: "Disable Collapsible",
|
||||
});
|
||||
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");
|
||||
await wrapper.find(".ui-form-collapsible-icon").trigger("click");
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<h6 v-if="collectionType" class="description py-1">
|
||||
<span>a {{ collectionLabel | localize }}</span>
|
||||
<span v-if="elementCount == 1">with 1 item</span>
|
||||
<span v-else-if="elementCount > 1">with {{ elementCount }} items</span>
|
||||
</h6>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labels: {
|
||||
list: "list",
|
||||
paired: "dataset pair",
|
||||
"list:paired": "list of pairs",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
collectionLabel() {
|
||||
return this.labels[this.collectionType] || "nested list";
|
||||
},
|
||||
collectionType() {
|
||||
return this.item.collection_type;
|
||||
},
|
||||
elementCount() {
|
||||
return this.item.element_count;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div
|
||||
:id="contentId"
|
||||
:class="['content-item m-1 p-0 rounded', clsStatus]"
|
||||
draggable
|
||||
:data-hid="id"
|
||||
:data-state="state"
|
||||
@dragstart="onDragStart">
|
||||
<div class="p-1 cursor-pointer" @click.stop="onClick">
|
||||
<div class="clearfix overflow-hidden">
|
||||
<div class="btn-group float-right">
|
||||
<b-button
|
||||
v-if="isDataset"
|
||||
:disabled="displayDisabled"
|
||||
:title="displayButtonTitle"
|
||||
class="px-1"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onDisplay">
|
||||
<icon icon="eye" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem"
|
||||
:disabled="editDisabled"
|
||||
class="px-1"
|
||||
title="Edit attributes"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onEdit">
|
||||
<icon icon="pen" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && !item.deleted"
|
||||
class="px-1"
|
||||
title="Delete"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:disabled="item.purged"
|
||||
@click.stop="$emit('delete', item)">
|
||||
<icon icon="trash" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && item.deleted"
|
||||
class="px-1"
|
||||
title="Undelete"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:disabled="item.purged"
|
||||
@click.stop="$emit('undelete', item)">
|
||||
<icon icon="trash-restore" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="isHistoryItem && !item.visible"
|
||||
class="px-1"
|
||||
title="Unhide"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="$emit('unhide', item)">
|
||||
<icon icon="unlock" />
|
||||
</b-button>
|
||||
</div>
|
||||
<h5 class="float-left p-1 w-75 font-weight-bold">
|
||||
<div v-if="selectable" class="selector float-left mr-2">
|
||||
<span
|
||||
v-if="selected"
|
||||
class="fa fa-lg fa-check-square-o"
|
||||
@click.stop="$emit('update:selected', false)" />
|
||||
<span v-else class="fa fa-lg fa-square-o" @click.stop="$emit('update:selected', true)" />
|
||||
</div>
|
||||
<icon v-if="stateIcon" :icon="stateIcon" />
|
||||
<span class="id hid">{{ id }}</span>
|
||||
<span>:</span>
|
||||
<span class="content-title name">{{ name }}</span>
|
||||
<CollectionDescription v-if="!isDataset" :item="item" />
|
||||
<div v-if="!expandDataset && item.tags && item.tags.length > 0" class="nametags">
|
||||
<Nametag v-for="tag in item.tags" :key="tag" :tag="tag" />
|
||||
</div>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
<!-- collections are not expandable, so we only need the DatasetDetails component here -->
|
||||
<div class="detail-animation-wrapper" :class="expandDataset ? '' : 'collapsed'">
|
||||
<DatasetDetails v-if="expandDataset" @edit="onEdit" :item="item" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { backboneRoute, useGalaxy, iframeRedirect } from "components/plugins/legacyNavigation";
|
||||
import { Nametag } from "components/Nametags";
|
||||
import CollectionDescription from "./Collection/CollectionDescription";
|
||||
import DatasetDetails from "./Dataset/DatasetDetails";
|
||||
import CONTENTSTATE from "./contentState";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CollectionDescription,
|
||||
DatasetDetails,
|
||||
Nametag,
|
||||
},
|
||||
props: {
|
||||
expandDataset: { type: Boolean, required: true },
|
||||
item: { type: Object, required: true },
|
||||
id: { type: Number, required: true },
|
||||
isDataset: { type: Boolean, default: true },
|
||||
isHistoryItem: { type: Boolean, default: true },
|
||||
name: { type: String, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
selectable: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
contentId() {
|
||||
return `dataset-${this.item.id}`;
|
||||
},
|
||||
state() {
|
||||
if (this.item.job_state_summary) {
|
||||
for (const key of ["error", "failed", "paused", "upload", "running"]) {
|
||||
if (this.item.job_state_summary[key] > 0) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return "ok";
|
||||
} else {
|
||||
return this.item.state;
|
||||
}
|
||||
},
|
||||
clsStatus() {
|
||||
const status = CONTENTSTATE[this.state] && CONTENTSTATE[this.state].status;
|
||||
if (this.selected) {
|
||||
return "alert-info";
|
||||
} else if (!status) {
|
||||
return `alert-success`;
|
||||
} else {
|
||||
return `alert-${status}`;
|
||||
}
|
||||
},
|
||||
displayButtonTitle() {
|
||||
if (this.item.purged) {
|
||||
return "Cannot display datasets removed from disk.";
|
||||
}
|
||||
if (this.displayDisabled) {
|
||||
return "This dataset is not yet viewable.";
|
||||
}
|
||||
return "Display";
|
||||
},
|
||||
displayDisabled() {
|
||||
return this.item.purged || ["discarded", "new", "upload"].includes(this.state);
|
||||
},
|
||||
editButtonTitle() {
|
||||
if (this.item.purged) {
|
||||
return "Cannot edit attributes of datasets removed from disk.";
|
||||
}
|
||||
if (this.editDisabled) {
|
||||
return "This dataset is not yet editable.";
|
||||
}
|
||||
return "Edit Attributes";
|
||||
},
|
||||
editDisabled() {
|
||||
return (
|
||||
this.item.purged || ["discarded", "new", "upload", "queued", "running", "waiting"].includes(this.state)
|
||||
);
|
||||
},
|
||||
stateIcon() {
|
||||
return CONTENTSTATE[this.state] && CONTENTSTATE[this.state].icon;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDragStart(evt) {
|
||||
evt.dataTransfer.dropEffect = "move";
|
||||
evt.dataTransfer.effectAllowed = "move";
|
||||
evt.dataTransfer.setData("text", JSON.stringify([this.item]));
|
||||
},
|
||||
onDisplay() {
|
||||
const id = this.item.id;
|
||||
useGalaxy((Galaxy) => {
|
||||
if (Galaxy.frame && Galaxy.frame.active) {
|
||||
Galaxy.frame.addDataset(id);
|
||||
} else {
|
||||
iframeRedirect(`datasets/${id}/display/?preview=True`);
|
||||
}
|
||||
});
|
||||
},
|
||||
onEdit() {
|
||||
if (this.item.collection_type) {
|
||||
backboneRoute(`collection/edit/${this.item.id}`);
|
||||
} else {
|
||||
backboneRoute("datasets/edit", { dataset_id: this.item.id });
|
||||
}
|
||||
},
|
||||
onClick() {
|
||||
if (this.isDataset) {
|
||||
this.$emit("update:expand-dataset", !this.expandDataset);
|
||||
} else {
|
||||
this.$emit("view-collection", this.item);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.content-item:hover {
|
||||
filter: brightness(105%);
|
||||
}
|
||||
.content-item {
|
||||
.name {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
.detail-animation-wrapper {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.5s ease-out;
|
||||
height: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
.detail-animation-wrapper.collapsed {
|
||||
max-height: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="dataset-actions mb-1">
|
||||
<div class="clearfix">
|
||||
<div class="btn-group float-left">
|
||||
<b-button v-if="showError" class="px-1" title="Error" size="sm" variant="link" @click.stop="onError">
|
||||
<span class="fa fa-bug" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="showDownloads"
|
||||
class="download-btn px-1"
|
||||
title="Download"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onDownload">
|
||||
<span class="fa fa-save" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="showDownloads"
|
||||
class="px-1"
|
||||
title="Copy Link"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onCopyLink">
|
||||
<span class="fa fa-link" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="showInfo"
|
||||
class="params-btn px-1"
|
||||
title="Dataset Details"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onInfo">
|
||||
<span class="fa fa-info-circle" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="showRerun"
|
||||
class="px-1"
|
||||
title="Run Job Again"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onRerun">
|
||||
<span class="fa fa-redo" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="showVisualizations"
|
||||
class="px-1"
|
||||
title="Visualize"
|
||||
size="sm"
|
||||
variant="link"
|
||||
@click.stop="onVisualize">
|
||||
<span class="fa fa-bar-chart-o" />
|
||||
</b-button>
|
||||
<b-button v-if="showRerun" class="px-1" title="Help" size="sm" variant="link" @click.stop="onRerun">
|
||||
<span class="fa fa-question" />
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="btn-group float-right">
|
||||
<b-button
|
||||
class="px-1"
|
||||
title="Edit Tags"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:pressed="showTags"
|
||||
@click="onToggleTags">
|
||||
<span class="fa fa-tags" />
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showTags" class="mb-2">
|
||||
<StatelessTags class="tags" v-model="tags" @input="onTags" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { StatelessTags } from "components/Tags";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import { prependPath } from "utils/redirect";
|
||||
import { copy as sendToClipboard } from "utils/clipboard";
|
||||
import { absPath } from "utils/redirect";
|
||||
import { updateContentFields } from "components/History/model/queries";
|
||||
|
||||
export default {
|
||||
mixins: [legacyNavigationMixin],
|
||||
components: {
|
||||
StatelessTags,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showTags: false,
|
||||
tags: this.item.tags,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
downloadUrl() {
|
||||
return prependPath(`datasets/${this.item.id}/display?to_ext=${this.item.extension}`);
|
||||
},
|
||||
showDownloads() {
|
||||
return !this.item.purged && ["ok", "failed_metadata", "error"].includes(this.item.state);
|
||||
},
|
||||
showError() {
|
||||
return this.item.state == "error";
|
||||
},
|
||||
showInfo() {
|
||||
return this.item.state != "noPermission";
|
||||
},
|
||||
showRerun() {
|
||||
return (
|
||||
this.item.rerunnable &&
|
||||
this.item.creating_job &&
|
||||
this.item.state != "upload" &&
|
||||
this.item.state != "noPermission"
|
||||
);
|
||||
},
|
||||
showVisualizations() {
|
||||
// TODO: Check hasViz, if visualizations are activated in the config
|
||||
return !this.item.purged && ["ok", "failed_metadata", "error"].includes(this.item.state);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCopyLink() {
|
||||
const msg = this.localize("Link is copied to your clipboard");
|
||||
sendToClipboard(absPath(this.downloadUrl), msg);
|
||||
},
|
||||
onDownload() {
|
||||
window.location.href = this.downloadUrl;
|
||||
},
|
||||
onError() {
|
||||
this.backboneRoute("datasets/error", { dataset_id: this.item.id });
|
||||
},
|
||||
onInfo() {
|
||||
this.backboneRoute(`datasets/${this.item.id}/details`);
|
||||
},
|
||||
onRerun() {
|
||||
this.backboneRoute(`root?job_id=${this.item.creating_job}`);
|
||||
},
|
||||
onTags() {
|
||||
updateContentFields(this.item, { tags: this.tags });
|
||||
},
|
||||
onToggleTags() {
|
||||
this.showTags = !this.showTags;
|
||||
},
|
||||
onVisualize() {
|
||||
const path = `visualizations?dataset_id=${this.item.id}`;
|
||||
const redirectParams = {
|
||||
path: path,
|
||||
title: "Dataset details",
|
||||
tryIframe: false,
|
||||
};
|
||||
if (!this.iframeAdd(redirectParams)) {
|
||||
this.backboneRoute(path);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<UrlDataProvider :url="dataUrl" auto-refresh v-slot="{ loading, result }">
|
||||
<div v-if="!loading" class="dataset">
|
||||
<div class="p-2 details">
|
||||
<div class="summary">
|
||||
<div v-if="result.misc_blurb" class="blurb">
|
||||
<span class="value">{{ result.misc_blurb }}</span>
|
||||
</div>
|
||||
<span v-if="result.file_ext" class="datatype">
|
||||
<label class="prompt" v-localize>format</label>
|
||||
<span class="value">{{ result.file_ext }}</span>
|
||||
</span>
|
||||
<span v-if="result.genome_build" class="dbkey">
|
||||
<label class="prompt" v-localize>database</label>
|
||||
<b-link class="value" @click.stop="$emit('edit', dataset)" data-label="Database/Build">{{
|
||||
result.genome_build
|
||||
}}</b-link>
|
||||
</span>
|
||||
<div v-if="result.misc_info" class="info">
|
||||
<span class="value">{{ result.misc_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<DatasetActions :item="result" />
|
||||
<pre v-if="result.peek" class="dataset-peek p-1" v-html="result.peek" />
|
||||
</div>
|
||||
</div>
|
||||
</UrlDataProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UrlDataProvider } from "components/providers/UrlDataProvider";
|
||||
import DatasetActions from "./DatasetActions";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetActions,
|
||||
UrlDataProvider,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
dataset() {
|
||||
return this.item.object || this.item;
|
||||
},
|
||||
dataUrl() {
|
||||
const datasetId = this.dataset.id;
|
||||
return `api/datasets/${datasetId}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<ContentItem
|
||||
:item="item.object"
|
||||
:id="item.element_index"
|
||||
:name="item.element_identifier"
|
||||
:is-dataset="item.element_type == 'hda'"
|
||||
:expand-dataset="expandDataset"
|
||||
:is-history-item="false"
|
||||
@update:expand-dataset="expandDataset = $event"
|
||||
@view-collection="viewCollection = !viewCollection" />
|
||||
<GenericItem v-if="viewCollection" :itemId="item.object.id" :itemSrc="item.object.history_content_type" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentItem from "./ContentItem";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentItem,
|
||||
GenericItem: () => import("components/History/Content/GenericItem"),
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewCollection: false,
|
||||
expandDataset: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<component :is="providerComponent" :id="itemId" auto-refresh v-slot="{ result: item, loading }">
|
||||
<loading-span v-if="loading" message="Loading dataset" />
|
||||
<div v-else>
|
||||
<ContentItem
|
||||
:item="item"
|
||||
:id="item.hid"
|
||||
:name="item.name"
|
||||
:expand-dataset="expandDataset"
|
||||
:is-dataset="item.history_content_type == 'dataset'"
|
||||
@update:expand-dataset="expandDataset = $event"
|
||||
@view-collection="viewCollection = !viewCollection"
|
||||
@delete="onDelete"
|
||||
@undelete="onUndelete"
|
||||
@unhide="onUnhide" />
|
||||
<div v-if="viewCollection">
|
||||
<div v-for="(collectionItem, collectionIndex) in item.elements" :key="collectionIndex">
|
||||
<GenericElement :item="collectionItem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import { DatasetProvider, DatasetCollectionProvider } from "components/providers";
|
||||
import { deleteContent, updateContentFields } from "components/History/model/queries";
|
||||
import ContentItem from "./ContentItem";
|
||||
import GenericElement from "./GenericElement";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentItem,
|
||||
GenericElement,
|
||||
DatasetProvider,
|
||||
DatasetCollectionProvider,
|
||||
LoadingSpan,
|
||||
},
|
||||
props: {
|
||||
itemId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
itemSrc: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewCollection: false,
|
||||
expandDataset: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
providerComponent() {
|
||||
return this.itemSrc == "hda" ? "DatasetProvider" : "DatasetCollectionProvider";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDelete(item) {
|
||||
deleteContent(item);
|
||||
},
|
||||
onUndelete(item) {
|
||||
updateContentFields(item, { deleted: false });
|
||||
},
|
||||
onHide(item) {
|
||||
updateContentFields(item, { visible: false });
|
||||
},
|
||||
onUnhide(item) {
|
||||
updateContentFields(item, { visible: true });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+3
-12
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* This is simple list management for the monent, but if I remember right we have plans for a more
|
||||
* This is simple list management for the moment, but if I remember right we have plans for a more
|
||||
* abstracted selection scheme in the future for purposes of handling large bulk operations on the
|
||||
* server, this is a good place to implement that logic.
|
||||
*/
|
||||
@@ -28,10 +28,10 @@ export default {
|
||||
const key = this.getItemKey(item);
|
||||
return this.items.has(key);
|
||||
},
|
||||
setSelected(item, bSelected) {
|
||||
setSelected(item, selected) {
|
||||
const key = this.getItemKey(item);
|
||||
const newSelected = new Map(this.items);
|
||||
bSelected ? newSelected.set(key, item) : newSelected.delete(key);
|
||||
selected ? newSelected.set(key, item) : newSelected.delete(key);
|
||||
this.items = newSelected;
|
||||
},
|
||||
selectItems(items = []) {
|
||||
@@ -42,14 +42,6 @@ export default {
|
||||
});
|
||||
this.items = new Map(newEntries);
|
||||
},
|
||||
// unselectItems(items = []) {
|
||||
// const doomedKeys = items.map((item) => this.getItemKey(item));
|
||||
// const existing = this.items.entries();
|
||||
// const newEntries = Array.from(existing).filter(([key]) => {
|
||||
// return !doomedKeys.includes(key);
|
||||
// });
|
||||
// this.items = new Map(newEntries);
|
||||
// },
|
||||
reset() {
|
||||
this.items = new Map();
|
||||
},
|
||||
@@ -77,7 +69,6 @@ export default {
|
||||
showSelection: this.showSelection,
|
||||
setShowSelection: this.setShowSelection,
|
||||
selectItems: this.selectItems,
|
||||
// unselectItems: this.unselectItems,
|
||||
isSelected: this.isSelected,
|
||||
setSelected: this.setSelected,
|
||||
resetSelection: this.reset,
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Client representation of state and state messages. See: https://github.com/galaxyproject/galaxy/blob/dev/lib/galaxy/model/__init__.py#L3292
|
||||
for a list of available states.
|
||||
*/
|
||||
export default {
|
||||
/** deleted while uploading */
|
||||
discarded: {
|
||||
status: "danger",
|
||||
text: "The job creating this dataset was cancelled before completion.",
|
||||
icon: "exclamation-triangle",
|
||||
},
|
||||
/** has no data */
|
||||
empty: {
|
||||
status: "success",
|
||||
text: "No data",
|
||||
},
|
||||
/** the tool producing this dataset failed */
|
||||
error: {
|
||||
status: "danger",
|
||||
text: "An error occurred with this dataset",
|
||||
icon: "exclamation-triangle",
|
||||
},
|
||||
/** metadata discovery/setting failed or errored (but otherwise ok) */
|
||||
failed_metadata: {
|
||||
status: "danger",
|
||||
},
|
||||
/** was created without a tool */
|
||||
new: {
|
||||
status: "warning",
|
||||
text: "This is a new dataset and not all of its data are available yet.",
|
||||
icon: "clock",
|
||||
},
|
||||
/** has successfully completed running */
|
||||
ok: {
|
||||
status: "success",
|
||||
},
|
||||
/** the job that will produce the dataset paused */
|
||||
paused: {
|
||||
status: "info",
|
||||
text: "This job is paused. Use the 'Resume Paused Jobs' in the history menu to resume.",
|
||||
icon: "pause",
|
||||
},
|
||||
/** the job that will produce the dataset queued in the runner */
|
||||
queued: {
|
||||
status: "warning",
|
||||
text: "This job is waiting to run.",
|
||||
icon: "clock",
|
||||
},
|
||||
/** the job that will produce the dataset is running */
|
||||
running: {
|
||||
status: "warning",
|
||||
text: "This job is currently running.",
|
||||
icon: "clock",
|
||||
},
|
||||
/** metadata for the dataset is being discovered/set */
|
||||
setting_metadata: {
|
||||
status: "warning",
|
||||
text: "Metadata is being auto-detected.",
|
||||
icon: "clock",
|
||||
},
|
||||
/** is uploading and not ready */
|
||||
upload: {
|
||||
status: "warning",
|
||||
text: "This dataset is currently uploading.",
|
||||
icon: "clock",
|
||||
},
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import ContentItem from "./ContentItem";
|
||||
|
||||
export default {
|
||||
mixins: [ContentItem],
|
||||
|
||||
computed: {
|
||||
contentItemComponent() {
|
||||
const { history_content_type } = this.item;
|
||||
switch (history_content_type) {
|
||||
case "dataset":
|
||||
return "Subdataset";
|
||||
case "dataset_collection":
|
||||
return "Subcollection";
|
||||
default:
|
||||
return "Placeholder";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* This is a single item in the list. The main job of the contentItem is to
|
||||
* pick a specific selector for the :is attribute of the generic component
|
||||
* placeholder. This should be a dataset, collection, or other component
|
||||
* depending on the specific list implementation.
|
||||
*
|
||||
* The mixin also pulls out "selected", and "expanded" properties which are
|
||||
* derived from the current component state rather than the data itself.
|
||||
*/
|
||||
|
||||
import Placeholder from "./Placeholder";
|
||||
import Dataset from "./Dataset";
|
||||
import DatasetCollection from "./DatasetCollection";
|
||||
import Subdataset from "./Subdataset";
|
||||
import Subcollection from "./Subcollection";
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<component :is="contentItemComponent"
|
||||
:data-ci-type="contentItemComponent"
|
||||
class="content-item"
|
||||
:class="{ loading }"
|
||||
:tabindex="index"
|
||||
:writable="writable"
|
||||
v-on="$listeners"
|
||||
v-bind="$attrs"
|
||||
:item="item"
|
||||
:index="index"
|
||||
:row-key="rowKey"
|
||||
:writable="writable"
|
||||
@mouseover.native.stop="setFocus(index)"
|
||||
@keydown.native.arrow-up.self.stop="setFocus(index - 1)"
|
||||
@keydown.native.arrow-down.self.stop="setFocus(index + 1)"
|
||||
/>
|
||||
`,
|
||||
|
||||
components: {
|
||||
Placeholder,
|
||||
Dataset,
|
||||
DatasetCollection,
|
||||
Subdataset,
|
||||
Subcollection,
|
||||
},
|
||||
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
index: { type: Number, required: false, default: null },
|
||||
rowKey: { type: [Number, String], required: false, default: "" },
|
||||
writable: { type: Boolean, required: false, default: true },
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
suppressFocus: false,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
setFocus(index) {
|
||||
if (this.suppressFocus) {
|
||||
return;
|
||||
}
|
||||
const ul = this.$el.closest(".scroller");
|
||||
const el = ul.querySelector(`[tabindex="${index}"]`);
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
loading() {
|
||||
return !this.item;
|
||||
},
|
||||
contentItemComponent() {
|
||||
// override me
|
||||
return "Placeholder";
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.$on("bv::dropdown::show", () => (this.suppressFocus = true));
|
||||
this.$root.$on("bv::dropdown::hide", () => (this.suppressFocus = false));
|
||||
},
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<DatasetUI
|
||||
v-if="dataset"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:dataset="dataset"
|
||||
@update:dataset="onUpdate"
|
||||
@delete="onDelete"
|
||||
@undelete="onUndelete"
|
||||
@unhide="onUnhide"
|
||||
@copy-link="onCopyLink" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatasetUI from "./DatasetUI";
|
||||
import { Dataset } from "../../model";
|
||||
import { deleteContent, updateContentFields } from "../../model/queries";
|
||||
import { cacheContent } from "components/providers/History/caching";
|
||||
import { copy as sendToClipboard } from "utils/clipboard";
|
||||
import { absPath } from "utils/redirect";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
dataset() {
|
||||
return new Dataset(this.item);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onDelete(opts = {}) {
|
||||
const ajaxResult = await deleteContent(this.item, opts);
|
||||
await cacheContent(ajaxResult);
|
||||
},
|
||||
async onUnhide() {
|
||||
await this.onUpdate({ visible: true });
|
||||
},
|
||||
async onUndelete() {
|
||||
await this.onUpdate({ deleted: false });
|
||||
},
|
||||
async onUpdate(changes) {
|
||||
const newContent = await updateContentFields(this.item, changes);
|
||||
await cacheContent(newContent);
|
||||
},
|
||||
onCopyLink() {
|
||||
const relPath = this.dataset.getUrl("download");
|
||||
const msg = this.localize("Link is copied to your clipboard");
|
||||
sendToClipboard(absPath(relPath), msg);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,294 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-button-group>
|
||||
<IconButton
|
||||
v-if="notIn(STATES.NOT_VIEWABLE, STATES.DISCARDED)"
|
||||
icon="eye"
|
||||
:title="displayButtonTitle"
|
||||
:disabled="dataset.purged || isIn(STATES.UPLOAD, STATES.NEW)"
|
||||
@click.stop="viewData"
|
||||
variant="link"
|
||||
class="px-1 display-btn" />
|
||||
<IconButton
|
||||
v-if="writable && notIn(STATES.DISCARDED)"
|
||||
icon="pen"
|
||||
:title="editButtonTitle"
|
||||
:disabled="dataset.deleted || isIn(STATES.UPLOAD, STATES.NEW)"
|
||||
@click.stop="$emit('edit')"
|
||||
variant="link"
|
||||
class="px-1 edit-btn" />
|
||||
<IconButton
|
||||
v-if="writable && dataset.accessible"
|
||||
:icon="dataset.deleted ? 'trash-restore' : 'trash'"
|
||||
:title="deleteButtonTitle"
|
||||
:disabled="dataset.purged"
|
||||
v-b-modal="bsId('delete-modal')"
|
||||
variant="link"
|
||||
class="px-1 delete-btn"
|
||||
@click.stop />
|
||||
|
||||
<b-dropdown
|
||||
right
|
||||
size="sm"
|
||||
variant="link"
|
||||
:text="'Dataset Operations' | l"
|
||||
toggle-class="p-1 pl-2"
|
||||
class="flex-grow-0 dataset-operations-dropdown"
|
||||
v-if="expanded"
|
||||
boundary="window">
|
||||
<template v-slot:button-content>
|
||||
<Icon icon="ellipsis-v" variant="link" />
|
||||
<span class="sr-only">Dataset Operations</span>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="isIn(STATES.ERROR)"
|
||||
key="show-error"
|
||||
title="Error"
|
||||
@click.stop="
|
||||
backboneRoute('datasets/error', {
|
||||
dataset_id: dataset.id,
|
||||
})
|
||||
">
|
||||
<Icon icon="exclamation-triangle" class="mr-1" />
|
||||
<span v-localize>Show Error</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="showDownloads && dataset.getUrl('download')"
|
||||
title="Copy Link"
|
||||
@click.stop="$emit('copy-link')">
|
||||
<Icon icon="link" class="mr-1" />
|
||||
<span v-localize>Copy Link</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-group v-if="showDownloads && dataset.hasMetaData">
|
||||
<b-dropdown-item
|
||||
v-for="mf in dataset.meta_files"
|
||||
:key="'download-' + mf.download_url"
|
||||
:title="'Download ' + mf.file_type"
|
||||
:href="prependPath(dataset.getUrl('meta_download') + mf.file_type)"
|
||||
target="_blank"
|
||||
download>
|
||||
<Icon icon="download" class="mr-1" />
|
||||
<span v-localize>{{ "Download " + mf.file_type }}</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown-group>
|
||||
|
||||
<b-dropdown-item
|
||||
v-else-if="showDownloads"
|
||||
title="Download"
|
||||
:href="prependPath(dataset.getUrl('download'))"
|
||||
target="_blank"
|
||||
class="download-btn"
|
||||
download>
|
||||
<Icon icon="download" class="mr-1" />
|
||||
<span v-localize>Download</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="dataset.rerunnable && dataset.creating_job && notIn(STATES.UPLOAD, STATES.NOT_VIEWABLE)"
|
||||
title="Run job again"
|
||||
:href="prependPath(dataset.getUrl('rerun'))"
|
||||
@click.stop.prevent="
|
||||
backboneRoute('/', {
|
||||
job_id: dataset.creating_job,
|
||||
})
|
||||
">
|
||||
<Icon icon="play" class="mr-1" />
|
||||
<span v-localize>Run job again</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="showViz && hasViz && isIn(STATES.OK, STATES.FAILED_METADATA)"
|
||||
title="Visualize Data"
|
||||
@click.stop.prevent="visualize">
|
||||
<Icon icon="chart-area" class="mr-1" />
|
||||
<span v-localize>Visualize Data</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="currentUser && currentUser.id && dataset.creating_job"
|
||||
title="Tool Help"
|
||||
@click.stop="showToolHelp(dataset.creating_job)">
|
||||
<Icon icon="question" class="mr-1" />
|
||||
<span v-localize>Tool Help</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
v-if="notIn(STATES.NOT_VIEWABLE)"
|
||||
key="dataset-details"
|
||||
title="View Dataset Details"
|
||||
class="params-btn"
|
||||
@click.stop.prevent="showDetails">
|
||||
<Icon icon="info-circle" class="mr-1" />
|
||||
<span v-localize>View Dataset Details</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</b-button-group>
|
||||
|
||||
<!-- modals -->
|
||||
<b-modal :id="bsId('delete-modal')" title="Delete Dataset?" title-tag="h2" @ok="onDeleteClick">
|
||||
<p v-localize>Really delete this dataset?</p>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { Dataset } from "../../model";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import IconButton from "components/IconButton";
|
||||
|
||||
export default {
|
||||
mixins: [legacyNavigationMixin],
|
||||
inject: ["STATES"],
|
||||
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
dataset: { type: Dataset, required: true },
|
||||
expanded: { type: Boolean, required: true },
|
||||
writable: { type: Boolean, default: true },
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
toolHelp: null,
|
||||
testVal: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters("user", ["currentUser"]),
|
||||
...mapGetters("config", ["config"]),
|
||||
|
||||
showViz() {
|
||||
return this.config.visualizations_visible;
|
||||
},
|
||||
|
||||
displayButtonTitle() {
|
||||
if (this.dataset.purged) {
|
||||
return "Cannot display datasets removed from disk";
|
||||
}
|
||||
if (this.dataset.state == this.STATES.UPLOAD) {
|
||||
return "This dataset must finish uploading before it can be viewed";
|
||||
}
|
||||
if (this.dataset.state == this.STATES.NEW) {
|
||||
return "This dataset is not yet viewable";
|
||||
}
|
||||
return "View data";
|
||||
},
|
||||
|
||||
editButtonTitle() {
|
||||
if (this.dataset.deleted) {
|
||||
return "Undelete dataset to edit attributes";
|
||||
}
|
||||
if (this.dataset.purged) {
|
||||
return "Cannot edit attributes of datasets removed from disk";
|
||||
}
|
||||
const unreadyStates = new Set([this.STATES.UPLOAD, this.STATES.NEW]);
|
||||
if (unreadyStates.has(this.dataset.state)) {
|
||||
return "This dataset is not yet editable";
|
||||
}
|
||||
return "Edit attributes";
|
||||
},
|
||||
|
||||
deleteButtonTitle() {
|
||||
return this.dataset.purged
|
||||
? "Dataset has been permanently deleted"
|
||||
: this.dataset.deleted
|
||||
? "Undelete"
|
||||
: "Delete";
|
||||
},
|
||||
|
||||
showDownloads() {
|
||||
if (this.dataset.purged) {
|
||||
return false;
|
||||
}
|
||||
if (!this.dataset.hasData) {
|
||||
return false;
|
||||
}
|
||||
const okStates = new Set([this.STATES.OK, this.STATES.FAILED_METADATA, this.STATES.ERROR]);
|
||||
return okStates.has(this.dataset.state);
|
||||
},
|
||||
|
||||
hasViz() {
|
||||
const viz_count = this.dataset.viz_count || 0;
|
||||
return viz_count && this.dataset.hasData && !this.dataset.deleted;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
notIn(...states) {
|
||||
const badStates = new Set(states);
|
||||
return !badStates.has(this.dataset.state);
|
||||
},
|
||||
|
||||
isIn(...states) {
|
||||
const goodStates = new Set(states);
|
||||
return goodStates.has(this.dataset.state);
|
||||
},
|
||||
|
||||
viewData() {
|
||||
const id = this.dataset.id;
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
this.useGalaxy((Galaxy) => {
|
||||
if (Galaxy.frame && Galaxy.frame.active) {
|
||||
Galaxy.frame.addDataset(id);
|
||||
} else {
|
||||
const path = this.dataset.getUrl("display");
|
||||
if (path) {
|
||||
this.iframeRedirect(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// wierd iframe navigation
|
||||
visualize() {
|
||||
const showDetailsUrl = `/datasets/${this.dataset.id}/details`;
|
||||
const redirectParams = {
|
||||
path: showDetailsUrl,
|
||||
title: "Dataset details",
|
||||
tryIframe: false,
|
||||
};
|
||||
if (!this.iframeAdd(redirectParams)) {
|
||||
this.backboneRoute("visualizations", {
|
||||
dataset_id: this.dataset.id,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showDetails() {
|
||||
const showDetailsUrl = `/datasets/${this.dataset.id}/details`;
|
||||
const redirectParams = {
|
||||
path: showDetailsUrl,
|
||||
title: "Dataset details",
|
||||
tryIframe: false,
|
||||
};
|
||||
if (!this.iframeAdd(redirectParams)) {
|
||||
this.backboneRoute(showDetailsUrl);
|
||||
}
|
||||
},
|
||||
|
||||
showToolHelp(job_id) {
|
||||
this.eventHub.$emit("toggleToolHelp", job_id);
|
||||
},
|
||||
|
||||
onDeleteClick() {
|
||||
const eventName = this.dataset.deleted ? "undelete" : "delete";
|
||||
this.$emit(eventName);
|
||||
},
|
||||
|
||||
// bootstrap demands ids instead of simply using a ref so I guess we're doing this
|
||||
bsId(suffix) {
|
||||
return `dataset-${this.dataset.id}-${suffix}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,249 +0,0 @@
|
||||
<!--
|
||||
The guts and markup of the dataset item separated from the
|
||||
data assignment so we can re-use it on the collection listing
|
||||
|
||||
DO NOT put data retrieval into this component, access the dataset
|
||||
either through the props, and make updates through the events -->
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dataset history-content"
|
||||
:id="typeId"
|
||||
:class="{ expanded, collapsed, selected }"
|
||||
: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)">
|
||||
<!-- 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">
|
||||
<div class="pl-1" v-if="showSelectionBox">
|
||||
<b-check class="selector" :checked="selected" @change="$emit('update:selected', $event)"></b-check>
|
||||
</div>
|
||||
|
||||
<StatusIcon
|
||||
v-if="!collapsed"
|
||||
class="status-icon px-1"
|
||||
:state="dataset.state"
|
||||
@click.stop="onStatusClick" />
|
||||
|
||||
<IconButton
|
||||
v-if="!collapsed && !dataset.visible"
|
||||
class="px-1"
|
||||
state="hidden"
|
||||
title="Unhide"
|
||||
icon="eye-slash"
|
||||
@click.stop="$emit('unhide')" />
|
||||
|
||||
<IconButton
|
||||
v-if="!collapsed && dataset.isDeleted && !dataset.purged"
|
||||
class="px-1"
|
||||
state="deleted"
|
||||
title="Undelete"
|
||||
icon="trash-restore"
|
||||
@click.stop="$emit('undelete')" />
|
||||
|
||||
<div class="content-title title p-1 overflow-hidden">
|
||||
<h5 class="text-truncate" v-if="collapsed">
|
||||
<span class="hid" data-description="dataset hid">{{ dataset.hid }}</span>
|
||||
<span class="name" data-description="dataset name">{{ dataset.title }}</span>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<slot name="menu">
|
||||
<DatasetMenu
|
||||
:dataset="dataset"
|
||||
:expanded="expanded"
|
||||
:writable="writable"
|
||||
@edit="edit"
|
||||
v-on="$listeners" />
|
||||
</slot>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!--- read-only tags with name: prefix -->
|
||||
<div v-if="collapsed && dataset.nameTags.length" class="nametags px-2 pb-2">
|
||||
<Nametag v-for="tag in dataset.nameTags" :key="tag" :tag="tag" />
|
||||
</div>
|
||||
|
||||
<div v-if="expanded" class="p-3 details">
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1">
|
||||
<h4 data-description="dataset name">{{ name || "(Dataset Name)" }}</h4>
|
||||
|
||||
<template v-if="!editing">
|
||||
<p v-if="annotation">{{ annotation }}</p>
|
||||
<div v-if="dataset.nameTags.length" class="nametags mt-2">
|
||||
<Nametag v-for="tag in dataset.nameTags" :key="tag" :tag="tag" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="my-3">
|
||||
<b-textarea
|
||||
class="mb-3"
|
||||
v-model="name"
|
||||
placeholder="Dataset Name"
|
||||
trim
|
||||
max-rows="4"></b-textarea>
|
||||
|
||||
<b-textarea
|
||||
class="mb-3"
|
||||
v-model="annotation"
|
||||
placeholder="Annotation (optional)"
|
||||
trim
|
||||
max-rows="4"></b-textarea>
|
||||
|
||||
<StatelessTags class="mt-2" v-model="tags" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditorMenu
|
||||
class="ml-3 flex-grow-0 d-flex flex-column"
|
||||
v-if="writable"
|
||||
model-name="Dataset"
|
||||
:editing.sync="editing"
|
||||
:writable="writable"
|
||||
:valid="valid"
|
||||
:dirty="dirty"
|
||||
@save="save"
|
||||
@revert="revert" />
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<DatasetSummary :dataset="dataset" class="summary" @edit="edit" />
|
||||
|
||||
<div class="display-applications" v-if="dataset.displayLinks.length">
|
||||
<div class="display-application" v-for="app in dataset.displayLinks" :key="app.label">
|
||||
<span class="display-application-location">
|
||||
{{ app.label }}
|
||||
</span>
|
||||
<span class="display-application-links">
|
||||
<a v-for="l in app.links" :key="l.href" :href="l.href" :target="l.target">
|
||||
{{ l.text }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<pre v-if="dataset.peek" class="dataset-peek p-1" v-html="dataset.peek"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dataset, STATES } from "../../model";
|
||||
import { Nametag } from "components/Nametags";
|
||||
import StatusIcon from "../../StatusIcon";
|
||||
import DatasetMenu from "./DatasetMenu";
|
||||
import DatasetSummary from "./Summary";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import IconButton from "components/IconButton";
|
||||
import EditorMenu from "../../EditorMenu";
|
||||
import { StatelessTags } from "components/Tags";
|
||||
|
||||
export default {
|
||||
mixins: [legacyNavigationMixin],
|
||||
components: {
|
||||
StatusIcon,
|
||||
DatasetMenu,
|
||||
DatasetSummary,
|
||||
Nametag,
|
||||
IconButton,
|
||||
EditorMenu,
|
||||
StatelessTags,
|
||||
},
|
||||
props: {
|
||||
dataset: { type: Dataset, required: true },
|
||||
expanded: { type: Boolean, required: true },
|
||||
selected: { type: Boolean, required: false, default: false },
|
||||
showSelection: { type: Boolean, required: false, default: false },
|
||||
writable: { type: Boolean, required: false, default: true },
|
||||
selectable: { type: Boolean, required: false, default: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false,
|
||||
tempContent: this.dataset.clone(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
collapsed() {
|
||||
return !this.expanded;
|
||||
},
|
||||
typeId() {
|
||||
return this.dataset.type_id;
|
||||
},
|
||||
isEditing() {
|
||||
return this.writable && this.editing;
|
||||
},
|
||||
valid() {
|
||||
return true;
|
||||
},
|
||||
dirty() {
|
||||
return !this.tempContent.equals(this.dataset);
|
||||
},
|
||||
name: {
|
||||
get() {
|
||||
return this.tempContent?.name || "";
|
||||
},
|
||||
set(newVal) {
|
||||
this.patchTempContent({ name: newVal });
|
||||
},
|
||||
},
|
||||
annotation: {
|
||||
get() {
|
||||
return this.tempContent?.annotation || "";
|
||||
},
|
||||
set(newVal) {
|
||||
this.patchTempContent({ annotation: newVal });
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
get() {
|
||||
return this.tempContent?.tags || [];
|
||||
},
|
||||
set(newTags) {
|
||||
if (Array.isArray(newTags)) {
|
||||
this.patchTempContent({ tags: newTags });
|
||||
}
|
||||
},
|
||||
},
|
||||
showSelectionBox() {
|
||||
return this.selectable && this.showSelection;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onStatusClick(evt) {
|
||||
switch (this.dataset.state) {
|
||||
case STATES.ERROR:
|
||||
this.backboneRoute("datasets/error", { dataset_id: this.dataset.id });
|
||||
break;
|
||||
default:
|
||||
console.log("unhandled status icon click", this.dataset.state);
|
||||
}
|
||||
},
|
||||
patchTempContent(props = {}) {
|
||||
this.tempContent = this.tempContent.patch(props);
|
||||
},
|
||||
save() {
|
||||
if (this.valid) {
|
||||
this.$emit("update:dataset", this.tempContent);
|
||||
this.editing = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.tempContent = this.dataset.clone();
|
||||
},
|
||||
revert() {
|
||||
if (this.dirty) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
edit() {
|
||||
this.backboneRoute("datasets/edit", { dataset_id: this.dataset.id });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>The job creating this dataset was cancelled before completion.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div v-if="props.dataset">
|
||||
<span v-localize>No data</span>: <i>{{ props.dataset.misc_blurb }}</i>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,11 +0,0 @@
|
||||
<template functional>
|
||||
<div v-if="props.dataset">
|
||||
<div v-if="!props.dataset.purged && props.dataset.misc_info">
|
||||
<span>{{ props.dataset.misc_info }}</span>
|
||||
</div>
|
||||
<span class="help-text" v-localize>An error occurred with this dataset</span>
|
||||
<div v-if="props.dataset.misc_info" class="job-error-text">
|
||||
<span>{{ props.dataset.misc_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>This is a new dataset and not all of its data are available yet.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>You do not have permission to view this dataset.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template functional>
|
||||
<div v-if="props.dataset">
|
||||
<div v-if="props.dataset.misc_blurb" class="blurb">
|
||||
<span class="value">{{ props.dataset.misc_blurb }}</span>
|
||||
</div>
|
||||
<div v-if="props.dataset.file_ext" class="datatype">
|
||||
<label class="prompt" v-localize>format</label>
|
||||
<span class="value">{{ props.dataset.file_ext }}</span>
|
||||
</div>
|
||||
<div v-if="props.dataset.genome_build" class="dbkey">
|
||||
<label class="prompt" v-localize>database</label>
|
||||
<b-link class="value" @click.stop="listeners['edit']()">{{ props.dataset.genome_build }}</b-link>
|
||||
</div>
|
||||
<div v-if="props.dataset.misc_info" class="info">
|
||||
<span class="value">{{ props.dataset.misc_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,8 +0,0 @@
|
||||
<template functional>
|
||||
<div v-if="dataset">
|
||||
<p v-localize>This job is paused. Use the "Resume Paused Jobs" in the history menu to resume.</p>
|
||||
<div v-if="dataset.misc_info">
|
||||
<span>{{ props.dataset.misc_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>This job is waiting to run.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>This job is currently running.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>Metadata is being auto-detected.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template functional>
|
||||
<div v-if="props.dataset">Error: unknown dataset state "{{ props.dataset.state }}"</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template functional>
|
||||
<div>
|
||||
<p v-localize>This dataset is currently uploading.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<component :is="summaryComponent" :dataset="dataset" @edit="edit" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { capitalize, camelize } from "underscore.string";
|
||||
|
||||
import Discarded from "./Discarded";
|
||||
import Empty from "./Empty";
|
||||
import Error from "./Error";
|
||||
import New from "./New";
|
||||
import NotViewable from "./NotViewable";
|
||||
import Ok from "./Ok";
|
||||
import Paused from "./Paused";
|
||||
import Queued from "./Queued";
|
||||
import Running from "./Running";
|
||||
import SettingMetadata from "./SettingMetadata";
|
||||
import Upload from "./Upload";
|
||||
|
||||
export default {
|
||||
inject: ["STATES"],
|
||||
components: {
|
||||
Discarded,
|
||||
Empty,
|
||||
Error,
|
||||
New,
|
||||
NotViewable,
|
||||
Ok,
|
||||
Paused,
|
||||
Queued,
|
||||
Running,
|
||||
SettingMetadata,
|
||||
Upload,
|
||||
},
|
||||
props: {
|
||||
dataset: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
summaryComponent() {
|
||||
let state = this.dataset.state;
|
||||
if (state == this.STATES.FAILED_METADATA) {
|
||||
state = this.STATES.OK;
|
||||
}
|
||||
return capitalize(camelize(state));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
edit() {
|
||||
this.$emit("edit");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +0,0 @@
|
||||
import Dataset from "./Dataset";
|
||||
|
||||
export { default as DatasetUI } from "./DatasetUI";
|
||||
export { default as DatasetMenu } from "./DatasetMenu";
|
||||
export default Dataset;
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<DscUI
|
||||
v-if="dsc"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
class="dataset-collection history-content"
|
||||
:dsc="dsc"
|
||||
@update:dsc="onUpdate"
|
||||
@delete="onDelete"
|
||||
@undelete="onUndelete"
|
||||
@unhide="onUnhide" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DscUI from "./DscUI";
|
||||
import { DatasetCollection } from "../../model";
|
||||
import { deleteDatasetCollection, updateContentFields } from "../../model/queries";
|
||||
import { cacheContent } from "components/providers/History/caching";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DscUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
dsc() {
|
||||
return new DatasetCollection(this.item);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onDelete(flags = {}) {
|
||||
const { recursive = false, purge = false } = flags;
|
||||
const collection = this.item;
|
||||
const result = await deleteDatasetCollection(collection, recursive, purge);
|
||||
if (result.deleted) {
|
||||
const newFields = Object.assign(collection, {
|
||||
isDeleted: result.deleted,
|
||||
});
|
||||
await cacheContent(newFields);
|
||||
}
|
||||
},
|
||||
async onUnhide() {
|
||||
await this.onUpdate({ visible: true });
|
||||
},
|
||||
async onUndelete() {
|
||||
await this.onUpdate({ deleted: false });
|
||||
},
|
||||
async onUpdate(changes) {
|
||||
const newContent = await updateContentFields(this.item, changes);
|
||||
await cacheContent(newContent);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
<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>
|
||||
@@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div class="collection-menu">
|
||||
<b-button-group>
|
||||
<IconButton
|
||||
icon="info-circle"
|
||||
v-if="hasJob"
|
||||
key="dataset-details"
|
||||
title="View Dataset Collection Details"
|
||||
@click.stop.prevent="backboneRoute(collectionDetailsPath)"
|
||||
variant="link"
|
||||
class="px-1">
|
||||
</IconButton>
|
||||
<IconButton
|
||||
v-if="notIn(STATES.DISCARDED)"
|
||||
icon="pen"
|
||||
:title="editButtonTitle"
|
||||
:disabled="dsc.deleted || isIn(STATES.UPLOAD, STATES.NEW)"
|
||||
@click.stop="backboneRoute('collection/edit/' + dsc.hdca_id)"
|
||||
variant="link"
|
||||
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" />
|
||||
<span class="sr-only">Delete Collection</span>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item @click.stop="$emit('delete')">
|
||||
<span v-localize>Delete Collection Only</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item @click.stop="$emit('delete', { recursive: true })">
|
||||
<span v-localize>Delete Contained Datasets</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item @click.stop="$emit('delete', { recursive: true, purge: true })">
|
||||
<span v-localize>Purge Contained Datasets</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</b-button-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DatasetCollection } from "../../model";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import IconButton from "components/IconButton";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
deleteSelected: null,
|
||||
deleteCollectionModalShow: false,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
mixins: [legacyNavigationMixin],
|
||||
inject: ["STATES"],
|
||||
props: {
|
||||
dsc: { type: DatasetCollection, required: true },
|
||||
},
|
||||
computed: {
|
||||
editButtonTitle() {
|
||||
if (this.dsc.deleted) {
|
||||
return "Collection must not be deleted to edit";
|
||||
}
|
||||
if (this.dsc.purged) {
|
||||
return "Cannot edit attributes of collections removed from disk";
|
||||
}
|
||||
const unreadyStates = new Set([this.STATES.UPLOAD, this.STATES.NEW]);
|
||||
if (unreadyStates.has(this.dsc.state)) {
|
||||
return "This collection is not yet editable";
|
||||
}
|
||||
return "Edit attributes";
|
||||
},
|
||||
deleteCollectionButtonTitle() {
|
||||
return "Delete Collection";
|
||||
},
|
||||
hasJob() {
|
||||
return this.dsc?.job_source_type == "Job";
|
||||
},
|
||||
collectionDetailsPath() {
|
||||
return `jobs/${this.dsc.job_source_id}/view`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
runDelete: function (kind) {
|
||||
if (kind) {
|
||||
this.$emit("delete", kind);
|
||||
} else {
|
||||
this.$emit("delete");
|
||||
}
|
||||
},
|
||||
notIn(...states) {
|
||||
const badStates = new Set(states);
|
||||
return !badStates.has(this.dsc.state);
|
||||
},
|
||||
isIn(...states) {
|
||||
const goodStates = new Set(states);
|
||||
return goodStates.has(this.dsc.state);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,114 +0,0 @@
|
||||
<!-- UI only, updates/changes to data should be handled outside
|
||||
this component via events emissions -->
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dataset dataset-collection collapsed"
|
||||
:class="{ selected }"
|
||||
:id="typedId"
|
||||
:data-state="dsc.state"
|
||||
@keydown.arrow-right.self.stop="$emit('viewCollection')"
|
||||
@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">
|
||||
<b-check
|
||||
class="selector"
|
||||
:checked="selected"
|
||||
@click.stop
|
||||
@change="$emit('update:selected', $event)" />
|
||||
</div>
|
||||
|
||||
<StatusIcon
|
||||
v-if="dsc.state != 'ok'"
|
||||
class="status-icon px-1"
|
||||
:state="dsc.state"
|
||||
@click.stop="onStatusClick" />
|
||||
|
||||
<IconButton
|
||||
v-if="!dsc.visible"
|
||||
class="px-1"
|
||||
state="hidden"
|
||||
title="Unhide"
|
||||
icon="eye-slash"
|
||||
@click.stop="$emit('unhide')" />
|
||||
|
||||
<IconButton
|
||||
v-if="dsc.deleted"
|
||||
class="px-1"
|
||||
state="deleted"
|
||||
title="Undelete"
|
||||
icon="trash-restore"
|
||||
@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>
|
||||
</h5>
|
||||
<DscDescription :dsc="dsc" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex" v-if="writable">
|
||||
<slot name="menu">
|
||||
<DscMenu v-if="!dsc.deleted" v-on="$listeners" :dsc="dsc" />
|
||||
</slot>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!--- read-only tags with name: prefix -->
|
||||
<div v-if="collapsed && dsc.nameTags.length" class="nametags px-2 pb-2">
|
||||
<Nametag v-for="tag in dsc.nameTags" :key="tag" :tag="tag" />
|
||||
</div>
|
||||
|
||||
<JobStateProgress class="m-2" v-if="dsc.jobSummary" :summary="dsc.jobSummary" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StatusIcon,
|
||||
JobStateProgress,
|
||||
DscMenu,
|
||||
Nametag,
|
||||
IconButton,
|
||||
DscDescription,
|
||||
},
|
||||
props: {
|
||||
dsc: { type: DatasetCollection, required: true },
|
||||
selected: { type: Boolean, required: false, default: false },
|
||||
showSelection: { type: Boolean, required: false, default: false },
|
||||
writable: { type: Boolean, required: false, default: true },
|
||||
},
|
||||
methods: {
|
||||
onStatusClick() {
|
||||
console.log("onStatusClick", ...arguments);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
typedId() {
|
||||
return this.dsc.type_id;
|
||||
},
|
||||
collapsed() {
|
||||
return !this.expanded;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
header,
|
||||
header * {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,44 +0,0 @@
|
||||
<!-- Job state progress bar for a collection. There's another similar component
|
||||
at components/JobStates/CollectionJobStates but it relies on the backbone data
|
||||
model, so probably has to go eventually.-->
|
||||
|
||||
<template>
|
||||
<b-progress v-if="maxJobs && runningJobs" :max="maxJobs" height="1em">
|
||||
<b-progress-bar v-if="errorJobs" :value="errorJobs" variant="danger"></b-progress-bar>
|
||||
<b-progress-bar v-if="okJobs" :value="okJobs" variant="success"></b-progress-bar>
|
||||
<b-progress-bar v-if="runningJobs" :value="runningJobs" variant="info" animated></b-progress-bar>
|
||||
<b-progress-bar v-if="waitingJobs" :value="waitingJobs" variant="dark" animated></b-progress-bar>
|
||||
</b-progress>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JobStateSummary } from "../../model";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
summary: { type: JobStateSummary, required: true },
|
||||
},
|
||||
computed: {
|
||||
hasRunningJobs() {
|
||||
return this.summary && this.summary.running > 0;
|
||||
},
|
||||
maxJobs() {
|
||||
return this.summary.all_jobs;
|
||||
},
|
||||
okJobs() {
|
||||
return this.summary.ok;
|
||||
},
|
||||
runningJobs() {
|
||||
return this.summary.running;
|
||||
},
|
||||
errorJobs() {
|
||||
const { failed = 0, error = 0 } = this.summary;
|
||||
return failed + error;
|
||||
},
|
||||
waitingJobs() {
|
||||
const { waiting = 0, queued = 0 } = this.summary;
|
||||
return waiting + queued;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
import DatasetCollection from "./DatasetCollection";
|
||||
|
||||
export { default as DscUI } from "./DscUI";
|
||||
export { default as DscMenu } from "./DscMenu";
|
||||
export { default as JobStateProgress } from "./JobStateProgress";
|
||||
export default DatasetCollection;
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="contentItem in collectionContents" :index="1" :key="contentItem.id" style="margin-left: 2em">
|
||||
<DatasetProvider
|
||||
v-if="contentItem.element_type === 'hda'"
|
||||
:id="contentItem.object.id"
|
||||
v-slot="{ item, loading }">
|
||||
<div>
|
||||
<loading-span v-if="loading" message="Loading datasets" />
|
||||
<DatasetUIWrapper v-else :item="item" :element_identifier="contentItem.element_identifier">
|
||||
</DatasetUIWrapper>
|
||||
</div>
|
||||
</DatasetProvider>
|
||||
<DatasetCollectionUIWrapper
|
||||
v-if="contentItem.element_type === 'dataset_collection'"
|
||||
:item="contentItem"
|
||||
:element_count="collectionContents.length + 1">
|
||||
</DatasetCollectionUIWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { DatasetProvider } from "components/providers";
|
||||
import DatasetUIWrapper from "./DatasetUIWrapper";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetProvider,
|
||||
DatasetCollectionUIWrapper: () => import("./DatasetCollectionUIWrapper"),
|
||||
DatasetUIWrapper,
|
||||
LoadingSpan,
|
||||
},
|
||||
props: {
|
||||
collectionContents: { type: Array, required: true },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
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/providers/History/test/json/DatasetCollection.json";
|
||||
import datasetCollectionContent from "components/providers/History/test/json/DatasetCollection.nested.json";
|
||||
|
||||
describe("DatasetUIWrapper.vue with Dataset", () => {
|
||||
let wrapper;
|
||||
let propsData;
|
||||
let onHide;
|
||||
let onUnhide;
|
||||
let onDelete;
|
||||
let onUndelete;
|
||||
|
||||
beforeEach(async () => {
|
||||
propsData = {
|
||||
item: datasetCollectionRaw,
|
||||
};
|
||||
onHide = jest.spyOn(DatasetCollectionUIWrapper.methods, "onHide").mockImplementation();
|
||||
onUnhide = jest.spyOn(DatasetCollectionUIWrapper.methods, "onUnhide").mockImplementation();
|
||||
onDelete = jest.spyOn(DatasetCollectionUIWrapper.methods, "onDelete").mockImplementation();
|
||||
onUndelete = jest.spyOn(DatasetCollectionUIWrapper.methods, "onUndelete").mockImplementation();
|
||||
wrapper = shallowMount(DatasetCollectionUIWrapper, {
|
||||
propsData,
|
||||
});
|
||||
});
|
||||
|
||||
it("loads DscUI component", async () => {
|
||||
expect(wrapper.findComponent(DscUI).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("reacts to all emittted events", async () => {
|
||||
expect(onHide).not.toHaveBeenCalled();
|
||||
expect(onUnhide).not.toHaveBeenCalled();
|
||||
expect(onDelete).not.toHaveBeenCalled();
|
||||
expect(onUndelete).not.toHaveBeenCalled();
|
||||
wrapper.findComponent(DscUI).vm.$emit("hide");
|
||||
expect(onHide).toHaveBeenCalled();
|
||||
wrapper.findComponent(DscUI).vm.$emit("unhide");
|
||||
expect(onUnhide).toHaveBeenCalled();
|
||||
wrapper.findComponent(DscUI).vm.$emit("delete");
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
wrapper.findComponent(DscUI).vm.$emit("undelete");
|
||||
expect(onUndelete).toHaveBeenCalled();
|
||||
});
|
||||
it("creates DatasetCollection", async () => {
|
||||
const datasetCollection = wrapper.vm.datasetCollection;
|
||||
expect(datasetCollection.collectionType).toBe("list of pairs");
|
||||
});
|
||||
it("manages collection expansion", async () => {
|
||||
expect(wrapper.vm.expand).toBeFalsy();
|
||||
wrapper.findComponent(DscUI).vm.$emit("viewCollection", wrapper.vm.datasetCollection);
|
||||
expect(wrapper.vm.expand).toBeTruthy();
|
||||
});
|
||||
it("build dsc from collection content", async () => {
|
||||
wrapper.setProps({ item: datasetCollectionContent });
|
||||
await flushPromises();
|
||||
expect(wrapper.vm.datasetCollection.name).toBe(datasetCollectionContent.element_identifier);
|
||||
});
|
||||
});
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<DscUI
|
||||
v-if="datasetCollection"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:dsc="datasetCollection"
|
||||
:show-tags="true"
|
||||
@viewCollection="toggleExpand"
|
||||
@hide="onHide(datasetCollection)"
|
||||
@unhide="onUnhide(datasetCollection)"
|
||||
@delete="onDelete(datasetCollection, $event)"
|
||||
@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" />
|
||||
</DatasetCollectionContentProvider>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { deleteDatasetCollection, updateContentFields } from "../../model/queries";
|
||||
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";
|
||||
import DatasetCollectionContents from "./DatasetCollectionContents";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetCollectionContentProvider,
|
||||
DatasetCollectionContents,
|
||||
DscUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
element_count: { type: Number, required: false },
|
||||
},
|
||||
data() {
|
||||
return { expand: false };
|
||||
},
|
||||
computed: {
|
||||
datasetCollection() {
|
||||
var rawObject = this.item;
|
||||
if (rawObject.model_class === "DatasetCollectionElement") {
|
||||
rawObject = this.item.object;
|
||||
rawObject.name = this.item.element_identifier;
|
||||
rawObject.contents_url = this.item.contents_url || rawObject.contents_url;
|
||||
rawObject.element_count = this.element_count;
|
||||
rawObject.populated_state = "ok";
|
||||
}
|
||||
return new DatasetCollection(rawObject);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleExpand() {
|
||||
this.expand = !this.expand;
|
||||
},
|
||||
async onDelete(collection, flags = {}) {
|
||||
const { recursive = false, purge = false } = flags;
|
||||
const result = await deleteDatasetCollection(collection, recursive, purge);
|
||||
if (result.deleted) {
|
||||
const newFields = Object.assign(collection, {
|
||||
isDeleted: result.deleted,
|
||||
});
|
||||
await cacheContent(newFields);
|
||||
}
|
||||
},
|
||||
|
||||
async onUndelete(collection) {
|
||||
const result = await updateContentFields(collection, { deleted: false });
|
||||
if (result.deleted === false) {
|
||||
const newFields = Object.assign(collection, {
|
||||
isDeleted: result.deleted,
|
||||
});
|
||||
await cacheContent(newFields);
|
||||
}
|
||||
},
|
||||
async onHide(collection) {
|
||||
const result = await updateContentFields(collection, { visible: false });
|
||||
if (result.visible === false) {
|
||||
const newFields = Object.assign(collection, {
|
||||
isVisible: result.visible,
|
||||
});
|
||||
await cacheContent(newFields);
|
||||
}
|
||||
},
|
||||
async onUnhide(collection) {
|
||||
const result = await updateContentFields(collection, { visible: true });
|
||||
if (result.visible) {
|
||||
const newFields = Object.assign(collection, {
|
||||
isVisible: result.visible,
|
||||
});
|
||||
await cacheContent(newFields);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,51 +0,0 @@
|
||||
import DatasetUIWrapper from "./DatasetUIWrapper";
|
||||
import DatasetUI from "components/History/ContentItem/Dataset/DatasetUI";
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import raw from "components/providers/History/test/json/Dataset.json";
|
||||
|
||||
describe("DatasetUIWrapper.vue with Dataset", () => {
|
||||
let wrapper;
|
||||
let propsData;
|
||||
let onDelete;
|
||||
let onUnhide;
|
||||
let onUndelete;
|
||||
let updateDataset;
|
||||
|
||||
beforeEach(async () => {
|
||||
propsData = {
|
||||
item: raw,
|
||||
};
|
||||
onDelete = jest.spyOn(DatasetUIWrapper.methods, "onDelete").mockImplementation();
|
||||
onUnhide = jest.spyOn(DatasetUIWrapper.methods, "onUnhide").mockImplementation();
|
||||
onUndelete = jest.spyOn(DatasetUIWrapper.methods, "onUndelete").mockImplementation();
|
||||
updateDataset = jest.spyOn(DatasetUIWrapper.methods, "onUpdate").mockImplementation();
|
||||
wrapper = shallowMount(DatasetUIWrapper, {
|
||||
propsData,
|
||||
});
|
||||
});
|
||||
|
||||
it("loads DatasetUI component", async () => {
|
||||
expect(wrapper.findComponent(DatasetUI).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("reacts to all emittted events", async () => {
|
||||
expect(onDelete).not.toHaveBeenCalled();
|
||||
expect(onUnhide).not.toHaveBeenCalled();
|
||||
expect(onUndelete).not.toHaveBeenCalled();
|
||||
expect(updateDataset).not.toHaveBeenCalled();
|
||||
wrapper.findComponent(DatasetUI).vm.$emit("delete");
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
wrapper.findComponent(DatasetUI).vm.$emit("undelete");
|
||||
expect(onUndelete).toHaveBeenCalled();
|
||||
wrapper.findComponent(DatasetUI).vm.$emit("unhide");
|
||||
expect(onUnhide).toHaveBeenCalled();
|
||||
wrapper.findComponent(DatasetUI).vm.$emit("update");
|
||||
expect(updateDataset).toHaveBeenCalled();
|
||||
});
|
||||
it("handles element identifier", async () => {
|
||||
expect(wrapper.vm.dataset.canEditName).toBeTruthy();
|
||||
await wrapper.setProps({ element_identifier: "a cool identifier" });
|
||||
expect(wrapper.vm.dataset.title).toBe("a cool identifier");
|
||||
expect(wrapper.vm.dataset.canEditName).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
<!-- controls display of DisplayUI and delegates modifications -->
|
||||
<template>
|
||||
<DatasetUI
|
||||
v-if="dataset"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@update:expanded="expand"
|
||||
:expanded="expanded"
|
||||
:dataset="dataset"
|
||||
:show-tags="true"
|
||||
@delete="onDelete"
|
||||
@undelete="onUndelete"
|
||||
@unhide="onUnhide"
|
||||
@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 "components/providers/History/caching";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
element_identifier: { type: String, required: false, default: "" },
|
||||
},
|
||||
computed: {
|
||||
dataset() {
|
||||
const element = { ...this.item, element_identifier: this.element_identifier };
|
||||
return new Dataset(element);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
expand(event) {
|
||||
this.expanded = !this.expanded;
|
||||
},
|
||||
async onDelete(opts = {}) {
|
||||
const ajaxResult = await deleteContent(this.dataset, opts);
|
||||
await cacheContent(ajaxResult);
|
||||
},
|
||||
async onUnhide() {
|
||||
await this.onUpdate({ visible: true });
|
||||
},
|
||||
async onUndelete() {
|
||||
await this.onUpdate({ deleted: false });
|
||||
},
|
||||
async onUpdate(changes = {}) {
|
||||
const newContent = await updateContentFields(this.dataset, changes);
|
||||
await cacheContent(newContent);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<component :is="providerComponent" :id="data_item.id" v-slot="{ item, loading }">
|
||||
<div>
|
||||
<loading-span v-if="loading" message="Loading dataset" />
|
||||
<component :is="renderComponent" v-if="item" :item="item"> </component>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DatasetProvider, DatasetCollectionProvider } from "components/providers";
|
||||
import DatasetCollectionUIWrapper from "./DatasetCollectionUIWrapper";
|
||||
import DatasetUIWrapper from "./DatasetUIWrapper";
|
||||
import { STATES } from "components/History/model";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetProvider,
|
||||
DatasetCollectionProvider,
|
||||
DatasetUIWrapper,
|
||||
DatasetCollectionUIWrapper,
|
||||
LoadingSpan,
|
||||
},
|
||||
props: {
|
||||
data_item: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
renderComponent() {
|
||||
return { hda: "DatasetUIWrapper", hdca: "DatasetCollectionUIWrapper" }[this.data_item.src];
|
||||
},
|
||||
providerComponent() {
|
||||
return { hda: "DatasetProvider", hdca: "DatasetCollectionProvider" }[this.data_item.src];
|
||||
},
|
||||
},
|
||||
provide: {
|
||||
STATES,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
import ContentItem from "./ContentItem";
|
||||
|
||||
export default {
|
||||
mixins: [ContentItem],
|
||||
|
||||
computed: {
|
||||
contentItemComponent() {
|
||||
if (this.item.id === undefined) {
|
||||
return "Placeholder";
|
||||
}
|
||||
const { history_content_type } = this.item;
|
||||
switch (history_content_type) {
|
||||
case "dataset":
|
||||
return "Dataset";
|
||||
case "dataset_collection":
|
||||
return "DatasetCollection";
|
||||
default:
|
||||
return "Placeholder";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<div class="collapsed" data-state="waiting">
|
||||
<nav class="content-top-menu d-flex align-items-center justify-content-between p-1">
|
||||
<h5 class="flex-grow-1 overflow-hidden mr-auto text-nowrap text-truncate">
|
||||
<span class="name">Placholder: {{ item.hid }}</span>
|
||||
</h5>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
index: { type: Number, required: true },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,29 +0,0 @@
|
||||
<!-- a separate collection content item for collections
|
||||
inside of other collections -->
|
||||
|
||||
<template>
|
||||
<DscUI v-if="dsc" v-bind="$attrs" v-on="$listeners" class="dataset-collection history-content" :dsc="dsc" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DatasetCollection } from "../model/DatasetCollection";
|
||||
import { STATES } from "../model";
|
||||
import DscUI from "./DatasetCollection/DscUI";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DscUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
dsc() {
|
||||
// TODO: I'm sure this is not the right way to set the state, but the collection api
|
||||
// doesn't return any kind of state value at because the api is inconsistent
|
||||
// /api/dataset_collections/d071e794759ab192/contents/e144222fed44799a
|
||||
return new DatasetCollection({ ...this.item, populated_state: STATES.OK });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,60 +0,0 @@
|
||||
<!-- a read-only dataset inside a collection. The collection query does not return enough
|
||||
information to properly render the dataset, so what we do is look it up on expand, checking the
|
||||
cache first to see if we already have the data -->
|
||||
|
||||
<template>
|
||||
<DatasetUI
|
||||
v-if="dataset"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:dataset="dataset"
|
||||
:writable="false"
|
||||
:selectable="false"
|
||||
@update:expanded="onExpand" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dataset } from "../model";
|
||||
import { getContentByTypeId, cacheContent } from "components/providers/History/caching";
|
||||
import { getContentDetails } from "../model/queries";
|
||||
import DatasetUI from "./Dataset/DatasetUI";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatasetUI,
|
||||
},
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localItem: { ...this.item },
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataset() {
|
||||
return new Dataset(this.localItem);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onExpand(val) {
|
||||
if (val) {
|
||||
this.loadDetails();
|
||||
}
|
||||
this.$emit("update:expand", val);
|
||||
},
|
||||
async loadDetails() {
|
||||
const { type_id, history_id } = this.item;
|
||||
const existingDataset = await getContentByTypeId(history_id, type_id);
|
||||
if (existingDataset) {
|
||||
this.localItem = existingDataset;
|
||||
} else {
|
||||
const loadedDataset = await getContentDetails(this.item);
|
||||
await cacheContent(loadedDataset);
|
||||
this.localItem = loadedDataset;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Scrolling components for history contents and collection contents.
|
||||
*/
|
||||
|
||||
import "./styles.scss";
|
||||
|
||||
export { default as ContentItem } from "./ContentItem";
|
||||
export { default as CollectionContentItem } from "./CollectionContentItem";
|
||||
export { default as HistoryContentItem } from "./HistoryContentItem";
|
||||
@@ -1,32 +0,0 @@
|
||||
@import "theme/blue.scss";
|
||||
@import "scss/mixins.scss";
|
||||
|
||||
.content-item {
|
||||
outline: none;
|
||||
border-bottom: 2px solid white;
|
||||
&:focus-within {
|
||||
box-shadow: inset 0 0 1em 0.25em rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
// Nav across the top of the content item
|
||||
.content-item .content-top-menu {
|
||||
min-height: 2em;
|
||||
|
||||
.content-title {
|
||||
.hid {
|
||||
color: adjust-color($text-color, $alpha: -0.6);
|
||||
font-size: 80%;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
min-width: 0.25rem;
|
||||
&:not(:empty)::after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
.name {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<Details
|
||||
:name="name"
|
||||
:tags="dsc.tags"
|
||||
:writeable="writeable"
|
||||
:show-annotation="false"
|
||||
@save="$emit('update:dsc', $event)">
|
||||
<template v-slot:name>
|
||||
<h3 data-description="collection name display">{{ name || "Collection" }}</h3>
|
||||
<CollectionDescription :item="dsc" />
|
||||
</template>
|
||||
</Details>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Details from "components/History/Layout/Details";
|
||||
import CollectionDescription from "components/History/Content/Collection/CollectionDescription";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CollectionDescription,
|
||||
Details,
|
||||
},
|
||||
props: {
|
||||
dsc: { type: Object, required: true },
|
||||
writeable: { type: Boolean, required: true },
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.dsc.name || this.dsc.element_identifier;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-button size="sm" variant="link" @click="close">
|
||||
<icon icon="angle-double-left" class="mr-1" data-description="back to history" /><span>History</span>
|
||||
</b-button>
|
||||
<b-button size="sm" variant="link" v-if="previousName" @click="back">
|
||||
<span class="fa fa-angle-left mr-1" /><span>{{ previousName }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
selectedCollections: { type: Array, required: true, validate: (val) => val.length > 0 },
|
||||
},
|
||||
computed: {
|
||||
previousName() {
|
||||
const length = this.selectedCollections.length;
|
||||
if (length > 1) {
|
||||
const last = this.selectedCollections[length - 2];
|
||||
return last.name || last.element_identifier;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
const newList = this.selectedCollections.slice(0, -1);
|
||||
this.$emit("update:selected-collections", newList);
|
||||
},
|
||||
close() {
|
||||
this.$emit("update:selected-collections", []);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,32 +1,25 @@
|
||||
<template>
|
||||
<section>
|
||||
<nav class="d-flex justify-content-end">
|
||||
<nav class="content-operations d-flex justify-content-between bg-secondary">
|
||||
<b-button-group>
|
||||
<IconButton
|
||||
v-if="isRoot"
|
||||
class="mb-2 mx-3"
|
||||
icon="download"
|
||||
title="Download Collection"
|
||||
:href="downloadCollectionUrl"
|
||||
download />
|
||||
<b-button title="Download Collection" size="sm" variant="link" download :href="downloadUrl">
|
||||
<Icon class="mr-1" icon="download" />
|
||||
<span>Download</span>
|
||||
</b-button>
|
||||
</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 },
|
||||
dsc: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
/** @return {String} */
|
||||
downloadCollectionUrl() {
|
||||
return `${this.collection.url}/download`;
|
||||
downloadUrl() {
|
||||
return `${this.dsc.url}/download`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<!-- When a dataset collection is being viewed, this panel shows the contents of that collection -->
|
||||
|
||||
<template>
|
||||
<UrlDataProvider v-if="dsc" :key="dsc.id" :url="url" auto-refresh v-slot="{ result: payload }">
|
||||
<ExpandedItems :scope-key="dsc.id" :get-item-key="(item) => item.id" v-slot="{ isExpanded, setExpanded }">
|
||||
<Layout class="dataset-collection-panel">
|
||||
<template v-slot:navigation>
|
||||
<CollectionNavigation
|
||||
:history="history"
|
||||
:selected-collections="selectedCollections"
|
||||
v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listcontrols>
|
||||
<CollectionOperations v-if="isRoot" :dsc="dsc" />
|
||||
</template>
|
||||
|
||||
<template v-slot:details>
|
||||
<CollectionDetails :dsc="dsc" :writeable="isRoot" @update:dsc="updateDsc(dsc, $event)" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listing>
|
||||
<Listing item-key="element_index" :payload="payload" :limit="limit" @scroll="onScroll">
|
||||
<template v-slot:history-item="{ item }">
|
||||
<ContentItem
|
||||
:item="item.object"
|
||||
:id="item.element_index"
|
||||
:name="item.element_identifier"
|
||||
:is-dataset="item.element_type == 'hda'"
|
||||
:expand-dataset="isExpanded(item)"
|
||||
:is-history-item="false"
|
||||
@update:expand-dataset="setExpanded(item, $event)"
|
||||
@view-collection="$emit('view-collection', item)" />
|
||||
</template>
|
||||
</Listing>
|
||||
</template>
|
||||
</Layout>
|
||||
</ExpandedItems>
|
||||
</UrlDataProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UrlDataProvider } from "components/providers/UrlDataProvider";
|
||||
import { History } from "components/History/model";
|
||||
import { updateContentFields } from "components/History/model/queries";
|
||||
import ContentItem from "components/History/Content/ContentItem";
|
||||
import ExpandedItems from "components/History/Content/ExpandedItems";
|
||||
import Listing from "components/History/Layout/Listing";
|
||||
import Layout from "components/History/Layout/Layout";
|
||||
import CollectionNavigation from "./CollectionNavigation";
|
||||
import CollectionOperations from "./CollectionOperations";
|
||||
import CollectionDetails from "./CollectionDetails";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CollectionDetails,
|
||||
CollectionNavigation,
|
||||
CollectionOperations,
|
||||
ContentItem,
|
||||
ExpandedItems,
|
||||
Layout,
|
||||
Listing,
|
||||
UrlDataProvider,
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
selectedCollections: { type: Array, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
offset: 0,
|
||||
limit: 100,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dsc() {
|
||||
const arr = this.selectedCollections;
|
||||
return arr[arr.length - 1];
|
||||
},
|
||||
isRoot() {
|
||||
return this.dsc == this.rootCollection;
|
||||
},
|
||||
rootCollection() {
|
||||
return this.selectedCollections[0];
|
||||
},
|
||||
url() {
|
||||
// either the observered source is a collection element or the initial history item i.e. root collection
|
||||
const source = this.dsc.object || this.rootCollection;
|
||||
const contentsUrl = source.contents_url.substring(1);
|
||||
return `${contentsUrl}?offset=${this.offset}&limit=${this.limit}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDsc(collection, fields) {
|
||||
updateContentFields(collection, fields).then((response) => {
|
||||
Object.keys(response).forEach((key) => {
|
||||
collection[key] = response[key];
|
||||
});
|
||||
});
|
||||
},
|
||||
onScroll(offset) {
|
||||
this.offset = offset;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,128 +0,0 @@
|
||||
<!-- Edits a collection, if that collection is editable. -->
|
||||
|
||||
<template>
|
||||
<section class="history-details d-flex" data-description="edit details">
|
||||
<div class="flex-grow-1">
|
||||
<h3 class="history-title" data-description="collection name display">
|
||||
{{ dscName || "(Collection Name)" }}
|
||||
</h3>
|
||||
<p class="mt-1">
|
||||
<DscDescription :dsc="dsc" />
|
||||
</p>
|
||||
|
||||
<div v-if="isEditing" class="mt-3" @keydown.esc="revertAndCancel" data-description="edit form">
|
||||
<b-textarea
|
||||
v-model="dscName"
|
||||
placeholder="Collection Name"
|
||||
trim
|
||||
max-rows="4"
|
||||
data-description="name input"></b-textarea>
|
||||
<StatelessTags v-model="tags" class="mt-3 tags" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="dsc.tags && dsc.tags.length">
|
||||
<Nametag v-for="tag in dsc.tags" :key="tag" :tag="tag" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditorMenu
|
||||
class="ml-3 flex-grow-0 d-flex flex-column"
|
||||
v-if="writable"
|
||||
model-name="Collection"
|
||||
:editing.sync="editing"
|
||||
:writable="writable"
|
||||
:valid="valid"
|
||||
:dirty="dirty"
|
||||
@save="save"
|
||||
@revert="revert" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 },
|
||||
writable: { type: Boolean, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false,
|
||||
tempContent: this.dsc.clone(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dscId() {
|
||||
return this.dsc.id;
|
||||
},
|
||||
isEditing() {
|
||||
return this.writable && this.editing;
|
||||
},
|
||||
valid() {
|
||||
return this.tempContent.name.length > 0;
|
||||
},
|
||||
dirty() {
|
||||
return !this.tempContent.equals(this.dsc);
|
||||
},
|
||||
dscName: {
|
||||
get() {
|
||||
return this.tempContent.name;
|
||||
},
|
||||
set(newName) {
|
||||
this.patchTempDsc({ name: newName });
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
get() {
|
||||
return this.tempContent?.tags || [];
|
||||
},
|
||||
set(newTags) {
|
||||
if (Array.isArray(newTags)) {
|
||||
this.patchTempDsc({ tags: newTags });
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
dscId(newId, oldId) {
|
||||
if (newId != oldId) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reset() {
|
||||
this.tempContent = this.dsc.clone();
|
||||
},
|
||||
patchTempDsc(props = {}) {
|
||||
this.tempContent = this.tempContent.patch(props);
|
||||
},
|
||||
save() {
|
||||
if (this.valid) {
|
||||
this.$emit("update:dsc", this.tempContent);
|
||||
this.editing = false;
|
||||
}
|
||||
},
|
||||
revert() {
|
||||
if (this.dirty) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
revertAndCancel() {
|
||||
this.revert();
|
||||
this.editing = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,126 +0,0 @@
|
||||
<!-- When a dataset collection is being viewed, this panel shows the contents of that collection -->
|
||||
|
||||
<template>
|
||||
<DscProvider :is-root="isRoot" :debounce-period="500" :collection="selectedCollection" v-slot="{ dsc }">
|
||||
<CollectionContentProvider v-if="dsc" :parent="dsc" :debug="false" v-slot="{ loading, payload, setScrollPos }">
|
||||
<ExpandedItems
|
||||
:scope-key="selectedCollection.id"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
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>
|
||||
<!-- Empty -->
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<template v-slot:listcontrols>
|
||||
<CollectionOperations :collection="selectedCollection" :is-root="isRoot" />
|
||||
</template>
|
||||
|
||||
<template v-slot:details>
|
||||
<Details :dsc="dsc" :writable="writable" @update:dsc="updateDsc(dsc, $event)" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listing>
|
||||
<Scroller
|
||||
key-field="element_index"
|
||||
:class="{ loadingBackground: loading }"
|
||||
v-bind="payload"
|
||||
@scroll="setScrollPos"
|
||||
:debug="false">
|
||||
<template v-slot="{ item, index, rowKey }">
|
||||
<CollectionContentItem
|
||||
:item="item"
|
||||
:index="index"
|
||||
:row-key="rowKey"
|
||||
:expanded="isExpanded(item)"
|
||||
:writable="false"
|
||||
:selectable="false"
|
||||
@update:expanded="setExpanded(item, $event)"
|
||||
@viewCollection="$emit('viewCollection', item)"
|
||||
:data-index="index"
|
||||
:data-row-key="rowKey" />
|
||||
</template>
|
||||
</Scroller>
|
||||
</template>
|
||||
</Layout>
|
||||
</ExpandedItems>
|
||||
</CollectionContentProvider>
|
||||
</DscProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "../model";
|
||||
import { updateContentFields } from "../model/queries";
|
||||
import { cacheContent } from "components/providers/History/caching";
|
||||
|
||||
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 "components/providers/History/ContentProvider/helpers";
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
reportPayload,
|
||||
},
|
||||
components: {
|
||||
DscProvider,
|
||||
CollectionContentProvider,
|
||||
Layout,
|
||||
TopNav,
|
||||
Details,
|
||||
Scroller,
|
||||
CollectionContentItem,
|
||||
ExpandedItems,
|
||||
CollectionOperations,
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
selectedCollections: { type: Array, required: true },
|
||||
},
|
||||
computed: {
|
||||
selectedCollection() {
|
||||
const arr = this.selectedCollections;
|
||||
const selected = arr[arr.length - 1];
|
||||
return selected;
|
||||
},
|
||||
isRoot() {
|
||||
return this.selectedCollection == this.rootCollection;
|
||||
},
|
||||
writable() {
|
||||
return this.isRoot;
|
||||
},
|
||||
rootCollection() {
|
||||
return this.selectedCollections[0];
|
||||
},
|
||||
downloadCollectionUrl() {
|
||||
let url = "";
|
||||
if (this.rootCollection) {
|
||||
url = `${this.rootCollection.url}/download`;
|
||||
}
|
||||
return url;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// change the data of the root collection, anything past the root
|
||||
// collection is part of the dataset collection, which i believe is supposed to
|
||||
// be immutable, so only edit name, tags, blah of top-level selected collection,
|
||||
async updateDsc(collection, fields) {
|
||||
if (this.writable) {
|
||||
const ajaxResult = await updateContentFields(collection, fields);
|
||||
await cacheContent({ ...collection, ...ajaxResult });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<b-dropdown
|
||||
v-if="breadCrumbOptions.length"
|
||||
size="sm"
|
||||
text="Return to..."
|
||||
boundary="viewport"
|
||||
data-description="collection breadcrumbs menu"
|
||||
no-caret>
|
||||
<template v-slot:button-content>
|
||||
<Icon icon="arrow-up" class="mr-1" />
|
||||
<b class="text-nowrap" v-localize>Return to...</b>
|
||||
</template>
|
||||
|
||||
<b-dropdown-item @click="close" data-description="back to history">
|
||||
<span>History: {{ history.name }}</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-for="option in breadCrumbOptions" :key="option.key" @click="reselect(option.value)">
|
||||
<span>{{ option.text }}</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-button
|
||||
v-else
|
||||
class="back"
|
||||
size="sm"
|
||||
title="`Return to: ${history.name}`"
|
||||
@click="close"
|
||||
data-description="back to history">
|
||||
<Icon icon="arrow-up" class="mr-1" />
|
||||
<b class="text-nowrap">Return to: {{ history.name }}</b>
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "../model";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
selectedCollections: { type: Array, required: true, validate: (val) => val.length > 0 },
|
||||
},
|
||||
computed: {
|
||||
// List for the dropdown. Should be the history and every parent
|
||||
// collection excepting the current one.
|
||||
breadCrumbOptions() {
|
||||
const parents = this.selectedCollections.slice(0, -1);
|
||||
const options = parents.map((bc, i) => {
|
||||
return {
|
||||
key: bc.type_id,
|
||||
value: parents.slice(0, i + 1),
|
||||
text: bc.name,
|
||||
};
|
||||
});
|
||||
return options;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reselect(newList) {
|
||||
this.$emit("update:selectedCollections", newList);
|
||||
},
|
||||
back() {
|
||||
const newList = this.selectedCollections.slice(0, -1);
|
||||
this.reselect(newList);
|
||||
},
|
||||
close() {
|
||||
this.reselect([]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<Details
|
||||
:name="history.name"
|
||||
:annotation="history.annotation"
|
||||
:tags="history.tags"
|
||||
:writeable="writeable"
|
||||
@save="$emit('update:currentHistory', $event)">
|
||||
<template v-slot:name>
|
||||
<h3 data-description="history name display">{{ history.name || "History" }}</h3>
|
||||
<h5 class="history-size">
|
||||
<span v-if="history.size">{{ history.size | niceFileSize }}</span>
|
||||
<span v-else v-localize>(empty)</span>
|
||||
</h5>
|
||||
</template>
|
||||
</Details>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { History } from "components/History/model";
|
||||
import Details from "components/History/Layout/Details";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Details,
|
||||
},
|
||||
filters: {
|
||||
niceFileSize(rawSize = 0) {
|
||||
return prettyBytes(rawSize);
|
||||
},
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
writeable: { type: Boolean, default: true },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+4
-15
@@ -31,7 +31,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SearchParams } from "components/providers/History/SearchParams";
|
||||
import DebouncedInput from "components/DebouncedInput";
|
||||
|
||||
export default {
|
||||
@@ -39,25 +38,15 @@ export default {
|
||||
DebouncedInput,
|
||||
},
|
||||
props: {
|
||||
params: { type: SearchParams, required: true },
|
||||
params: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
skip: {
|
||||
get() {
|
||||
return this.params.skip;
|
||||
},
|
||||
set(newSkip) {
|
||||
const newParams = this.params.clone();
|
||||
newParams.skip = newSkip;
|
||||
this.updateParams(newParams);
|
||||
},
|
||||
},
|
||||
filterText: {
|
||||
get() {
|
||||
return this.params.filterText;
|
||||
},
|
||||
set(newVal) {
|
||||
const newParams = this.params.clone();
|
||||
const newParams = Object.assign({}, this.params);
|
||||
newParams.filterText = newVal;
|
||||
this.updateParams(newParams);
|
||||
},
|
||||
@@ -67,7 +56,7 @@ export default {
|
||||
return this.params.showDeleted;
|
||||
},
|
||||
set(newFlag) {
|
||||
const newParams = this.params.clone();
|
||||
const newParams = Object.assign({}, this.params);
|
||||
newParams.showDeleted = newFlag;
|
||||
this.updateParams(newParams);
|
||||
},
|
||||
@@ -77,7 +66,7 @@ export default {
|
||||
return this.params.showHidden;
|
||||
},
|
||||
set(newFlag) {
|
||||
const newParams = this.params.clone();
|
||||
const newParams = Object.assign({}, this.params);
|
||||
newParams.showHidden = newFlag;
|
||||
this.updateParams(newParams);
|
||||
},
|
||||
+2
-2
@@ -106,8 +106,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "./model";
|
||||
import CopyHistoryModal from "./CopyModal";
|
||||
import { History } from "components/History/model";
|
||||
import CopyHistoryModal from "components/History/Modals/CopyModal";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
|
||||
export default {
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "./model";
|
||||
import { History } from "components/History/model";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
+3
-3
@@ -61,10 +61,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "./model";
|
||||
import { History } from "components/History/model";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import { switchToLegacyHistoryPanel } from "./adapters/betaToggle";
|
||||
import HistorySelectorModal from "./HistorySelectorModal";
|
||||
import { switchToLegacyHistoryPanel } from "components/History/adapters/betaToggle";
|
||||
import HistorySelectorModal from "components/History/Modals/HistorySelectorModal";
|
||||
|
||||
export default {
|
||||
mixins: [legacyNavigationMixin],
|
||||
+95
-140
@@ -1,117 +1,103 @@
|
||||
<template>
|
||||
<section>
|
||||
<nav class="content-operations-menu d-flex justify-content-between bg-secondary">
|
||||
<nav class="content-operations d-flex justify-content-between bg-secondary">
|
||||
<b-button-group>
|
||||
<IconButton
|
||||
:title="hasSelection ? 'Select All Contents' : 'Unselect All Contents'"
|
||||
icon="check-square"
|
||||
:disabled="!totalMatches"
|
||||
:pressed="hasSelection"
|
||||
@click="toggleSelectAll" />
|
||||
<IconButton
|
||||
title="Filter History Content"
|
||||
icon="filter"
|
||||
:disabled="!totalMatches"
|
||||
<b-button
|
||||
title="Select Items"
|
||||
class="show-history-content-selectors-btn rounded-0"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:disabled="!hasMatches"
|
||||
:pressed="showSelection"
|
||||
@click="toggleSelection">
|
||||
<Icon icon="check-square" />
|
||||
</b-button>
|
||||
<b-button
|
||||
title="Search Items"
|
||||
class="rounded-0"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:pressed="showFilter"
|
||||
@click="showFilter = !showFilter"
|
||||
data-description="content filter toggle" />
|
||||
<IconButton
|
||||
title="Collapse Expanded Items"
|
||||
icon="compress"
|
||||
@click="toggleFilter"
|
||||
data-description="content filter toggle">
|
||||
<Icon icon="search" />
|
||||
</b-button>
|
||||
<b-button
|
||||
title="Collapse Items"
|
||||
class="rounded-0"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:disabled="!expandedCount"
|
||||
@click="$emit('collapseAllContent')" />
|
||||
@click="$emit('collapse-all')">
|
||||
<Icon icon="compress" />
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
|
||||
<b-button-group>
|
||||
<b-dropdown
|
||||
class="history-contents-list-action-menu-btn"
|
||||
size="sm"
|
||||
text="Selection"
|
||||
:disabled="!hasSelection"
|
||||
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>
|
||||
size="sm"
|
||||
variant="link"
|
||||
toggle-class="text-decoration-none"
|
||||
data-description="selected content menu"
|
||||
:disabled="!hasSelection">
|
||||
<b-dropdown-text>
|
||||
<span v-localize>With {{ numSelected }} selected...</span>
|
||||
</b-dropdown-text>
|
||||
|
||||
<b-dropdown-item aria-describedby="history-op-selected-content" v-b-modal:hide-selected-content>
|
||||
<span v-localize>Hide</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item aria-describedby="history-op-selected-content" v-b-modal:show-selected-content>
|
||||
<b-dropdown-item v-if="params.showHidden" v-b-modal:show-selected-content>
|
||||
<span v-localize>Unhide</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item aria-describedby="history-op-selected-content" v-b-modal:delete-selected-content>
|
||||
<span v-localize>Delete</span>
|
||||
<b-dropdown-item v-else v-b-modal:hide-selected-content>
|
||||
<span v-localize>Hide</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item aria-describedby="history-op-selected-content" v-b-modal:restore-selected-content>
|
||||
<b-dropdown-item v-if="params.showDeleted" v-b-modal:restore-selected-content>
|
||||
<span v-localize>Undelete</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
aria-describedby="history-op-selected-content"
|
||||
v-b-modal:purge-selected-content
|
||||
:disabled="!hasSelection">
|
||||
<span v-localize>Permanently Delete</span>
|
||||
<b-dropdown-item v-else v-b-modal:delete-selected-content>
|
||||
<span v-localize>Delete</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
aria-describedby="history-op-selected-content"
|
||||
@click="buildDatasetList"
|
||||
data-description="build list">
|
||||
<b-dropdown-item v-if="!params.showDeleted" v-b-modal:purge-selected-content>
|
||||
<span v-localize>Delete (permanently)</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item v-if="showBuildOptions" @click="buildDatasetList" 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">
|
||||
<b-dropdown-item v-if="showBuildOptions" @click="buildDatasetPair" data-description="build pair">
|
||||
<span v-localize>Build Dataset Pair</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item
|
||||
aria-describedby="history-op-selected-content"
|
||||
v-if="showBuildOptions"
|
||||
@click="buildListOfPairs"
|
||||
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"
|
||||
v-if="showBuildOptions"
|
||||
@click="buildCollectionFromRules"
|
||||
data-description="build collection from rules">
|
||||
<span v-localize>Build Collection from Rules</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-dropdown
|
||||
class="history-contents-list-action-menu-btn"
|
||||
size="sm"
|
||||
text="History"
|
||||
:disabled="!totalMatches"
|
||||
data-description="history action menu">
|
||||
size="sm"
|
||||
variant="link"
|
||||
class="rounded-0"
|
||||
toggle-class="text-decoration-none"
|
||||
data-description="history action menu"
|
||||
:disabled="!hasMatches">
|
||||
<b-dropdown-text id="history-op-all-content">
|
||||
<span v-localize>With entire history...</span>
|
||||
</b-dropdown-text>
|
||||
|
||||
<b-dropdown-item
|
||||
aria-describedby="history-op-all-content"
|
||||
@click="iframeRedirect('/dataset/copy_datasets')"
|
||||
data-description="copy datasets">
|
||||
<b-dropdown-item @click="iframeRedirect('/dataset/copy_datasets')" data-description="copy datasets">
|
||||
<span v-localize>Copy Datasets</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:show-all-hidden-content aria-describedby="history-op-all-content">
|
||||
<b-dropdown-item v-b-modal:show-all-hidden-content>
|
||||
<span v-localize>Unhide All Hidden Content</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:delete-all-hidden-content aria-describedby="history-op-all-content">
|
||||
<b-dropdown-item v-b-modal:delete-all-hidden-content>
|
||||
<span v-localize>Delete All Hidden Content</span>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item v-b-modal:purge-all-deleted-content aria-describedby="history-op-all-content">
|
||||
<b-dropdown-item v-b-modal:purge-all-deleted-content>
|
||||
<span v-localize>Purge All Hidden Content</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
@@ -119,11 +105,9 @@
|
||||
</nav>
|
||||
|
||||
<transition name="shutterfade">
|
||||
<content-filters v-if="showContentFilters" class="content-filters p-2" :params.sync="localParams" />
|
||||
<history-filters v-if="showFilter" class="content-operations-filters p-2" :params.sync="localParams" />
|
||||
</transition>
|
||||
|
||||
<!-- #region Menus and popovers -->
|
||||
|
||||
<b-modal id="hide-selected-content" title="Hide Selected Content?" title-tag="h2" @ok="hideSelected">
|
||||
<p v-localize>Really hide {{ numSelected }} content items?</p>
|
||||
</b-modal>
|
||||
@@ -155,14 +139,11 @@
|
||||
<b-modal id="purge-all-deleted-content" title="Purge Deleted Datasets" title-tag="h2" @ok="purgeAllDeleted">
|
||||
<p v-localize>"Really delete all deleted datasets permanently? This cannot be undone.</p>
|
||||
</b-modal>
|
||||
|
||||
<!-- #endregion -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SearchParams } from "components/providers/History/SearchParams";
|
||||
import { History } from "./model/History";
|
||||
import { History } from "components/History/model/History";
|
||||
import {
|
||||
hideSelectedContent,
|
||||
unhideSelectedContent,
|
||||
@@ -172,28 +153,24 @@ import {
|
||||
unhideAllHiddenContent,
|
||||
deleteAllHiddenContent,
|
||||
purgeAllDeletedContent,
|
||||
} from "./model";
|
||||
import { createDatasetCollection } from "./model/queries";
|
||||
import { cacheContent } from "components/providers/History/caching";
|
||||
} from "components/History/model";
|
||||
import { createDatasetCollection } from "components/History/model/queries";
|
||||
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
|
||||
import { buildCollectionModal } from "./adapters/buildCollectionModal";
|
||||
import ContentFilters from "./ContentFilters";
|
||||
import IconButton from "components/IconButton";
|
||||
import { buildCollectionModal } from "components/History/adapters/buildCollectionModal";
|
||||
import HistoryFilters from "./HistoryFilters";
|
||||
|
||||
export default {
|
||||
mixins: [legacyNavigationMixin],
|
||||
components: {
|
||||
ContentFilters,
|
||||
IconButton,
|
||||
HistoryFilters,
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
params: { type: SearchParams, required: true },
|
||||
params: { type: Object, required: true },
|
||||
contentSelection: { type: Map, required: true },
|
||||
showSelection: { type: Boolean, required: true },
|
||||
totalMatches: { type: Number, required: true },
|
||||
hasMatches: { type: Boolean, required: true },
|
||||
expandedCount: { type: Number, required: false, default: 0 },
|
||||
debug: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -201,62 +178,55 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showContentFilters() {
|
||||
showBuildOptions() {
|
||||
return !this.params.showHidden && !this.params.showDeleted;
|
||||
},
|
||||
hasHistoryFilters() {
|
||||
const { showHidden, showDeleted, filterText } = this.params;
|
||||
return this.showFilter || showHidden || showDeleted || filterText;
|
||||
return showHidden || showDeleted || filterText;
|
||||
},
|
||||
|
||||
localParams: {
|
||||
get() {
|
||||
return this.params;
|
||||
},
|
||||
set(newVal) {
|
||||
this.$emit("update:params", newVal.clone());
|
||||
this.$emit("update:params", Object.assign({}, newVal));
|
||||
},
|
||||
},
|
||||
|
||||
numSelected() {
|
||||
return this.contentSelection.size || 0;
|
||||
},
|
||||
|
||||
hasSelection() {
|
||||
return this.numSelected > 0;
|
||||
},
|
||||
|
||||
countHidden() {
|
||||
return this.history.contents_active.hidden;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSelectAll() {
|
||||
if (this.hasSelection) {
|
||||
this.$emit("resetSelection");
|
||||
this.$emit("update:show-selection", false);
|
||||
} else {
|
||||
this.$emit("selectAllContent");
|
||||
this.$emit("update:show-selection", true);
|
||||
toggleSelection() {
|
||||
this.$emit("update:show-selection", !this.showSelection);
|
||||
},
|
||||
toggleFilter() {
|
||||
if (!this.showFilter) {
|
||||
this.showFilter = true;
|
||||
} else if (!this.hasContentFilters) {
|
||||
this.showFilter = false;
|
||||
}
|
||||
},
|
||||
|
||||
// #region history-wide bulk updates, does server query first to determine "selection"
|
||||
|
||||
// History-wide bulk updates, does server query first to determine "selection"
|
||||
async unhideAll(evt) {
|
||||
await unhideAllHiddenContent(this.history);
|
||||
this.$emit("manualReload");
|
||||
this.$emit("reload");
|
||||
},
|
||||
async deleteAllHidden(evt) {
|
||||
await deleteAllHiddenContent(this.history);
|
||||
this.$emit("manualReload");
|
||||
this.$emit("reload");
|
||||
},
|
||||
async purgeAllDeleted(evt) {
|
||||
await purgeAllDeletedContent(this.history);
|
||||
this.$emit("manualReload");
|
||||
this.$emit("reload");
|
||||
},
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Selected content manipulation, hide/show/delete/purge
|
||||
|
||||
// Selected content manipulation, hide/show/delete/purge
|
||||
hideSelected() {
|
||||
this.runOnSelection(hideSelectedContent);
|
||||
},
|
||||
@@ -275,18 +245,12 @@ export default {
|
||||
async runOnSelection(fn) {
|
||||
const items = Array.from(this.contentSelection.values());
|
||||
const type_ids = items.map((o) => o.type_id);
|
||||
const results = await fn(this.history, type_ids);
|
||||
if (this.debug) {
|
||||
console.log("operation results", results);
|
||||
}
|
||||
this.$emit("resetSelection");
|
||||
this.$emit("manualReload");
|
||||
await fn(this.history, type_ids);
|
||||
this.$emit("hide-selection", items);
|
||||
this.$emit("reset-selection");
|
||||
},
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region collection creation, fires up a modal
|
||||
|
||||
// collection creation, fires up a modal
|
||||
async buildDatasetList() {
|
||||
await this.buildNewCollection("list");
|
||||
},
|
||||
@@ -301,28 +265,19 @@ export default {
|
||||
},
|
||||
async buildNewCollection(collectionTypeCode) {
|
||||
const modalResult = await buildCollectionModal(collectionTypeCode, this.history.id, this.contentSelection);
|
||||
const newCollection = await createDatasetCollection(this.history, modalResult);
|
||||
|
||||
// cache the collection
|
||||
await cacheContent(newCollection);
|
||||
await createDatasetCollection(this.history, modalResult);
|
||||
|
||||
// have to hide the source items if that was requested
|
||||
if (modalResult.hide_source_items) {
|
||||
this.contentSelection.forEach(async (dataset) => {
|
||||
await cacheContent({ ...dataset, visible: false }, true);
|
||||
});
|
||||
this.$emit("resetSelection");
|
||||
this.$emit("hide-selection", this.contentSelection);
|
||||
this.$emit("reset-selection");
|
||||
}
|
||||
|
||||
// this.$emit("manualReload");
|
||||
},
|
||||
|
||||
// #endregion
|
||||
},
|
||||
watch: {
|
||||
hasSelection(newVal) {
|
||||
if (newVal) {
|
||||
this.$emit("update:showSelection", true);
|
||||
this.$emit("update:show-selection", true);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -331,7 +286,7 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
// remove borders around buttons in menu
|
||||
.content-operations-menu .btn-group .btn {
|
||||
.content-operations .btn-group .btn {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<UrlDataProvider :key="history.id" :url="dataUrl" auto-refresh v-slot="{ loading, result: payload }">
|
||||
<ExpandedItems
|
||||
:scope-key="history.id"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
v-slot="{ expandedCount, isExpanded, setExpanded, collapseAll }">
|
||||
<SelectedItems
|
||||
:scope-key="history.id"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
v-slot="{
|
||||
selectedItems,
|
||||
showSelection,
|
||||
setShowSelection,
|
||||
selectItems,
|
||||
isSelected,
|
||||
setSelected,
|
||||
resetSelection,
|
||||
}">
|
||||
<Layout>
|
||||
<template v-slot:navigation>
|
||||
<slot name="navigation" :history="history" />
|
||||
<HistoryMenu :history="history" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<template v-slot:details>
|
||||
<HistoryDetails :history="history" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<template v-slot:messages>
|
||||
<HistoryMessages class="m-2" :history="history" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listcontrols>
|
||||
<HistoryOperations
|
||||
:history="history"
|
||||
:params.sync="params"
|
||||
:content-selection="selectedItems"
|
||||
:show-selection="showSelection"
|
||||
:expanded-count="expandedCount"
|
||||
:has-matches="hasMatches(payload)"
|
||||
@update:content-selection="selectItems"
|
||||
@update:show-selection="setShowSelection"
|
||||
@reset-selection="resetSelection"
|
||||
@hide-selection="onHideSelection"
|
||||
@select-all="selectItems(payload)"
|
||||
@collapse-all="collapseAll" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listing>
|
||||
<HistoryEmpty v-if="payload && payload.length == 0" class="m-2" />
|
||||
<b-alert v-else-if="loading" class="m-2" variant="info" show>
|
||||
<LoadingSpan message="Loading History" />
|
||||
</b-alert>
|
||||
<Listing
|
||||
v-else
|
||||
reversed
|
||||
:query-key="queryKey"
|
||||
:limit="limit"
|
||||
:payload="payload"
|
||||
@scroll="onScroll">
|
||||
<template v-slot:history-item="{ item }">
|
||||
<ContentItem
|
||||
v-if="!hideSelection[item.hid]"
|
||||
:item="item"
|
||||
:id="item.hid"
|
||||
:name="item.name"
|
||||
:expand-dataset="isExpanded(item)"
|
||||
:is-dataset="item.history_content_type == 'dataset'"
|
||||
:selected="isSelected(item)"
|
||||
:selectable="showSelection"
|
||||
@update:expand-dataset="setExpanded(item, $event)"
|
||||
@update:selected="setSelected(item, $event)"
|
||||
@view-collection="$emit('view-collection', item)"
|
||||
@delete="onDelete"
|
||||
@undelete="onUndelete"
|
||||
@unhide="onUnhide" />
|
||||
</template>
|
||||
</Listing>
|
||||
</template>
|
||||
|
||||
<template v-slot:modals>
|
||||
<ToolHelpModal />
|
||||
</template>
|
||||
</Layout>
|
||||
</SelectedItems>
|
||||
</ExpandedItems>
|
||||
</UrlDataProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UrlDataProvider } from "components/providers/UrlDataProvider";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import ContentItem from "components/History/Content/ContentItem";
|
||||
import { History } from "components/History/model";
|
||||
import { deleteContent, updateContentFields } from "components/History/model/queries";
|
||||
import ExpandedItems from "components/History/Content/ExpandedItems";
|
||||
import SelectedItems from "components/History/Content/SelectedItems";
|
||||
import Layout from "components/History/Layout/Layout";
|
||||
import Listing from "components/History/Layout/Listing";
|
||||
import ToolHelpModal from "components/History/Modals/ToolHelpModal";
|
||||
import HistoryOperations from "./HistoryOperations";
|
||||
import HistoryDetails from "./HistoryDetails";
|
||||
import HistoryEmpty from "./HistoryEmpty";
|
||||
import HistoryMenu from "./HistoryMenu";
|
||||
import HistoryMessages from "./HistoryMessages";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingSpan,
|
||||
UrlDataProvider,
|
||||
Layout,
|
||||
Listing,
|
||||
ContentItem,
|
||||
HistoryMessages,
|
||||
HistoryDetails,
|
||||
HistoryEmpty,
|
||||
HistoryMenu,
|
||||
HistoryOperations,
|
||||
ToolHelpModal,
|
||||
ExpandedItems,
|
||||
SelectedItems,
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hideSelection: {},
|
||||
offset: 0,
|
||||
limit: 500,
|
||||
params: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
queryKey() {
|
||||
this.hideSelection = {};
|
||||
this.offset = 0;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dataUrl() {
|
||||
return `api/histories/${this.historyId}/contents?v=dev&order=hid&offset=${this.offset}&limit=${this.limit}&${this.queryString}`;
|
||||
},
|
||||
historyId() {
|
||||
return this.history.id;
|
||||
},
|
||||
queryKey() {
|
||||
return `${this.history.id}&${this.queryString}`;
|
||||
},
|
||||
queryString() {
|
||||
const deleted = this.params.showDeleted ? "True" : "False";
|
||||
const visible = this.params.showHidden ? "False" : "True";
|
||||
return `q=deleted&q=visible&qv=${deleted}&qv=${visible}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasMatches(payload) {
|
||||
return !!payload && payload.length > 0;
|
||||
},
|
||||
onScroll(offset) {
|
||||
this.offset = offset;
|
||||
},
|
||||
onHideSelection(selectedItems) {
|
||||
selectedItems.forEach((item) => {
|
||||
this.hideSelection[item.hid] = true;
|
||||
});
|
||||
},
|
||||
onDelete(item) {
|
||||
this.hideSelection[item.hid] = true;
|
||||
deleteContent(item);
|
||||
},
|
||||
onUndelete(item) {
|
||||
this.hideSelection[item.hid] = true;
|
||||
updateContentFields(item, { deleted: false });
|
||||
},
|
||||
onUnhide(item) {
|
||||
this.hideSelection[item.hid] = true;
|
||||
updateContentFields(item, { visible: true });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<CurrentUser class="d-flex flex-column" v-slot="{ user }">
|
||||
<UserHistories v-if="user" :user="user" v-slot="{ currentHistory, histories, handlers }">
|
||||
<HistoryPanel v-if="currentHistory" :history="currentHistory" v-on="handlers" id="current-history-panel">
|
||||
<template v-slot:globalNav>
|
||||
<HistoriesMenu
|
||||
v-on="handlers"
|
||||
:histories="histories"
|
||||
:current-history="currentHistory"
|
||||
title="Histories" />
|
||||
</template>
|
||||
</HistoryPanel>
|
||||
|
||||
<div v-else class="flex-grow-1 loadingBackground h-100">
|
||||
<span class="sr-only" v-localize>Loading History...</span>
|
||||
</div>
|
||||
</UserHistories>
|
||||
</CurrentUser>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CurrentUser from "components/providers/CurrentUser";
|
||||
import UserHistories from "components/providers/History/UserHistories";
|
||||
import HistoryPanel from "./HistoryPanel";
|
||||
import HistoriesMenu from "./HistoriesMenu";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CurrentUser,
|
||||
UserHistories,
|
||||
HistoryPanel,
|
||||
HistoriesMenu,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<b-modal ref="deleteModal" title="Delete History" v-model="showModal" title-tag="h2">
|
||||
<p>Delete?</p>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { History } from "./model";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
value: { type: Boolean, required: false, default: false },
|
||||
},
|
||||
computed: {
|
||||
showModal: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.$emit("input", newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,73 +0,0 @@
|
||||
<template>
|
||||
<nav class="edit-controls">
|
||||
<IconButton
|
||||
:title="label('Edit') | l"
|
||||
class="edit-button mb-1"
|
||||
:pressed="editing"
|
||||
icon="pen"
|
||||
@click="$emit('update:editing', !editing)"
|
||||
data-description="editor toggle" />
|
||||
|
||||
<template v-if="editing">
|
||||
<!-- save -->
|
||||
<IconButton
|
||||
:title="label('Save') | l"
|
||||
class="save-button mb-1"
|
||||
:class="{ disabled: !valid }"
|
||||
icon="save"
|
||||
@click="$emit('save')"
|
||||
data-description="editor save button" />
|
||||
|
||||
<!-- revert -->
|
||||
<IconButton
|
||||
:title="label('Revert') | l"
|
||||
class="revert-changes-button mb-1"
|
||||
:class="{ disabled: !dirty }"
|
||||
icon="undo"
|
||||
@click="$emit('revert')"
|
||||
data-description="editor revert button" />
|
||||
|
||||
<!-- form status -->
|
||||
<IconButton
|
||||
:title="statusLabel | l"
|
||||
class="status-button mb-1"
|
||||
:class="{ valid: valid, disabled: !valid }"
|
||||
:icon="valid ? 'check' : 'exclamation-triangle'"
|
||||
variant="link"
|
||||
data-description="editor status" />
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from "components/IconButton";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
props: {
|
||||
modelName: { type: String, required: true },
|
||||
editing: { type: Boolean, required: true },
|
||||
dirty: { type: Boolean, required: false, default: false },
|
||||
valid: { type: Boolean, required: false, default: false },
|
||||
},
|
||||
computed: {
|
||||
statusLabel() {
|
||||
const status = this.valid ? "Valid" : "Invalid";
|
||||
return `${status} ${this.modelName}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
label(verb) {
|
||||
return `${verb} ${this.modelName}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.edit-controls .disabled {
|
||||
opacity: 0.25;
|
||||
}
|
||||
</style>
|
||||
@@ -1,146 +0,0 @@
|
||||
<template>
|
||||
<HistoryContentProvider
|
||||
:parent="history"
|
||||
:params="params"
|
||||
:disable-poll="false"
|
||||
:debug="false"
|
||||
:debounce-period="500"
|
||||
v-slot="{ loading, payload, manualReload, setScrollPos }">
|
||||
<ExpandedItems
|
||||
:scope-key="history.id"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
v-slot="{ expandedCount, isExpanded, setExpanded, collapseAll }">
|
||||
<SelectedItems
|
||||
:scope-key="history.id"
|
||||
:get-item-key="(item) => item.type_id"
|
||||
v-slot="{
|
||||
selectedItems,
|
||||
showSelection,
|
||||
setShowSelection,
|
||||
selectItems,
|
||||
isSelected,
|
||||
setSelected,
|
||||
resetSelection,
|
||||
}">
|
||||
<Layout>
|
||||
<template v-slot:globalNav>
|
||||
<slot name="globalNav" :history="history"></slot>
|
||||
</template>
|
||||
|
||||
<template v-slot:localNav>
|
||||
<HistoryMenu :history="history" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<template v-slot:details>
|
||||
<HistoryDetails :history="history" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<template v-slot:messages>
|
||||
<HistoryMessages class="m-2" :history="history" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listcontrols>
|
||||
<ContentOperations
|
||||
:history="history"
|
||||
:total-matches="payload.totalMatches"
|
||||
:loading="loading"
|
||||
:params.sync="params"
|
||||
:content-selection="selectedItems"
|
||||
@update:content-selection="selectItems"
|
||||
:show-selection="showSelection"
|
||||
@update:show-selection="setShowSelection"
|
||||
@resetSelection="resetSelection"
|
||||
@selectAllContent="selectItems(payload.contents)"
|
||||
@manualReload="manualReload"
|
||||
:expanded-count="expandedCount"
|
||||
@collapseAllContent="collapseAll" />
|
||||
</template>
|
||||
|
||||
<template v-slot:listing>
|
||||
<HistoryEmpty v-if="history.empty" class="m-2" />
|
||||
<HistoryEmpty v-else-if="payload && payload.noResults" message="No Results." class="m-2" />
|
||||
<Scroller
|
||||
v-else-if="payload"
|
||||
:class="{ loadingBackground: loading }"
|
||||
key-field="hid"
|
||||
v-bind="payload"
|
||||
:debug="false"
|
||||
@scroll="setScrollPos">
|
||||
<template v-slot="{ item, index, rowKey }">
|
||||
<HistoryContentItem
|
||||
:item="item"
|
||||
:index="index"
|
||||
:row-key="rowKey"
|
||||
:show-selection="showSelection"
|
||||
:expanded="isExpanded(item)"
|
||||
@update:expanded="setExpanded(item, $event)"
|
||||
:selected="isSelected(item)"
|
||||
@update:selected="setSelected(item, $event)"
|
||||
@viewCollection="$emit('viewCollection', item)"
|
||||
:data-hid="item.hid"
|
||||
:data-index="index"
|
||||
:data-row-key="rowKey" />
|
||||
</template>
|
||||
</Scroller>
|
||||
</template>
|
||||
|
||||
<template v-slot:modals>
|
||||
<ToolHelpModal />
|
||||
</template>
|
||||
</Layout>
|
||||
</SelectedItems>
|
||||
</ExpandedItems>
|
||||
</HistoryContentProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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";
|
||||
import HistoryEmpty from "./HistoryEmpty";
|
||||
import ContentOperations from "./ContentOperations";
|
||||
import ToolHelpModal from "./ToolHelpModal";
|
||||
import Scroller from "./Scroller";
|
||||
import { HistoryContentItem } from "./ContentItem";
|
||||
import { reportPayload } from "components/providers/History/ContentProvider/helpers";
|
||||
import HistoryMenu from "./HistoryMenu";
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
reportPayload,
|
||||
},
|
||||
components: {
|
||||
HistoryContentProvider,
|
||||
Layout,
|
||||
HistoryMessages,
|
||||
HistoryDetails,
|
||||
HistoryEmpty,
|
||||
ContentOperations,
|
||||
ToolHelpModal,
|
||||
Scroller,
|
||||
HistoryContentItem,
|
||||
ExpandedItems,
|
||||
SelectedItems,
|
||||
HistoryMenu,
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
params: new SearchParams(),
|
||||
useItemSelection: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
historyId() {
|
||||
return this.history.id;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,151 +0,0 @@
|
||||
<!--
|
||||
History object editor. Change name, edit tags, description.
|
||||
Emit new History model object when done via .sync syntax
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section class="history-details d-flex" data-description="edit details">
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<h3 data-description="history name display">{{ historyName || "(History Name)" }}</h3>
|
||||
<h5 class="history-size">
|
||||
<span v-if="history.size">{{ history.size | niceFileSize }}</span>
|
||||
<span v-else v-localize>(empty)</span>
|
||||
</h5>
|
||||
|
||||
<!-- display title, annotation, tags -->
|
||||
<div v-if="!editing">
|
||||
<p data-description="history annotation" v-if="annotation" class="mt-1">{{ annotation }}</p>
|
||||
<StatelessTags class="mt-2 tags" :value="tags" :disabled="true" />
|
||||
</div>
|
||||
|
||||
<!-- edit form, change title, annotation, or tags -->
|
||||
<div v-else class="mt-3" @keydown.esc="revertAndCancel" data-description="edit form">
|
||||
<b-textarea
|
||||
class="mb-2"
|
||||
v-model="historyName"
|
||||
placeholder="History Name"
|
||||
trim
|
||||
max-rows="4"
|
||||
data-description="name input"></b-textarea>
|
||||
|
||||
<b-textarea
|
||||
class="mb-2"
|
||||
v-model="annotation"
|
||||
placeholder="Annotation (optional)"
|
||||
trim
|
||||
max-rows="4"
|
||||
data-description="annotation input"></b-textarea>
|
||||
|
||||
<StatelessTags class="mt-3 tags" v-model="tags" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditorMenu
|
||||
class="ml-3 flex-grow-0 d-flex flex-column"
|
||||
v-if="writable"
|
||||
model-name="History"
|
||||
:editing.sync="editing"
|
||||
:writable="writable"
|
||||
:valid="valid"
|
||||
:dirty="dirty"
|
||||
@save="save"
|
||||
@revert="revert" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { History } from "./model";
|
||||
import EditorMenu from "./EditorMenu";
|
||||
import { StatelessTags } from "components/Tags";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorMenu,
|
||||
StatelessTags,
|
||||
},
|
||||
filters: {
|
||||
niceFileSize(rawSize = 0) {
|
||||
return prettyBytes(rawSize);
|
||||
},
|
||||
},
|
||||
props: {
|
||||
history: { type: History, required: true },
|
||||
writable: { type: Boolean, required: false, default: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editing: false,
|
||||
tempHistory: this.history.clone(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
historyId() {
|
||||
return this.history.id;
|
||||
},
|
||||
isEditing() {
|
||||
return this.writable && this.editing;
|
||||
},
|
||||
valid() {
|
||||
return this.tempHistory.name.length > 0;
|
||||
},
|
||||
dirty() {
|
||||
return !this.tempHistory.equals(this.history);
|
||||
},
|
||||
annotation: {
|
||||
get() {
|
||||
return this.tempHistory.annotation;
|
||||
},
|
||||
set(annotation) {
|
||||
this.patchTempHistory({ annotation });
|
||||
},
|
||||
},
|
||||
historyName: {
|
||||
get() {
|
||||
return this.tempHistory.name;
|
||||
},
|
||||
set(name) {
|
||||
this.patchTempHistory({ name });
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
get() {
|
||||
return this.tempHistory.tags || [];
|
||||
},
|
||||
set(newTags) {
|
||||
this.patchTempHistory({ tags: newTags });
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
historyId(newId, oldId) {
|
||||
if (newId != oldId) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reset() {
|
||||
this.tempHistory = this.history.clone();
|
||||
},
|
||||
patchTempHistory(props = {}) {
|
||||
this.tempHistory = this.tempHistory.patch(props);
|
||||
},
|
||||
save() {
|
||||
if (this.valid) {
|
||||
this.$emit("update:history", this.tempHistory);
|
||||
this.editing = false;
|
||||
}
|
||||
},
|
||||
revert() {
|
||||
if (this.dirty) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
revertAndCancel() {
|
||||
this.revert();
|
||||
this.editing = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user