diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9afa42b97..c56122976 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,8 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 - run: pip install pre-commit - run: pre-commit --version - run: pre-commit install @@ -29,9 +29,9 @@ jobs: toxenv: [pylint, doc8, docs] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Install dependencies @@ -45,7 +45,7 @@ jobs: needs: pretest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: @@ -59,7 +59,7 @@ jobs: mv build .. cd ../../ - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Install dependencies @@ -92,9 +92,9 @@ jobs: env: PLATFORM: ${{ matrix.platform }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -104,7 +104,7 @@ jobs: - name: Test with tox (and all extra dependencies) run: tox -e py-all -- --color=no - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests @@ -123,10 +123,10 @@ jobs: env: PLATFORM: ${{ matrix.platform }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -138,7 +138,7 @@ jobs: run: tox -e algo -- tests/unittests/algo/long/${{ matrix.algo }} --color=no - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests @@ -150,9 +150,9 @@ jobs: needs: [pre-commit, pretest] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Install dependencies @@ -162,7 +162,7 @@ jobs: - name: Test with tox run: tox -e py -- --color=no - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests @@ -208,9 +208,9 @@ jobs: needs: [pre-commit, pretest] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Setup MongoDB @@ -236,7 +236,7 @@ jobs: - name: Test with tox run: tox -e mongodb -- --color=no - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: backward @@ -253,9 +253,9 @@ jobs: env: ORION_DB_TYPE: ${{ matrix.orion_db_type }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Setup MongoDB @@ -281,7 +281,7 @@ jobs: - name: Test with tox run: tox -e backward-compatibility -- --color=no - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: backward @@ -292,9 +292,9 @@ jobs: needs: [test, backward-compatibility, test-long-algos, mongodb, test_no_extras, test-dashboard-build] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Install dependencies @@ -322,6 +322,18 @@ jobs: if ( ls ${ORION_PREFIX}/orion-dashboard/build/static/js/main.*.js ); then true; else false; fi echo Check if frontend script can find dashboard build python -c "from orion.core.cli.frontend import get_dashboard_build_path; get_dashboard_build_path();" + + echo Check if frontend script can find dashboard build on sys.prefix/local + mkdir -p ${ORION_PREFIX}/local/ + mv ${ORION_PREFIX}/orion-dashboard ${ORION_PREFIX}/local/orion-dashboard + if ( [ -d "${ORION_PREFIX}/orion-dashboard" ] ); then false; else true; fi + if ( [ -d "${ORION_PREFIX}/local/orion-dashboard" ] ); then true; else false; fi + python -c "from orion.core.cli.frontend import get_dashboard_build_path; get_dashboard_build_path();" + echo Move build back to initial installation + mv ${ORION_PREFIX}/local/orion-dashboard ${ORION_PREFIX}/orion-dashboard + if ( [ -d "${ORION_PREFIX}/orion-dashboard" ] ); then true; else false; fi + if ( [ -d "${ORION_PREFIX}/local/orion-dashboard" ] ); then false; else true; fi + echo Clean-up pip uninstall -y orion echo Check if dashboard build is correctly removed @@ -376,9 +388,9 @@ jobs: env: ANACONDA_TOKEN: ${{ secrets.anaconda_token }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python 3.9 - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 - name: Build conda diff --git a/.github/workflows/dashboard-src-new-with-playwright.yml b/.github/workflows/dashboard-src-new-with-playwright.yml new file mode 100644 index 000000000..1324e9c01 --- /dev/null +++ b/.github/workflows/dashboard-src-new-with-playwright.yml @@ -0,0 +1,74 @@ +name: Playwright Tests + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + release: + types: [published] + workflow_dispatch: + +defaults: + run: + working-directory: dashboard/src + +jobs: + test-dashboard-src-with-playwright: + timeout-minutes: 60 + runs-on: ubuntu-latest + # Playwright requires Node.js 14 or higher. + strategy: + matrix: + node-version: [14.x, 16.x] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + # Launch Orion backend + + - name: Launch Orion server + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install Orion from local copy + run: python -m pip install ../../.[profet] + + - name: Setup MongoDB + uses: supercharge/mongodb-github-action@1.8.0 + with: + mongodb-version: 4.2 + mongodb-db: orion_dashboard_test + - name: Populate MongoDB + run: | + cd ../../ + python .github/workflows/orion/pickle_to_mongodb.py + cd dashboard/src/ + + - name: Start Orion backend + run: | + # Start Orion backend in repository root folder. + cd ../../ + mkdir -p gunicorn_tmp_dir + orion -vv serve -c .github/workflows/orion/orion_config_mongodb.yaml 2> orion-backend-${{ matrix.node-version }}.log & + cd dashboard/src/ + + - name: Install dependencies + run: yarn + # check files formatting using Carbon's `ci-check` script + - name: Check files formatting + run: yarn ci-check + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + + # Launch Orion frontend, needed by Playwright + - name: Build dashboard + run: yarn build + - name: Serve build + run: ./node_modules/.bin/serve -l 3000 build/ & + + - name: Run Playwright tests + run: yarn playwright test --project=chromium --reporter=line diff --git a/.github/workflows/dashboard-src.yml b/.github/workflows/dashboard-src.yml.deactivated similarity index 85% rename from .github/workflows/dashboard-src.yml rename to .github/workflows/dashboard-src.yml.deactivated index 97853021c..6220f2672 100644 --- a/.github/workflows/dashboard-src.yml +++ b/.github/workflows/dashboard-src.yml.deactivated @@ -38,13 +38,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - run: python -m pip install git+https://github.com/notoraptor/orion.git@feature/benchmark_webapi_rebased#egg=orion[profet] - - name: info about current directory - run: df . - - name: check filesystem type using df - run: df -Th - - name: check filesystem type using fstab - run: cat /etc/fstab + - name: Install Orion from local copy + run: python -m pip install ../../.[profet] - name: Setup MongoDB uses: supercharge/mongodb-github-action@1.8.0 @@ -69,7 +64,7 @@ jobs: # check files formatting using Carbon's `ci-check` script - run: yarn ci-check # Run tests - # NB: Tests are running in parallel by default, this may cause backend to receive too many requests in small time + # NB: Tests are running in parallel by default, this may cause backend to receive too many requests in few time # Option --runInBand allows running tests sequentially: https://jestjs.io/docs/cli#--runinband - run: yarn test --all --verbose --runInBand # Upload orion backend log. diff --git a/.github/workflows/orion/add_uncompleted_experiment.py b/.github/workflows/orion/add_uncompleted_experiment.py new file mode 100644 index 000000000..c3fe98ebf --- /dev/null +++ b/.github/workflows/orion/add_uncompleted_experiment.py @@ -0,0 +1,173 @@ +""" +Helper script to add an uncompleted experiment to +db_dashboard_full_with_uncompleted_experiments.pkl +built from a copy of db_dashboard_full.pkl +""" +from datetime import datetime, timedelta + +from orion.core.worker.trial import Trial +from orion.storage.base import setup_storage + +SUBMIT_TIME = datetime( + year=2000, + month=1, + day=1, + hour=10, + minute=0, + second=0, + microsecond=123456, +) + + +NEW_EXPERIMENT_DEFINITIONS = [ + # Default, max trial 200 + { + "name": "uncompleted_experiment", + "count": { + "completed": 40, + "new": 30, + "reserved": 25, + "suspended": 20, + "interrupted": 15, + "broken": 10, + }, + "max_trials": 200, + }, + # Max trial 0 + { + "name": "uncompleted_max_trials_0", + "count": { + "completed": 6, + "new": 5, + "reserved": 4, + "suspended": 3, + "interrupted": 2, + "broken": 1, + }, + "max_trials": 0, + }, + # Max trial infinite + { + "name": "uncompleted_max_trials_infinite", + "count": { + "completed": 6, + "new": 5, + "reserved": 4, + "suspended": 3, + "interrupted": 2, + "broken": 1, + }, + "max_trials": float("inf"), + }, + # Completed > max trials + { + "name": "uncompleted_max_trials_lt_completed_trials", + "count": { + "completed": 20, + "new": 5, + "reserved": 4, + "suspended": 3, + "interrupted": 2, + "broken": 1, + }, + "max_trials": 10, + }, + # No completed trials + { + "name": "uncompleted_no_completed_trials", + "count": { + "completed": 0, + "new": 5, + "reserved": 4, + "suspended": 3, + "interrupted": 2, + "broken": 1, + }, + "max_trials": 200, + }, +] + + +def main(): + # Get base experiment model from experiment 2-dim-shape-exp.1 read from db_dashboard_full.pkl + input_storage = setup_storage( + { + "database": { + "host": ".github/workflows/orion/db_dashboard_full.pkl", + "type": "pickleddb", + } + } + ) + (base_experiment,) = input_storage._db.read( + "experiments", {"name": "2-dim-shape-exp", "version": 1} + ) + del base_experiment["_id"] + base_experiment["metadata"]["datetime"] = SUBMIT_TIME + + storage = setup_storage( + { + "database": { + "host": ".github/workflows/orion/db_dashboard_full_with_uncompleted_experiments.pkl", + "type": "pickleddb", + } + } + ) + pickle_db = storage._db + + # Insert new experiments into db_dashboard_full_with_uncompleted_experiments.pkl + for new_exp_def in NEW_EXPERIMENT_DEFINITIONS: + new_experiment = base_experiment.copy() + new_experiment["name"] = new_exp_def["name"] + new_experiment["max_trials"] = new_exp_def["max_trials"] + + # Clean database if necessary. + for exp_found in pickle_db.read( + "experiments", {"name": new_experiment["name"]} + ): + nb_exps_deleted = pickle_db.remove("experiments", {"_id": exp_found["_id"]}) + nb_trials_deleted = pickle_db.remove( + "trials", {"experiment": exp_found["_id"]} + ) + print( + f"[{new_experiment['name']}] Deleted", + nb_exps_deleted, + "experiment(s),", + nb_trials_deleted, + "trial(s)", + ) + + # Write and get new experiment. + pickle_db.write( + "experiments", + new_experiment, + ) + (exp,) = pickle_db.read("experiments", {"name": new_experiment["name"]}) + + # Write new experiment trials. + x = {"name": "/x", "type": "real", "value": 0.0} + results = {"name": "obj", "type": "objective", "value": 0.0} + for status, count in sorted(new_exp_def["count"].items()): + for i in range(count): + trial_kwargs = dict( + experiment=exp["_id"], + params=[x], + status=status, + results=[results], + submit_time=SUBMIT_TIME, + ) + if status != "new": + trial_kwargs.update( + start_time=SUBMIT_TIME + timedelta(minutes=i), + ) + if status == "completed": + trial_kwargs.update( + end_time=SUBMIT_TIME + timedelta(minutes=(i + 2)), + ) + pickle_db.write("trials", Trial(**trial_kwargs).to_dict()) + x["value"] += 1 + results["value"] += 0.1 + print(f"[{new_experiment['name']}] written.") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/orion/db_dashboard_full_with_uncompleted_experiments.pkl b/.github/workflows/orion/db_dashboard_full_with_uncompleted_experiments.pkl new file mode 100644 index 000000000..c82c990ec Binary files /dev/null and b/.github/workflows/orion/db_dashboard_full_with_uncompleted_experiments.pkl differ diff --git a/.github/workflows/orion/pickle_to_mongodb.py b/.github/workflows/orion/pickle_to_mongodb.py index 32519c8e7..9e2bf99dd 100644 --- a/.github/workflows/orion/pickle_to_mongodb.py +++ b/.github/workflows/orion/pickle_to_mongodb.py @@ -7,7 +7,7 @@ def main(): storage = setup_storage( { "database": { - "host": ".github/workflows/orion/db_dashboard_full.pkl", + "host": ".github/workflows/orion/db_dashboard_full_with_uncompleted_experiments.pkl", "type": "pickleddb", } } @@ -18,6 +18,10 @@ def main(): with pickle_db.locked_database(write=False) as database: for collection_name in database._db.keys(): print(f"[{collection_name}]") + # Clean Mongo DB first + for element in mongo_db.read(collection_name): + mongo_db.remove(collection_name, {"_id": element["_id"]}) + # Fill Mongo DB data = database.read(collection_name) mongo_db.write(collection_name, data) print("Pickle to Mongo DB done.") diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16021bd57..04bc362c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: rev: v2.2.1 hooks: - id: codespell - args: ["--skip", "*.html,*.ipynb,dashboard/src/.yarn/**,dashboard/src/yarn.lock,dashboard/build/**,dashboard/src/src/__tests__/**", "--ignore-words-list=hist,wont"] + args: ["--skip", "*.html,*.ipynb,dashboard/src/.yarn/**,dashboard/src/yarn.lock,dashboard/build/**,dashboard/src/src/__tests__/**", "--ignore-words-list=hist,wont,ro"] - repo: https://github.com/PyCQA/flake8 rev: 5.0.4 hooks: @@ -24,7 +24,7 @@ repos: - --show-source - --statistics - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] diff --git a/README.rst b/README.rst index bc6002b4f..383042d9a 100644 --- a/README.rst +++ b/README.rst @@ -122,7 +122,7 @@ If you use Oríon for published work, please cite our work using the following b .. code-block:: bibtex - @software{xavier_bouthillier_2022_0_2_6, + @software{xavier_bouthillier_2023_0_2_7, author = {Xavier Bouthillier and Christos Tsirigotis and François Corneau-Tremblay and @@ -147,10 +147,10 @@ If you use Oríon for published work, please cite our work using the following b Pascal Lamblin and Christopher Beckham}, title = {{Epistimio/orion: Asynchronous Distributed Hyperparameter Optimization}}, - month = august, - year = 2022, + month = march, + year = 2023, publisher = {Zenodo}, - version = {v0.2.6, + version = {v0.2.7, doi = {10.5281/zenodo.3478592}, url = {https://doi.org/10.5281/zenodo.3478592} } diff --git a/ROADMAP.md b/ROADMAP.md index 81a591e06..893f8be90 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,8 +1,10 @@ # Roadmap -Last update August 18th, 2022 +Last update March 2nd, 2023 ## Next releases - Short-Term +### v0.2.8 + ### v0.2.7 ### v0.2.6 diff --git a/conda/ci_build.sh b/conda/ci_build.sh index f286d75ea..7b9e2e978 100755 --- a/conda/ci_build.sh +++ b/conda/ci_build.sh @@ -11,14 +11,15 @@ conda config --set channel_priority strict pip uninstall -y setuptools conda install -c anaconda setuptools +conda install conda-build conda update -q conda conda info -a conda install conda-build anaconda-client -conda build conda --python 3.7 -conda build conda --python 3.8 -conda build conda --python 3.9 +conda-build conda --python 3.7 +conda-build conda --python 3.8 +conda-build conda --python 3.9 # Conda 3.10 does not work because of a bug inside conda itself # conda build conda --python 3.10 diff --git a/dashboard/src/.gitignore b/dashboard/src/.gitignore index 7205a1ec3..07ebd1637 100644 --- a/dashboard/src/.gitignore +++ b/dashboard/src/.gitignore @@ -31,3 +31,6 @@ yarn-error.log* !.yarn/sdks !.yarn/versions !yarn.lock +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/dashboard/src/package.json b/dashboard/src/package.json index 44eadf6bf..a3a15bb19 100644 --- a/dashboard/src/package.json +++ b/dashboard/src/package.json @@ -32,6 +32,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.2.0", + "react-infinite-scroller": "^1.2.6", "react-plotly.js": "^2.5.1", "react-router-dom": "5.3.3", "react-scripts": "5.0.1", @@ -40,6 +41,7 @@ "devDependencies": { "@commitlint/cli": "17.0.1", "@commitlint/config-conventional": "7.6.0", + "@playwright/test": "^1.30.0", "@testing-library/dom": "^8.17.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", @@ -48,6 +50,7 @@ "lint-staged": "12.4.2", "prettier": "1.19.1", "sass": "1.52.1", + "serve": "^14.2.0", "wait-for-expect": "3.0.2" }, "resolutions": { diff --git a/dashboard/src/playwright.config.js b/dashboard/src/playwright.config.js new file mode 100644 index 000000000..bd962ec1f --- /dev/null +++ b/dashboard/src/playwright.config.js @@ -0,0 +1,107 @@ +// @ts-check +const { devices } = require('@playwright/test'); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + * @type {import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + testDir: './src/__tests__', + /* Maximum time one test can run for. */ + timeout: 180 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + retries: 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + }, + }, + + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}; + +module.exports = config; diff --git a/dashboard/src/src/__tests__/ExperimentNavBar.test.js b/dashboard/src/src/__tests__/ExperimentNavBar.test.js index 5884291b0..ec4b65234 100644 --- a/dashboard/src/src/__tests__/ExperimentNavBar.test.js +++ b/dashboard/src/src/__tests__/ExperimentNavBar.test.js @@ -1,105 +1,380 @@ -import React from 'react'; -import ExperimentNavBar from '../experiments/components/ExperimentNavBar'; -import { render, waitFor, screen } from '@testing-library/react'; -import { BackendContext } from '../experiments/BackendContext'; -import userEvent from '@testing-library/user-event'; - -// Since I updated dependencies in package.json, this seems necessary. -beforeEach(() => { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); -}); +import { test, expect } from '@playwright/test'; +import { StatusToProgress } from '../experiments/components/ExperimentStatusBar/ExperimentStatusBar'; -test('Check if experiments are loaded', async () => { - // ExperimentNavBar will be rendered with default value associated to BackendContext. - render(); - // `findByText` will wait for text to appear. - // This let time for ExperimentNavBar to load experiments. - expect(await screen.findByText(/2-dim-shape-exp/)).toBeInTheDocument(); - // Then, all other experiments should be already loaded. - expect(screen.queryByText(/4-dim-cat-shape-exp/)).toBeInTheDocument(); - expect(screen.queryByText(/2-dim-exp/)).toBeInTheDocument(); - expect(screen.queryByText(/3-dim-cat-shape-exp/)).toBeInTheDocument(); - expect(screen.queryByText(/random-rosenbrock/)).toBeInTheDocument(); - expect(screen.queryByText(/tpe-rosenbrock/)).toBeInTheDocument(); - expect(screen.queryByText(/hyperband-cifar10/)).toBeInTheDocument(); -}); +const PROGRESS_BAR_NAMES = [ + 'success', + 'suspended', + 'warning', + 'danger', + 'info', +]; -test('Check experiments display when backend call fails', async () => { - render( - - - +/** + * Check if a normal progress bar has expected non-null sub-bars + * in order given by statusDescendingOrder from longest to smallest. + * + * Any sub-bar non-mentioned in statusDescendingOrder + * is expected to have width zero. + * + * A "normal" progress bar is a bar for an experiment + * whose max_trials is non-infinite. + * + * @param row - container to find experiment name and bar + * @param name {string} - experiment name + * @param statusDescendingOrder {Array} - list of expected bar names + * (success, suspended, warning, danger or info). + * Expected bar names should have non-null width. + * Order of bar names should be from the longest to the smallest bar. + * @returns {Promise} + */ +async function checkNormalBars(row, name, statusDescendingOrder) { + // Check we find experiment name in row. + await expect(await row.getByText(name)).toHaveCount(1); + // Get progress bar. + const bar = await row.locator('.progress'); + const barWidths = {}; + const expectedNUllBars = []; + for (let barStatus of PROGRESS_BAR_NAMES) { + const subBar = await bar.locator(`.bg-${barStatus}`); + await expect(subBar).toHaveCount(1); + // Collect bar width. + barWidths[barStatus] = (await subBar.boundingBox()).width; + // Collect if bar must have width 0. + if (statusDescendingOrder.indexOf(barStatus) < 0) + expectedNUllBars.push(barStatus); + } + console.log( + `Testing bars: ${name}\nexpected: ${statusDescendingOrder.join(' >= ') || + 'no non-null bars'}, null bars: ${expectedNUllBars.join(', ') || + 'none'}, collected widths: ${PROGRESS_BAR_NAMES.map( + status => status + ': ' + barWidths[status] + ).join(', ')}` ); - // `findByText` will wait for text to appear. - // This let time for ExperimentNavBar to try backend call. - expect( - await screen.findByText(/No experiment available/) - ).toBeInTheDocument(); - // Then, we must not find any of expected experiments. - expect(screen.queryByText(/2-dim-shape-exp/)).toBeNull(); - expect(screen.queryByText(/4-dim-cat-shape-exp/)).toBeNull(); - expect(screen.queryByText(/2-dim-exp/)).toBeNull(); - expect(screen.queryByText(/3-dim-cat-shape-exp/)).toBeNull(); - expect(screen.queryByText(/random-rosenbrock/)).toBeNull(); - expect(screen.queryByText(/tpe-rosenbrock/)).toBeNull(); - expect(screen.queryByText(/hyperband-cifar10/)).toBeNull(); -}); + // Check bar widths. + if (statusDescendingOrder.length) { + // First expected bar must have width > 0. + expect(barWidths[statusDescendingOrder[0]]).toBeGreaterThan(0); + for (let i = 1; i < statusDescendingOrder.length; ++i) { + // Each following bar must be <= previous. + expect(barWidths[statusDescendingOrder[i - 1]]).toBeGreaterThanOrEqual( + barWidths[statusDescendingOrder[i]] + ); + expect(barWidths[statusDescendingOrder[i]]).toBeGreaterThan(0); + } + } + // Other bars must have width == 0. + for (let zeroStatus of expectedNUllBars) { + expect(barWidths[zeroStatus]).toBe(0); + } +} + +test.describe('Test experiment nav bar', () => { + test.beforeEach(async ({ page }) => { + // Set a hardcoded page size. + await page.setViewportSize({ width: 1920, height: 1080 }); + // Open Dashboard page. + await page.goto('localhost:3000'); + }); + + test('Test nav bar scrolling', async ({ page }) => { + // Get nav bar container and bounding box (x, y, width, height). + const navBar = await page.locator('.experiment-navbar'); + await expect(navBar).toHaveCount(1); + const navBarBox = await navBar.boundingBox(); + + // Get scrollable container and bounding box inside nav bar. + const scrollableContainer = await navBar.locator('.experiments-wrapper'); + const scrollableBox = await scrollableContainer.boundingBox(); + + // Check default loaded experiments. + const firstLoadedExperiments = await navBar.locator( + '.experiments-wrapper .experiment-cell span[title]' + ); + await firstLoadedExperiments.first().waitFor(); + // For given hardcoded page size, we should have 16 default loaded experiments. + await expect(firstLoadedExperiments).toHaveCount(16); + + // Get and check first and last of default loaded experiments. + const currentFirstLoadedExperiment = firstLoadedExperiments.first(); + const currentLastLoadedExperiment = firstLoadedExperiments.last(); + await expect(currentFirstLoadedExperiment).toHaveText('2-dim-exp'); + await expect(currentLastLoadedExperiment).toHaveText( + 'all_algos_webapi_AverageResult_EggHolder_1_2' + ); + // Get bounding boxes for first and last default loaded experiments. + const firstBox = await currentFirstLoadedExperiment.boundingBox(); + const lastBox = await currentLastLoadedExperiment.boundingBox(); + + // Check some values of collected bounding boxes. + console.log(navBarBox); + console.log(scrollableBox); + console.log(firstBox); + console.log(lastBox); + expect(navBarBox.y).toBe(48); + expect(navBarBox.height).toBe(1032); + expect(scrollableBox.y).toBe(48); + expect(scrollableBox.height).toBe(984); + expect(lastBox.y).toBeGreaterThan(1053); + /** + * We expect scrollable container to not be high enough to display + * all default loaded experiments. So, last default loaded experiment + * should be positioned after the end of scrollable bounding box + * vertically. + */ + expect(scrollableBox.y + scrollableBox.height).toBeLessThan(lastBox.y); + + /** + * Now, we want to scroll into scrollable container to trigger + * infinite scroll that should load supplementary experiments. + * To check that, we prepare a locator for next experiment to be loaded ... + */ + let nextExperiment = await navBar.getByText( + 'all_algos_webapi_AverageResult_EggHolder_2_1' + ); + // ... And we don't expect this experiment to be yet in the document. + await expect(nextExperiment).toHaveCount(0); + + // Then we scroll to the last default loaded experiment. + await currentLastLoadedExperiment.scrollIntoViewIfNeeded(); + + // We wait for next experiment to be loaded to appear. + await nextExperiment.waitFor(); + // And we check that this newly loaded experiment is indeed in document. + await expect(nextExperiment).toHaveCount(1); + + // Finally, we check number of loaded experiments after scrolling. + const newLoadedExperiments = await navBar.locator( + '.experiments-wrapper .experiment-cell span[title]' + ); + // For given hardcoded page size, we should not have 18 (16 + 2) experiments. + await expect(newLoadedExperiments).toHaveCount(18); + }); + + test('Check if experiments are loaded', async ({ page }) => { + const navBar = await page.locator('.experiment-navbar'); + // Wait for first experiment to appear. + // This let time for experiments to be loaded. + const firstExperiment = await navBar.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + await expect(firstExperiment).toHaveCount(1); + // Then, other experiments should be already loaded. + // NB: Due to scrolling, not all experiments are yet loaded. + await expect(await navBar.getByText(/4-dim-cat-shape-exp/)).toHaveCount(1); + await expect(await navBar.getByText(/2-dim-exp/)).toHaveCount(1); + await expect(await navBar.getByText(/3-dim-cat-shape-exp/)).toHaveCount(1); + await expect( + await navBar.getByText(/all_algos_webapi_AverageResult_Branin_0_0/) + ).toHaveCount(1); + await expect( + await navBar.getByText(/all_algos_webapi_AverageResult_Branin_2_1/) + ).toHaveCount(1); + await expect( + await navBar.getByText(/all_algos_webapi_AverageResult_EggHolder_1_2/) + ).toHaveCount(1); + }); -test('Check filter experiments with search field', async () => { - const experiments = [ - /2-dim-shape-exp/, - /4-dim-cat-shape-exp/, - /2-dim-exp/, - /3-dim-cat-shape-exp/, - /random-rosenbrock/, - /tpe-rosenbrock/, - /hyperband-cifar10/, - ]; - const checkExpectations = presences => { - for (let i = 0; i < presences.length; ++i) { - const shouldBePresent = presences[i]; - const domElement = screen.queryByText(experiments[i]); - if (shouldBePresent) expect(domElement).toBeInTheDocument(); - else expect(domElement).toBeNull(); + test('Check filter experiments with search field', async ({ page }) => { + const experiments = [ + /2-dim-shape-exp/, + /4-dim-cat-shape-exp/, + /2-dim-exp/, + /3-dim-cat-shape-exp/, + /random-rosenbrock/, + /all_algos_webapi_AverageResult_RosenBrock_0_1/, + /hyperband-cifar10/, + ]; + const checkExpectations = async (navBar, presences) => { + for (let i = 0; i < presences.length; ++i) { + const domElement = await navBar.getByText(experiments[i]); + await expect(domElement).toHaveCount(presences[i]); + } + }; + + // Get nav bar and wait for default experiments to be loaded. + const navBar = await page.locator('.experiment-navbar'); + const firstExperiment = await navBar.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + + const searchField = await page.getByPlaceholder('Search experiment'); + await expect(searchField).toHaveCount(1); + + let waiter; + + await searchField.type('random'); + await checkExpectations(navBar, [0, 0, 0, 0, 1, 0, 0]); + + await searchField.press('Control+A'); + await searchField.press('Backspace'); + await searchField.type('rosenbrock'); + // NB: random-rosenbrock won't be visible because + // it's in last loaded experiments, so we need to scroll a lot + // before seeing it. + await checkExpectations(navBar, [0, 0, 0, 0, 0, 1, 0]); + // Scroll until we find random-rosenbrock + while (true) { + let loadedExperiments = await navBar.locator( + '.experiments-wrapper .experiment-cell span[title]' + ); + await loadedExperiments.first().waitFor(); + await loadedExperiments.last().scrollIntoViewIfNeeded(); + let exp = await navBar.getByText(/random-rosenbrock/); + if ((await exp.count()) === 1) break; } - }; - const user = userEvent.setup(); - render(); - const searchField = await screen.findByPlaceholderText('Search experiment'); - expect(searchField).toBeInTheDocument(); - await user.type(searchField, 'random'); - await waitFor( - () => checkExpectations([0, 0, 0, 0, 1, 0, 0]), - global.CONFIG_WAIT_FOR_LONG - ); - await user.clear(searchField); - await user.type(searchField, 'rosenbrock'); - await waitFor( - () => checkExpectations([0, 0, 0, 0, 1, 1, 0]), - global.CONFIG_WAIT_FOR_LONG - ); - await user.clear(searchField); - await user.type(searchField, 'dim-cat'); - await waitFor( - () => checkExpectations([0, 1, 0, 1, 0, 0, 0]), - global.CONFIG_WAIT_FOR_LONG - ); - await user.clear(searchField); - await user.type(searchField, 'unknown experiment'); - await waitFor( - () => checkExpectations([0, 0, 0, 0, 0, 0, 0]), - global.CONFIG_WAIT_FOR_LONG - ); + // Noe we must find both + // random-rosenbrock and all_algos_webapi_AverageResult_RosenBrock_0_1 + await checkExpectations(navBar, [0, 0, 0, 0, 1, 1, 0]); + + await searchField.press('Control+A'); + await searchField.press('Backspace'); + await searchField.type('dim-cat'); + await checkExpectations(navBar, [0, 1, 0, 1, 0, 0, 0]); + + await searchField.press('Control+A'); + await searchField.press('Backspace'); + await searchField.type('unknown experiment'); + waiter = await navBar.getByText('No matching experiment'); + await waiter.waitFor(); + await expect(waiter).toHaveCount(1); + await checkExpectations(navBar, [0, 0, 0, 0, 0, 0, 0]); + }); + + test('Test small progress bar for experiment 2-dim-shape-exp', async ({ + page, + }) => { + const navBar = await page.locator('.experiment-navbar'); + // Wait for first experiment to appear. + // This let time for experiments to be loaded. + const firstExperiment = await navBar.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + await expect(firstExperiment).toHaveCount(1); + + /** Locate progress bar related to this experiment **/ + // Just check experiment element is indeed a span with experiment name as title + expect( + await firstExperiment.evaluate(node => node.tagName.toLowerCase()) + ).toBe('span'); + expect(await firstExperiment.evaluate(node => node.title)).toBe( + '2-dim-shape-exp' + ); + // Get experiment row. Span parent is cell, span parent's parent is row + const parent = await firstExperiment.locator('xpath=../..'); + expect(await parent.getAttribute('class')).toBe('bx--structured-list-row'); + // Get progress bar + const bar = await parent.locator('.progress'); + await expect(bar).toHaveCount(1); + // Make sure it's a small progress bar, not complete progress bar. + // Complete progress bar comes with grids to display supplementary info. + // Small progress bar does not have grid around. + // So, we must not find a grid inside experiment row. + await expect(await parent.locator('.bx--grid')).toHaveCount(0); + // Check sub-bars in progress bar. Experiment 2-dim-shape-exp should be fully completed. + // So, only success bar should have a width > 0. + const barSuccess = await bar.locator('.bg-success'); + const barSuspended = await bar.locator('.bg-suspended'); + const barWarning = await bar.locator('.bg-warning'); + const barDanger = await bar.locator('.bg-danger'); + const barInfo = await bar.locator('.bg-info'); + await expect(barSuccess).toHaveCount(1); + await expect(barSuspended).toHaveCount(1); + await expect(barWarning).toHaveCount(1); + await expect(barDanger).toHaveCount(1); + await expect(barInfo).toHaveCount(1); + await expect((await barSuccess.boundingBox()).width).toBeGreaterThan(40); + await expect((await barSuspended.boundingBox()).width).toBe(0); + await expect((await barWarning.boundingBox()).width).toBe(0); + await expect((await barDanger.boundingBox()).width).toBe(0); + await expect((await barInfo.boundingBox()).width).toBe(0); + }); + + test('Test small progress bar for uncompleted experiments', async ({ + page, + }) => { + // Get nav bar and wait for default experiments to be loaded. + const navBar = await page.locator('.experiment-navbar'); + const firstExperiment = await navBar.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + // Search uncompleted experiments + const searchField = await page.getByPlaceholder('Search experiment'); + await expect(searchField).toHaveCount(1); + await searchField.type('uncompleted'); + // Check we got expected experiments + const uncompletedExperiments = await navBar.locator( + '.bx--structured-list-tbody .bx--structured-list-row' + ); + await uncompletedExperiments.first().waitFor(); + await expect(uncompletedExperiments).toHaveCount(5); + const expectedNames = [ + 'uncompleted_experiment', + 'uncompleted_max_trials_0', + 'uncompleted_max_trials_infinite', + 'uncompleted_max_trials_lt_completed_trials', + 'uncompleted_no_completed_trials', + ]; + for (let i = 0; i < expectedNames.length; ++i) { + const row = uncompletedExperiments.nth(i); + await expect(await row.getByText(expectedNames[i])).toHaveCount(1); + } + // Check expected bars. + await checkNormalBars( + uncompletedExperiments.nth(0), + 'uncompleted_experiment', + [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ] + ); + await checkNormalBars( + uncompletedExperiments.nth(1), + 'uncompleted_max_trials_0', + [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ] + ); + await checkNormalBars( + uncompletedExperiments.nth(3), + 'uncompleted_max_trials_lt_completed_trials', + [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ] + ); + await checkNormalBars( + uncompletedExperiments.nth(4), + 'uncompleted_no_completed_trials', + [ + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ] + ); + // Check bar for experiment with max_trials infinite + const rowInfinite = uncompletedExperiments.nth(2); + await expect( + await rowInfinite.getByText('uncompleted_max_trials_infinite') + ).toHaveCount(1); + const barInfinite = await rowInfinite.locator('.progress'); + await expect(barInfinite).toHaveCount(1); + expect(await barInfinite.evaluate(node => node.title)).toBe( + 'N/A (max trials ∞)' + ); + await expect(await barInfinite.locator('.bg-success')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-suspended')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-warning')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-danger')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-info')).toHaveCount(0); + const subBarRunning = await barInfinite.locator('.bg-running'); + await expect(subBarRunning).toHaveCount(1); + await expect(subBarRunning).toHaveText(/^N\/A$/); + }); }); diff --git a/dashboard/src/src/__tests__/VisualizationsPage.test.js b/dashboard/src/src/__tests__/VisualizationsPage.test.js index ba2c69000..f16494d14 100644 --- a/dashboard/src/src/__tests__/VisualizationsPage.test.js +++ b/dashboard/src/src/__tests__/VisualizationsPage.test.js @@ -1,135 +1,115 @@ -import React from 'react'; -import App from '../App'; -import { render, fireEvent, waitFor, screen } from '@testing-library/react'; -/* Use MemoryRouter to isolate history for each test */ -import { MemoryRouter } from 'react-router-dom'; +import { test, expect } from '@playwright/test'; -// Since I updated dependencies in package.json, this seems necessary. -beforeEach(() => { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), +test.describe('Test experiments visualization page', () => { + test.beforeEach(async ({ page }) => { + // Set a hardcoded page size. + await page.setViewportSize({ width: 1920, height: 1080 }); + // Open Dashboard page. + await page.goto('localhost:3000'); }); -}); -test('Test if we switch to visualization page', async () => { - // Load page - render(, { wrapper: MemoryRouter }); - // Let time for ExperimentNavBar to load experiments - // to prevent warnings about async calls not terminated - expect( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ).toBeInTheDocument(); + test('Test if we switch to visualization page', async ({ page }) => { + // Let time for ExperimentNavBar to load experiments + const firstExperiment = await page.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + await expect(firstExperiment).toHaveCount(1); - // Make sure we are on default (landing) page - expect(screen.queryByText(/Landing Page/)).toBeInTheDocument(); + // Make sure we are on default (landing) page + await expect(await page.getByText(/Landing Page/)).toHaveCount(1); - // Make sure we are not on visualizations page - expect(screen.queryByText(/Nothing to display/)).toBeNull(); + // Make sure we are not on visualizations page + await expect(await page.getByText(/Nothing to display/)).toHaveCount(0); - // Get visualizations page link - const menu = screen.queryByTitle(/Go to experiments visualizations/); - expect(menu).toBeInTheDocument(); + // Go to visualization page - // CLick on visualizations page link - fireEvent.click(menu); + const menuExperiments = await page.locator('nav > ul > li:nth-child(1)'); + await expect(menuExperiments).toHaveCount(1); + await expect(menuExperiments).toBeVisible(); + await menuExperiments.click(); + const menu = await menuExperiments.getByTitle( + /Go to experiments visualizations/ + ); + await expect(menu).toHaveCount(1); + await expect(menu).toBeVisible(); + await menu.click(); - // Check we are on visualizations page - const elements = await screen.findAllByText(/Nothing to display/); - expect(elements.length).toBe(3); -}); + // Check we are on visualizations page + const elements = await page.getByText(/Nothing to display/); + await expect(elements).toHaveCount(3); + }); -test('Test if we can select and unselect experiments', async () => { - // Load page - render(, { wrapper: MemoryRouter }); - const experiment = await screen.findByText( - /2-dim-shape-exp/, - {}, - global.CONFIG_WAIT_FOR_LONG - ); - expect(experiment).toBeInTheDocument(); + test('Test if we can select and unselect experiments', async ({ page }) => { + const firstExperiment = await page.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); - // Switch to visualizations page - const menu = screen.queryByTitle(/Go to experiments visualizations/); - fireEvent.click(menu); - expect((await screen.findAllByText(/Nothing to display/)).length).toBe(3); + // Go to visualization page + const menuExperiments = await page.locator('nav > ul > li:nth-child(1)'); + await menuExperiments.click(); + const menu = await menuExperiments.getByTitle( + /Go to experiments visualizations/ + ); + await menu.click(); + // Check we are on visualizations page + await expect(await page.getByText(/Nothing to display/)).toHaveCount(3); - // Select an experiment - expect(experiment).toBeInTheDocument(); - fireEvent.click(experiment); + // Select an experiment + await firstExperiment.click(); - // Check if plots are loaded - // Wait enough (3 seconds) to let plots load - await waitFor(() => { - expect( - screen.queryByText(/Regret for experiment '2-dim-shape-exp'/) - ).toBeInTheDocument(); - }, global.CONFIG_WAIT_FOR_LONG); - expect( - await screen.findByText( - /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i - ) - ).toBeInTheDocument(); - expect( - await screen.findByText(/LPI for experiment '2-dim-shape-exp'/) - ).toBeInTheDocument(); + // Check if plots are loaded + for (let plotTitle of [ + /Regret for experiment '2-dim-shape-exp'/, + /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i, + /LPI for experiment '2-dim-shape-exp'/, + ]) { + const plot = await page.getByText(plotTitle); + await plot.waitFor(); + await expect(plot).toHaveCount(1); + } - // Unselect experiment - const row = screen.queryByTitle(/unselect experiment '2-dim-shape-exp'/); - expect(row).toBeInTheDocument(); - expect(row.tagName.toLowerCase()).toBe('label'); - fireEvent.click(row); - expect((await screen.findAllByText(/Nothing to display/)).length).toBe(3); - expect( - screen.queryByText(/Regret for experiment '2-dim-shape-exp'/) - ).toBeNull(); - expect( - screen.queryByText( - /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i - ) - ).toBeNull(); - expect(screen.queryByText(/LPI for experiment '2-dim-shape-exp'/)).toBeNull(); + // Unselect experiment + const row = await page.getByTitle(/unselect experiment '2-dim-shape-exp'/); + await expect(row).toHaveCount(1); + expect(await row.evaluate(node => node.tagName.toLowerCase())).toBe( + 'label' + ); + await row.click(); - // re-select experiment and check if plots are loaded - fireEvent.click(experiment); - await waitFor(() => { - expect( - screen.queryByText(/Regret for experiment '2-dim-shape-exp'/) - ).toBeInTheDocument(); - }, global.CONFIG_WAIT_FOR_LONG); - expect( - await screen.findByText( - /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i - ) - ).toBeInTheDocument(); - expect( - await screen.findByText(/LPI for experiment '2-dim-shape-exp'/) - ).toBeInTheDocument(); + await expect(await page.getByText(/Nothing to display/)).toHaveCount(3); + for (let plotTitle of [ + /Regret for experiment '2-dim-shape-exp'/, + /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i, + /LPI for experiment '2-dim-shape-exp'/, + ]) { + const plot = await page.getByText(plotTitle); + await expect(plot).toHaveCount(0); + } - // Select another experiment and check if plots are loaded - const anotherExperiment = await screen.findByText(/tpe-rosenbrock/); - expect(anotherExperiment).toBeInTheDocument(); - fireEvent.click(anotherExperiment); - await waitFor(() => { - expect( - screen.queryByText(/Regret for experiment 'tpe-rosenbrock'/) - ).toBeInTheDocument(); - }, global.CONFIG_WAIT_FOR_LONG); - expect( - await screen.findByText( - /Parallel Coordinates PLot for experiment 'tpe-rosenbrock'/i - ) - ).toBeInTheDocument(); - expect( - await screen.findByText(/LPI for experiment 'tpe-rosenbrock'/) - ).toBeInTheDocument(); + // re-select experiment and check if plots are loaded + await firstExperiment.click(); + for (let plotTitle of [ + /Regret for experiment '2-dim-shape-exp'/, + /Parallel Coordinates PLot for experiment '2-dim-shape-exp'/i, + /LPI for experiment '2-dim-shape-exp'/, + ]) { + const plot = await page.getByText(plotTitle); + await plot.waitFor(); + await expect(plot).toHaveCount(1); + } + + // Select another experiment and check if plots are loaded + const searchField = await page.getByPlaceholder('Search experiment'); + await searchField.type('tpe-rosenbrock'); + const anotherExperiment = await page.getByText(/tpe-rosenbrock/); + await expect(anotherExperiment).toHaveCount(1); + await anotherExperiment.click(); + for (let plotTitle of [ + /Regret for experiment 'tpe-rosenbrock'/, + /Parallel Coordinates PLot for experiment 'tpe-rosenbrock'/i, + /LPI for experiment 'tpe-rosenbrock'/, + ]) { + const plot = await page.getByText(plotTitle); + await plot.waitFor(); + await expect(plot).toHaveCount(1); + } + }); }); diff --git a/dashboard/src/src/__tests__/benchmark.test.js b/dashboard/src/src/__tests__/benchmark.test.js index 4bd8bf303..8b3256030 100644 --- a/dashboard/src/src/__tests__/benchmark.test.js +++ b/dashboard/src/src/__tests__/benchmark.test.js @@ -1,71 +1,45 @@ -import React from 'react'; -import App from '../App'; -import { - render, - waitFor, - queryByText, - findByText, - screen, - fireEvent, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -/* Use MemoryRouter to isolate history for each test */ -import { MemoryRouter } from 'react-router-dom'; - -// Since I updated dependencies in package.json, this seems necessary. -beforeEach(() => { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); -}); +import { test, expect } from '@playwright/test'; +import { performance } from 'perf_hooks'; /** * Return true if given DOM plot element contains all given texts. - * @param plot - DOM element containing a plot + * @param plot - locator of DOM element containing a plot * @param texts - texts (strings) to search * @return {boolean} - true if plot contains all texts */ -function plotHasTexts(plot, texts) { +async function plotHasTexts(plot, texts) { for (let text of texts) { - if (queryByText(plot, text) === null) return false; + const textFinder = await plot.getByText(text); + const count = await textFinder.count(); + if (count === 0) return false; } return true; } /** - * Check immediately (no async) that we find only 1 plot containing all given texts. + * Check that we find only 1 plot containing all given texts. + * @param page - page object + * @param plotId - plot ID * @param texts - texts to search - * @return {boolean} - true if plot is found */ -function hasPlotImmediately(...texts) { - const plots = document.querySelectorAll('.orion-plot'); - const filtered = []; - for (let plot of plots.values()) { - if (plotHasTexts(plot, texts)) { - filtered.push(plot); - } - } - return filtered.length === 1; +async function lookupPlotById(page, plotId, ...texts) { + const plot = await page.locator(`#${plotId}`); + await plot.waitFor({ timeout: 120000 }); + expect(await plotHasTexts(plot, texts)).toBe(true); } /** - * Check that we find only 1 plot containing all given texts. + * Check immediately (no waiting) that we find a plot + * @param page - page object + * @param plotId - plot ID * @param texts - texts to search + * @return {boolean} - true if plot is found */ -async function lookupPlot(...texts) { - await waitFor(() => { - expect(hasPlotImmediately(...texts)).toBe(true); - }, global.CONFIG_WAIT_FOR_LONG); +async function hasPlotByIdAndTextsImmediately(page, plotId, ...texts) { + const plot = await page.locator(`#${plotId}`); + const count = await plot.count(); + if (count === 0) return false; + return await plotHasTexts(plot, texts); } /** @@ -218,57 +192,177 @@ const AAWA2PlotsNoRandom = { ], }; -const AAWA2_all = [ - AAWA2Plots.average_rank_branin, - AAWA2Plots.average_rank_rosenbrock, - AAWA2Plots.average_result_branin, - AAWA2Plots.average_result_rosenbrock, - AAWA2Plots.parallel_assessment_time_branin, - AAWA2Plots.parallel_assessment_time_rosenbrock, - AAWA2Plots.parallel_assessment_pa_branin, - AAWA2Plots.parallel_assessment_pa_rosenbrock, - AAWA2Plots.parallel_assessment_regret_branin, - AAWA2Plots.parallel_assessment_regret_rosenbrock, +const AAWA2_all_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageRank-Branin-rankings-random-tpe', + AAWA2Plots.average_rank_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageRank-RosenBrock-rankings-random-tpe', + AAWA2Plots.average_rank_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-Branin-regrets-random-tpe', + AAWA2Plots.average_result_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-RosenBrock-regrets-random-tpe', + AAWA2Plots.average_result_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_rosenbrock, + ], ]; -const AAWA2_average_rank = [ - AAWA2Plots.average_rank_branin, - AAWA2Plots.average_rank_rosenbrock, +const AAWA2_average_rank_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageRank-Branin-rankings-random-tpe', + AAWA2Plots.average_rank_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageRank-RosenBrock-rankings-random-tpe', + AAWA2Plots.average_rank_rosenbrock, + ], ]; -const AAWA2_no_average_rank = [ - AAWA2Plots.average_result_branin, - AAWA2Plots.average_result_rosenbrock, - AAWA2Plots.parallel_assessment_time_branin, - AAWA2Plots.parallel_assessment_time_rosenbrock, - AAWA2Plots.parallel_assessment_pa_branin, - AAWA2Plots.parallel_assessment_pa_rosenbrock, - AAWA2Plots.parallel_assessment_regret_branin, - AAWA2Plots.parallel_assessment_regret_rosenbrock, +const AAWA2_no_average_rank_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageResult-Branin-regrets-random-tpe', + AAWA2Plots.average_result_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-RosenBrock-regrets-random-tpe', + AAWA2Plots.average_result_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_rosenbrock, + ], ]; -const AAWA2_branin = [ - AAWA2Plots.average_rank_branin, - AAWA2Plots.average_result_branin, - AAWA2Plots.parallel_assessment_time_branin, - AAWA2Plots.parallel_assessment_pa_branin, - AAWA2Plots.parallel_assessment_regret_branin, +const AAWA2_branin_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageRank-Branin-rankings-random-tpe', + AAWA2Plots.average_rank_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-Branin-regrets-random-tpe', + AAWA2Plots.average_result_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_branin, + ], ]; -const AAWA2_no_branin = [ - AAWA2Plots.average_rank_rosenbrock, - AAWA2Plots.average_result_rosenbrock, - AAWA2Plots.parallel_assessment_time_rosenbrock, - AAWA2Plots.parallel_assessment_pa_rosenbrock, - AAWA2Plots.parallel_assessment_regret_rosenbrock, +const AAWA2_no_branin_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageRank-RosenBrock-rankings-random-tpe', + AAWA2Plots.average_rank_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-RosenBrock-regrets-random-tpe', + AAWA2Plots.average_result_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-durations-random-tpe', + AAWA2Plots.parallel_assessment_time_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-parallel_assessment-random-tpe', + AAWA2Plots.parallel_assessment_pa_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-regrets-random-tpe', + AAWA2Plots.parallel_assessment_regret_rosenbrock, + ], ]; -const AAWA2_no_random = [ - AAWA2PlotsNoRandom.average_rank_branin, - AAWA2PlotsNoRandom.average_rank_rosenbrock, - AAWA2PlotsNoRandom.average_result_branin, - AAWA2PlotsNoRandom.average_result_rosenbrock, - AAWA2PlotsNoRandom.parallel_assessment_time_branin, - AAWA2PlotsNoRandom.parallel_assessment_time_rosenbrock, - AAWA2PlotsNoRandom.parallel_assessment_pa_branin, - AAWA2PlotsNoRandom.parallel_assessment_pa_rosenbrock, - AAWA2PlotsNoRandom.parallel_assessment_regret_branin, - AAWA2PlotsNoRandom.parallel_assessment_regret_rosenbrock, +const AAWA2_no_random_with_id = [ + [ + 'plot-all_assessments_webapi_2-AverageRank-Branin-rankings-tpe', + AAWA2PlotsNoRandom.average_rank_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageRank-RosenBrock-rankings-tpe', + AAWA2PlotsNoRandom.average_rank_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-Branin-regrets-tpe', + AAWA2PlotsNoRandom.average_result_branin, + ], + [ + 'plot-all_assessments_webapi_2-AverageResult-RosenBrock-regrets-tpe', + AAWA2PlotsNoRandom.average_result_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-durations-tpe', + AAWA2PlotsNoRandom.parallel_assessment_time_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-durations-tpe', + AAWA2PlotsNoRandom.parallel_assessment_time_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-parallel_assessment-tpe', + AAWA2PlotsNoRandom.parallel_assessment_pa_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-parallel_assessment-tpe', + AAWA2PlotsNoRandom.parallel_assessment_pa_rosenbrock, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-Branin-regrets-tpe', + AAWA2PlotsNoRandom.parallel_assessment_regret_branin, + ], + [ + 'plot-all_assessments_webapi_2-ParallelAssessment-RosenBrock-regrets-tpe', + AAWA2PlotsNoRandom.parallel_assessment_regret_rosenbrock, + ], ]; test('Test sleep', async () => { @@ -301,245 +395,297 @@ test('Test sleep', async () => { expect(diff).toBeLessThan(21000); }); -test('Test select benchmark', async () => { - const user = userEvent.setup(); - render(, { wrapper: MemoryRouter }); - - // Switch to benchmarks page - const menu = await screen.findByTitle(/Go to benchmarks visualizations/); - fireEvent.click(menu); - expect( - await screen.findByText( - /No benchmark selected/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - // Get benchmark search field - const benchmarkField = await screen.findByPlaceholderText( - 'Search a benchmark ...' - ); - expect(benchmarkField).toBeInTheDocument(); - - // Select branin_baselines_webapi benchmark - await user.type(benchmarkField, 'branin'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('branin_baselines_webapi'); - const leftMenu = document.querySelector('.bx--structured-list'); - expect(leftMenu).toBeInTheDocument(); - expect(await findByText(leftMenu, /AverageResult/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /Branin/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /gridsearch/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /random/)).toBeInTheDocument(); - expect(screen.queryByText(/No benchmark selected/)).toBeNull(); - // Check plot - await lookupPlot( - 'Average Regret', - 'branin', - 'Trials ordered by suggested time' - ); - - // Select all_algos_webapi benchmark - // Use backspace to clear field before typing new hint - await user.type(benchmarkField, '{Backspace>50/}all_algos'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('all_algos_webapi'); - expect(await findByText(leftMenu, /AverageResult/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /Branin/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /EggHolder/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /RosenBrock/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /gridsearch/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /random/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /tpe/)).toBeInTheDocument(); - expect(screen.queryByText(/No benchmark selected/)).toBeNull(); - // Check plots - await lookupPlot('Average Regret', 'branin'); - await lookupPlot('Average Regret', 'eggholder'); - await lookupPlot('Average Regret', 'rosenbrock'); - - // Select all_assessments_webapi_2 - await user.type(benchmarkField, '{Backspace>50/}all_asses'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('all_assessments_webapi_2'); - expect(await findByText(leftMenu, /AverageRank/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /AverageResult/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /ParallelAssessment/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /Branin/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /RosenBrock/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /random/)).toBeInTheDocument(); - expect(await findByText(leftMenu, /tpe/)).toBeInTheDocument(); - expect(screen.queryByText(/No benchmark selected/)).toBeNull(); - // Check plots - // Assessment AverageRank - await lookupPlot(...AAWA2Plots.average_rank_branin); - await lookupPlot(...AAWA2Plots.average_rank_rosenbrock); - // Assessment AverageResult - await lookupPlot(...AAWA2Plots.average_result_branin); - await lookupPlot(...AAWA2Plots.average_result_rosenbrock); - // Assessment ParallelAssessment (which also have regret plots as AverageResult) - await lookupPlot(...AAWA2Plots.parallel_assessment_time_branin); - await lookupPlot(...AAWA2Plots.parallel_assessment_time_rosenbrock); - await lookupPlot(...AAWA2Plots.parallel_assessment_pa_branin); - await lookupPlot(...AAWA2Plots.parallel_assessment_pa_rosenbrock); - await lookupPlot(...AAWA2Plots.parallel_assessment_regret_branin); - await lookupPlot(...AAWA2Plots.parallel_assessment_regret_rosenbrock); -}); +test.describe('Test benchmark dashboard', () => { + test.beforeEach(async ({ page }) => { + // Set a hardcoded page size. + await page.setViewportSize({ width: 1920, height: 1080 }); + // Open Dashboard page. + await page.goto('localhost:3000'); + }); -test('Test (de)select assessments', async () => { - const user = userEvent.setup(); - render(, { wrapper: MemoryRouter }); - - // Switch to benchmarks page - const menu = await screen.findByTitle(/Go to benchmarks visualizations/); - fireEvent.click(menu); - expect( - await screen.findByText( - /No benchmark selected/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - // Get benchmark search field - const benchmarkField = await screen.findByPlaceholderText( - 'Search a benchmark ...' - ); - expect(benchmarkField).toBeInTheDocument(); - // Select all_assessments_webapi_2 - await user.type(benchmarkField, 'all_asses'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('all_assessments_webapi_2'); - - // Make sure all plots are there (10 plots) - for (let texts of AAWA2_all) { - await lookupPlot(...texts); - } + test('Test select benchmark', async ({ page }) => { + // Check we are on home page and not benchmarks page + await expect(await page.getByText(/Landing Page/)).toHaveCount(1); + await expect(await page.getByText(/No benchmark selected/)).toHaveCount(0); - // Select 1 assessment. - const inputAssessmentAverageRank = document.getElementById('assessment-0'); - expect(inputAssessmentAverageRank).toBeInTheDocument(); - expect(inputAssessmentAverageRank.checked).toBe(true); + // Switch to benchmarks page + const menuBenchmark = await page.locator('nav > ul > li:nth-child(2)'); + await expect(menuBenchmark).toHaveCount(1); + await expect(menuBenchmark).toBeVisible(); + await menuBenchmark.click(); + const menu = await menuBenchmark.getByTitle( + /Go to benchmarks visualizations/ + ); + await expect(menu).toHaveCount(1); + await expect(menu).toBeVisible(); + await menu.click(); - // Deselect assessment - await user.click(inputAssessmentAverageRank); - expect(inputAssessmentAverageRank.checked).toBe(false); - await sleep(1000); - for (let texts of AAWA2_average_rank) { - expect(hasPlotImmediately(...texts)).toBe(false); - } - for (let texts of AAWA2_no_average_rank) { - expect(hasPlotImmediately(...texts)).toBe(true); - } - // Reselect assessment. - await user.click(inputAssessmentAverageRank); - expect(inputAssessmentAverageRank.checked).toBe(true); - await sleep(1000); - for (let texts of AAWA2_all) { - expect(hasPlotImmediately(...texts)).toBe(true); - } -}); + // Check we are in benchmarks page and not to home page anymore + const benchmarksPlaceholder = await page.getByText(/No benchmark selected/); + await benchmarksPlaceholder.waitFor(); + await expect(await page.getByText(/Landing Page/)).toHaveCount(0); + await expect(benchmarksPlaceholder).toHaveCount(1); -test('Test (de)select tasks', async () => { - const user = userEvent.setup(); - render(, { wrapper: MemoryRouter }); - - // Switch to benchmarks page - const menu = await screen.findByTitle(/Go to benchmarks visualizations/); - fireEvent.click(menu); - expect( - await screen.findByText( - /No benchmark selected/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - // Get benchmark search field - const benchmarkField = await screen.findByPlaceholderText( - 'Search a benchmark ...' - ); - expect(benchmarkField).toBeInTheDocument(); - // Select all_assessments_webapi_2 - await user.type(benchmarkField, 'all_asses'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('all_assessments_webapi_2'); - - // Make sure all plots are there (10 plots) - for (let texts of AAWA2_all) { - await lookupPlot(...texts); - } + // Get benchmark search field + const benchmarkField = await page.getByPlaceholder( + 'Search a benchmark ...' + ); + await expect(benchmarkField).toHaveCount(1); - // Select 1 task. - const inputTaskBranin = document.getElementById('task-0'); - expect(inputTaskBranin).toBeInTheDocument(); - expect(inputTaskBranin.checked).toBe(true); + // Select branin_baselines_webapi benchmark + await benchmarkField.type('branin'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe( + 'branin_baselines_webapi' + ); + const leftMenu = await page.locator('.bx--structured-list'); + await expect(leftMenu).toHaveCount(1); + await expect(await leftMenu.getByText(/AverageResult/)).toHaveCount(1); + await expect(await leftMenu.getByText(/Branin/)).toHaveCount(1); + await expect(await leftMenu.getByText(/gridsearch/)).toHaveCount(1); + await expect(await leftMenu.getByText(/random/)).toHaveCount(1); + await expect(await page.getByText(/No benchmark selected/)).toHaveCount(0); + // Check plot + await lookupPlotById( + page, + 'plot-branin_baselines_webapi-AverageResult-Branin-regrets-gridsearch-random', + 'Average Regret', + 'branin', + 'Trials ordered by suggested time' + ); - // Deselect task. - await user.click(inputTaskBranin); - expect(inputTaskBranin.checked).toBe(false); - await sleep(1000); - for (let texts of AAWA2_branin) { - expect(hasPlotImmediately(...texts)).toBe(false); - } - for (let texts of AAWA2_no_branin) { - expect(hasPlotImmediately(...texts)).toBe(true); - } - // Reselect task. - await user.click(inputTaskBranin); - expect(inputTaskBranin.checked).toBe(true); - await sleep(1000); - for (let texts of AAWA2_all) { - expect(hasPlotImmediately(...texts)).toBe(true); - } -}); + // Select all_algos_webapi benchmark + // Use Ctrl+A then backspace to clear field before typing new hint + await benchmarkField.press('Control+A'); + await benchmarkField.press('Backspace'); + expect(await benchmarkField.getAttribute('value')).toBe(''); + await benchmarkField.type('all_algos'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe('all_algos_webapi'); + await expect(await leftMenu.getByText(/AverageResult/)).toHaveCount(1); + await expect(await leftMenu.getByText(/Branin/)).toHaveCount(1); + await expect(await leftMenu.getByText(/EggHolder/)).toHaveCount(1); + await expect(await leftMenu.getByText(/RosenBrock/)).toHaveCount(1); + await expect(await leftMenu.getByText(/gridsearch/)).toHaveCount(1); + await expect(await leftMenu.getByText(/random/)).toHaveCount(1); + await expect(await leftMenu.getByText(/tpe/)).toHaveCount(1); + await expect(await page.getByText(/No benchmark selected/)).toHaveCount(0); + // Check plots + await lookupPlotById( + page, + 'plot-all_algos_webapi-AverageResult-Branin-regrets-gridsearch-random-tpe', + 'Average Regret', + 'branin' + ); + await lookupPlotById( + page, + 'plot-all_algos_webapi-AverageResult-EggHolder-regrets-gridsearch-random-tpe', + 'Average Regret', + 'eggholder' + ); + await lookupPlotById( + page, + 'plot-all_algos_webapi-AverageResult-RosenBrock-regrets-gridsearch-random-tpe', + 'Average Regret', + 'rosenbrock' + ); -test('Test (de)select algorithms', async () => { - const user = userEvent.setup(); - render(, { wrapper: MemoryRouter }); - - // Switch to benchmarks page - const menu = await screen.findByTitle(/Go to benchmarks visualizations/); - fireEvent.click(menu); - expect( - await screen.findByText( - /No benchmark selected/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - // Get benchmark search field - const benchmarkField = await screen.findByPlaceholderText( - 'Search a benchmark ...' - ); - expect(benchmarkField).toBeInTheDocument(); - // Select all_assessments_webapi_2 - await user.type(benchmarkField, 'all_asses'); - await user.keyboard('{enter}'); - expect(benchmarkField.value).toBe('all_assessments_webapi_2'); - - // Make sure all plots are there (10 plots) - for (let texts of AAWA2_all) { - await lookupPlot(...texts); - } + // Select all_assessments_webapi_2 + await benchmarkField.press('Control+A'); + await benchmarkField.press('Backspace'); + expect(await benchmarkField.getAttribute('value')).toBe(''); + await benchmarkField.type('all_asses'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe( + 'all_assessments_webapi_2' + ); + await expect(await leftMenu.getByText(/AverageRank/)).toHaveCount(1); + await expect(await leftMenu.getByText(/AverageResult/)).toHaveCount(1); + await expect(await leftMenu.getByText(/ParallelAssessment/)).toHaveCount(1); + await expect(await leftMenu.getByText(/Branin/)).toHaveCount(1); + await expect(await leftMenu.getByText(/RosenBrock/)).toHaveCount(1); + await expect(await leftMenu.getByText(/random/)).toHaveCount(1); + await expect(await leftMenu.getByText(/tpe/)).toHaveCount(1); + await expect(await page.getByText(/No benchmark selected/)).toHaveCount(0); + // Check plots + for (let [plotId, texts] of AAWA2_all_with_id) { + await lookupPlotById(page, plotId, ...texts); + } + }); - // Select 1 algorithm. - const inputAlgorithmRandom = document.getElementById('algorithm-0'); - expect(inputAlgorithmRandom).toBeInTheDocument(); - expect(inputAlgorithmRandom.checked).toBe(true); + test('Test (de)select assessments', async ({ page }) => { + // Switch to benchmarks page + const menuBenchmark = await page.locator('nav > ul > li:nth-child(2)'); + await menuBenchmark.click(); + const menu = await menuBenchmark.getByTitle( + /Go to benchmarks visualizations/ + ); + await menu.click(); + const benchmarksPlaceholder = await page.getByText(/No benchmark selected/); + await benchmarksPlaceholder.waitFor(); + await expect(benchmarksPlaceholder).toHaveCount(1); - // Deselect algorithm. - await user.click(inputAlgorithmRandom); - expect(inputAlgorithmRandom.checked).toBe(false); - await sleep(1000); - for (let texts of AAWA2_all) { - expect(hasPlotImmediately(...texts)).toBe(false); - } - for (let textsNoRandom of AAWA2_no_random) { - expect(hasPlotImmediately(...textsNoRandom)).toBe(true); - } - // Reselect algorithm. - await user.click(inputAlgorithmRandom); - expect(inputAlgorithmRandom.checked).toBe(true); - await sleep(1000); - for (let texts of AAWA2_all) { - expect(hasPlotImmediately(...texts)).toBe(true); - } + // Get benchmark search field + const benchmarkField = await page.getByPlaceholder( + 'Search a benchmark ...' + ); + // Select all_assessments_webapi_2 + await benchmarkField.type('all_asses'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe( + 'all_assessments_webapi_2' + ); + + // Make sure all plots are there (10 plots) + for (let [plotId, texts] of AAWA2_all_with_id) { + await lookupPlotById(page, plotId, ...texts); + } + + // Select 1 assessment. + const inputAssessmentAverageRank = await page.locator('#assessment-0'); + await expect(inputAssessmentAverageRank).toHaveCount(1); + await expect(inputAssessmentAverageRank).toBeChecked({ checked: true }); + + // Deselect assessment + await inputAssessmentAverageRank.uncheck({ force: true }); + await expect(inputAssessmentAverageRank).toBeChecked({ checked: false }); + + await sleep(1000); + for (let [plotId, texts] of AAWA2_average_rank_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + false + ); + } + for (let [plotId, texts] of AAWA2_no_average_rank_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + true + ); + } + + // Reselect assessment. + await inputAssessmentAverageRank.check({ force: true }); + await expect(inputAssessmentAverageRank).toBeChecked({ checked: true }); + await sleep(1000); + for (let [plotId, texts] of AAWA2_all_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + true + ); + } + }); + + test('Test (de)select tasks', async ({ page }) => { + // Switch to benchmarks page + const menuBenchmark = await page.locator('nav > ul > li:nth-child(2)'); + await menuBenchmark.click(); + const menu = await menuBenchmark.getByTitle( + /Go to benchmarks visualizations/ + ); + await menu.click(); + const benchmarksPlaceholder = await page.getByText(/No benchmark selected/); + await benchmarksPlaceholder.waitFor(); + await expect(benchmarksPlaceholder).toHaveCount(1); + + // Get benchmark search field + const benchmarkField = await page.getByPlaceholder( + 'Search a benchmark ...' + ); + // Select all_assessments_webapi_2 + await benchmarkField.type('all_asses'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe( + 'all_assessments_webapi_2' + ); + + // Make sure all plots are there (10 plots) + for (let [plotId, texts] of AAWA2_all_with_id) { + await lookupPlotById(page, plotId, ...texts); + } + + // Select 1 task. + const inputTaskBranin = await page.locator('#task-0'); + await expect(inputTaskBranin).toHaveCount(1); + await expect(inputTaskBranin).toBeChecked({ checked: true }); + + // Deselect task. + await inputTaskBranin.uncheck({ force: true }); + await expect(inputTaskBranin).toBeChecked({ checked: false }); + await sleep(1000); + for (let [plotId, texts] of AAWA2_branin_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + false + ); + } + for (let [plotId, texts] of AAWA2_no_branin_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + true + ); + } + // Reselect task. + await inputTaskBranin.check({ force: true }); + await expect(inputTaskBranin).toBeChecked({ checked: true }); + await sleep(1000); + for (let [plotId, texts] of AAWA2_all_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + true + ); + } + }); + + test('Test (de)select algorithms', async ({ page }) => { + // Switch to benchmarks page + const menuBenchmark = await page.locator('nav > ul > li:nth-child(2)'); + await menuBenchmark.click(); + const menu = await menuBenchmark.getByTitle( + /Go to benchmarks visualizations/ + ); + await menu.click(); + const benchmarksPlaceholder = await page.getByText(/No benchmark selected/); + await benchmarksPlaceholder.waitFor(); + await expect(benchmarksPlaceholder).toHaveCount(1); + + // Get benchmark search field + const benchmarkField = await page.getByPlaceholder( + 'Search a benchmark ...' + ); + // Select all_assessments_webapi_2 + await benchmarkField.type('all_asses'); + await benchmarkField.press('Enter'); + expect(await benchmarkField.getAttribute('value')).toBe( + 'all_assessments_webapi_2' + ); + + // Make sure all plots are there (10 plots) + for (let [plotId, texts] of AAWA2_all_with_id) { + await lookupPlotById(page, plotId, ...texts); + } + + // Select 1 algorithm. + const inputAlgorithmRandom = await page.locator('#algorithm-0'); + await expect(inputAlgorithmRandom).toHaveCount(1); + await expect(inputAlgorithmRandom).toBeChecked({ checked: true }); + + // Deselect algorithm. + await inputAlgorithmRandom.uncheck({ force: true }); + await expect(inputAlgorithmRandom).toBeChecked({ checked: false }); + await sleep(1000); + for (let [plotId, texts] of AAWA2_all_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + false + ); + } + for (let [plotId, textsNoRandom] of AAWA2_no_random_with_id) { + expect( + await hasPlotByIdAndTextsImmediately(page, plotId, ...textsNoRandom) + ).toBe(true); + } + // Reselect algorithm. + await inputAlgorithmRandom.check({ force: true }); + await expect(inputAlgorithmRandom).toBeChecked({ checked: true }); + await sleep(1000); + for (let [plotId, texts] of AAWA2_all_with_id) { + expect(await hasPlotByIdAndTextsImmediately(page, plotId, ...texts)).toBe( + true + ); + } + }); }); diff --git a/dashboard/src/src/__tests__/experiments.databasePage.test.js b/dashboard/src/src/__tests__/experiments.databasePage.test.js index 528762914..79d513c93 100644 --- a/dashboard/src/src/__tests__/experiments.databasePage.test.js +++ b/dashboard/src/src/__tests__/experiments.databasePage.test.js @@ -1,588 +1,955 @@ -import React from 'react'; -import App from '../App'; -import { - render, - screen, - queryByText, - queryByTitle, - fireEvent, -} from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -/* Use MemoryRouter to isolate history for each test */ -import { MemoryRouter } from 'react-router-dom'; - -async function waitForExperimentToBeLoaded(experiment) { +import { test, expect } from '@playwright/test'; +import { StatusToProgress } from '../experiments/components/ExperimentStatusBar/ExperimentStatusBar'; + +const PROGRESS_BAR_NAMES = [ + 'success', + 'suspended', + 'warning', + 'danger', + 'info', +]; + +async function waitForExperimentToBeLoaded(page, experiment) { /** Wait for trials table to be loaded for given experiment name. */ // Check if trials are loaded const regex = new RegExp(`Experiment Trials for "${experiment}"`); - expect( - await screen.findByTitle(regex, {}, global.CONFIG_WAIT_FOR_LONG) - ).toBeInTheDocument(); + const exp = await page.getByTitle(regex); + await exp.waitFor(); + await expect(exp).toHaveCount(1); +} + +/** + * Check if a normal progress bar has expected non-null sub-bars + * in order given by statusDescendingOrder from longest to smallest. + * + * Any sub-bar non-mentioned in statusDescendingOrder + * is expected to have width zero. + * + * A "normal" progress bar is a bar for an experiment + * whose max_trials is non-infinite. + * + * @param bar - progress bar locator + * @param statusDescendingOrder {Array} - list of expected bar names + * (success, suspended, warning, danger or info). + * Expected bar names should have non-null width. + * Order of bar names should be from the longest to the smallest bar. + * @returns {Promise} + */ +async function checkNormalBars(bar, statusDescendingOrder) { + const barWidths = {}; + const expectedNUllBars = []; + for (let barStatus of PROGRESS_BAR_NAMES) { + const subBar = await bar.locator(`.bg-${barStatus}`); + await expect(subBar).toHaveCount(1); + // Collect bar width. + barWidths[barStatus] = (await subBar.boundingBox()).width; + // Collect if bar must have width 0. + if (statusDescendingOrder.indexOf(barStatus) < 0) + expectedNUllBars.push(barStatus); + } + console.log( + `Testing bars: expected: ${statusDescendingOrder.join(' >= ') || + 'no non-null bars'}, null bars: ${expectedNUllBars.join(', ') || + 'none'}, collected widths: ${PROGRESS_BAR_NAMES.map( + status => status + ': ' + barWidths[status] + ).join(', ')}` + ); + // Check bar widths. + if (statusDescendingOrder.length) { + // First expected bar must have width > 0. + expect(barWidths[statusDescendingOrder[0]]).toBeGreaterThan(0); + for (let i = 1; i < statusDescendingOrder.length; ++i) { + // Each following bar must be <= previous. + expect(barWidths[statusDescendingOrder[i - 1]]).toBeGreaterThanOrEqual( + barWidths[statusDescendingOrder[i]] + ); + expect(barWidths[statusDescendingOrder[i]]).toBeGreaterThan(0); + } + } + // Other bars must have width == 0. + for (let zeroStatus of expectedNUllBars) { + expect(barWidths[zeroStatus]).toBe(0); + } } -// Since I updated dependencies in package.json, this seems necessary. -beforeEach(() => { - Object.defineProperty(window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), +test.describe('Test trials table in database page', () => { + test.beforeEach(async ({ page }) => { + // Set a hardcoded page size. + await page.setViewportSize({ width: 1920, height: 1080 }); + // Open Dashboard page. + await page.goto('localhost:3000'); + // Switch to database page + const menuExperiments = await page.locator('nav > ul > li:nth-child(1)'); + await menuExperiments.click(); + const menu = await menuExperiments.getByTitle(/Go to experiments database/); + await menu.click(); + await expect( + await page.getByText( + /No trials to display, please select an experiment\./ + ) + ).toHaveCount(1); }); -}); -test('Test if experiment trials are loaded', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - const experiment = await screen.findByText( - /2-dim-shape-exp/, - {}, - global.CONFIG_WAIT_FOR_LONG - ); - expect(experiment).toBeInTheDocument(); + test('Test if experiment trials are loaded', async ({ page }) => { + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await expect(experiment).toHaveCount(1); - // Switch to database page - const menu = screen.queryByTitle(/Go to experiments database/); - await user.click(menu); - expect( - await screen.findByText( - /No trials to display, please select an experiment\./ - ) - ).toBeInTheDocument(); + // Select an experiment + await experiment.click(); + await expect( + await page.getByText( + `Loading trials for experiment "2-dim-shape-exp" ...` + ) + ).toHaveCount(1); - // Select an experiment - expect(experiment).toBeInTheDocument(); - await user.click(experiment); + // Check if trials are loaded + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); - // Check if trials are loaded - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - - // Unselect experiment - const row = screen.queryByTitle(/unselect experiment '2-dim-shape-exp'/); - expect(row).toBeInTheDocument(); - expect(row.tagName.toLowerCase()).toBe('label'); - await user.click(row); - expect( - await screen.findByText( - /No trials to display, please select an experiment\./ - ) - ).toBeInTheDocument(); - expect( - screen.queryByTitle( - /Experiment Trials for "2-dim-shape-exp"/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeNull(); - expect(screen.queryByTitle(/0f886905874af10a6db412885341ae0b/)).toBeNull(); - - // re-select experiment and check if trials are loaded - await user.click(experiment); - expect( - await screen.findByTitle( - /Experiment Trials for "2-dim-shape-exp"/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - - // Select another experiment and check if trials are loaded - const anotherExperiment = await screen.findByText(/tpe-rosenbrock/); - expect(anotherExperiment).toBeInTheDocument(); - await user.click(anotherExperiment); - expect( - await screen.findByTitle( - /Experiment Trials for "tpe-rosenbrock"/, - {}, - global.CONFIG_WAIT_FOR_LONG - ) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/Experiment Trials for "tpe-rosenbrock"/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/15f4ed436861d25de9be04db9837a70c/) - ).toBeInTheDocument(); -}); + // Unselect experiment + const row = await page.getByTitle(/unselect experiment '2-dim-shape-exp'/); + await row.click(); -test('Test pagination - select items per page', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - // Items per page is 10 by default. Check expected trials. - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/13c04ed294010cecf4491b84837d8402/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/1dec3f2f7b72bc707500258d829a7762/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/6479b23d62db27f4563295e68f7aefe1/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) - ).toBeInTheDocument(); - // Change items per page to 5 and check expected trials. - const selectItemsPerPage = document.getElementById( - 'bx-pagination-select-trials-pagination' - ); - expect(selectItemsPerPage).toBeInTheDocument(); - await user.selectOptions(selectItemsPerPage, '5'); - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/13c04ed294010cecf4491b84837d8402/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/1dec3f2f7b72bc707500258d829a7762/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) - ).toBeInTheDocument(); - expect(screen.queryByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/)).toBeNull(); - expect(screen.queryByTitle(/5fa4a08bdbafd9a9b57753569b369c62/)).toBeNull(); - expect(screen.queryByTitle(/6479b23d62db27f4563295e68f7aefe1/)).toBeNull(); - expect(screen.queryByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/)).toBeNull(); - expect(screen.queryByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/)).toBeNull(); -}); + await expect( + await page.getByText( + /No trials to display, please select an experiment\./ + ) + ).toHaveCount(1); + await expect( + await page.getByTitle(/Experiment Trials for "2-dim-shape-exp"/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(0); -test('Test pagination - change page', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - // We are in first page by default. Check expected trials. - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/13c04ed294010cecf4491b84837d8402/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/1dec3f2f7b72bc707500258d829a7762/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/6479b23d62db27f4563295e68f7aefe1/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) - ).toBeInTheDocument(); - // Select 2nd page and check expected trials. - const selectPage = document.getElementById( - 'bx-pagination-select-trials-pagination-right' - ); - expect(selectPage).toBeInTheDocument(); - await user.selectOptions(selectPage, '2'); - expect(screen.queryByTitle(/0f886905874af10a6db412885341ae0b/)).toBeNull(); - expect(screen.queryByTitle(/13c04ed294010cecf4491b84837d8402/)).toBeNull(); - expect(screen.queryByTitle(/1dec3f2f7b72bc707500258d829a7762/)).toBeNull(); - expect(screen.queryByTitle(/227a7b2e5e9520d577b4c69c64a212c0/)).toBeNull(); - expect(screen.queryByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/)).toBeNull(); - expect(screen.queryByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/)).toBeNull(); - expect(screen.queryByTitle(/5fa4a08bdbafd9a9b57753569b369c62/)).toBeNull(); - expect(screen.queryByTitle(/6479b23d62db27f4563295e68f7aefe1/)).toBeNull(); - expect(screen.queryByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/)).toBeNull(); - expect(screen.queryByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/)).toBeNull(); - expect( - screen.queryByTitle(/b551c6ff4c4d816cdf93b844007eb707/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/c315d0d996290d5d5342cfce3e6d6c9e/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/d2bc2590825ca06cb88e4c54c1142530/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/d669de51fe55d524decf50bf5f5819df/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/f1f350224ae041550658149b55f6c72a/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/f584840e70e38f0cd0cfc4ff1b0e5f2b/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/fac3d17812d82ebd17bd771eae2802bb/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/fced71d7a9bc1b4fe7c0a4029fe73875/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/fd5104909823804b299548acbd089ca6/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/ff3adfecab4d01a5e8cb1550cc74b695/) - ).toBeInTheDocument(); - // Click to previous page button and check expected trials. - const buttonsPreviousPage = document.getElementsByClassName( - 'bx--pagination__button--backward' - ); - expect(buttonsPreviousPage).toHaveLength(1); - await user.click(buttonsPreviousPage[0]); - expect( - screen.queryByTitle(/0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/13c04ed294010cecf4491b84837d8402/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/1dec3f2f7b72bc707500258d829a7762/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/6479b23d62db27f4563295e68f7aefe1/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) - ).toBeInTheDocument(); - expect( - screen.queryByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) - ).toBeInTheDocument(); - expect(screen.queryByTitle(/b551c6ff4c4d816cdf93b844007eb707/)).toBeNull(); - expect(screen.queryByTitle(/c315d0d996290d5d5342cfce3e6d6c9e/)).toBeNull(); - expect(screen.queryByTitle(/d2bc2590825ca06cb88e4c54c1142530/)).toBeNull(); - expect(screen.queryByTitle(/d669de51fe55d524decf50bf5f5819df/)).toBeNull(); - expect(screen.queryByTitle(/f1f350224ae041550658149b55f6c72a/)).toBeNull(); - expect(screen.queryByTitle(/f584840e70e38f0cd0cfc4ff1b0e5f2b/)).toBeNull(); - expect(screen.queryByTitle(/fac3d17812d82ebd17bd771eae2802bb/)).toBeNull(); - expect(screen.queryByTitle(/fced71d7a9bc1b4fe7c0a4029fe73875/)).toBeNull(); - expect(screen.queryByTitle(/fd5104909823804b299548acbd089ca6/)).toBeNull(); - expect(screen.queryByTitle(/ff3adfecab4d01a5e8cb1550cc74b695/)).toBeNull(); -}); + // re-select experiment and check if trials are loaded + await experiment.click(); + await expect( + await page.getByTitle(/Experiment Trials for "2-dim-shape-exp"/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); -test('Test (de)select columns', async () => { - const user = userEvent.setup(); + // Select another experiment and check if trials are loaded + const searchField = await page.getByPlaceholder('Search experiment'); + await searchField.type('tpe-rosenbrock'); + const anotherExperiment = await page.getByText(/tpe-rosenbrock/); + await expect(anotherExperiment).toHaveCount(1); + await anotherExperiment.click(); + await waitForExperimentToBeLoaded(page, 'tpe-rosenbrock'); + await expect( + await page.getByTitle(/15f4ed436861d25de9be04db9837a70c/) + ).toHaveCount(1); + }); - // Load page - const { container } = render(, { wrapper: MemoryRouter }); + test('Test pagination - select items per page', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + // Items per page is 10 by default. Check expected trials. + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(1); + // Change items per page to 5 and check expected trials. + const selectItemsPerPage = await page.locator( + '#bx-pagination-select-trials-pagination' + ); + await expect(selectItemsPerPage).toHaveCount(1); + await selectItemsPerPage.selectOption('5'); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(0); + }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); + test('Test pagination - change page', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + // We are in first page by default. Check expected trials. + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(1); + // Select 2nd page and check expected trials. + const selectPage = await page.locator( + '#bx-pagination-select-trials-pagination-right' + ); + await expect(selectPage).toHaveCount(1); + await selectPage.selectOption('2'); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/b551c6ff4c4d816cdf93b844007eb707/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/c315d0d996290d5d5342cfce3e6d6c9e/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/d2bc2590825ca06cb88e4c54c1142530/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/d669de51fe55d524decf50bf5f5819df/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/f1f350224ae041550658149b55f6c72a/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/f584840e70e38f0cd0cfc4ff1b0e5f2b/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/fac3d17812d82ebd17bd771eae2802bb/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/fced71d7a9bc1b4fe7c0a4029fe73875/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/fd5104909823804b299548acbd089ca6/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/ff3adfecab4d01a5e8cb1550cc74b695/) + ).toHaveCount(1); + // Click to previous page button and check expected trials. + const buttonsPreviousPage = await page.locator( + '.bx--pagination__button--backward' + ); + await expect(buttonsPreviousPage).toHaveCount(1); + await buttonsPreviousPage.click(); + await expect( + await page.getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(1); + await expect( + await page.getByTitle(/b551c6ff4c4d816cdf93b844007eb707/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/c315d0d996290d5d5342cfce3e6d6c9e/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/d2bc2590825ca06cb88e4c54c1142530/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/d669de51fe55d524decf50bf5f5819df/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/f1f350224ae041550658149b55f6c72a/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/f584840e70e38f0cd0cfc4ff1b0e5f2b/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/fac3d17812d82ebd17bd771eae2802bb/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/fced71d7a9bc1b4fe7c0a4029fe73875/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/fd5104909823804b299548acbd089ca6/) + ).toHaveCount(0); + await expect( + await page.getByTitle(/ff3adfecab4d01a5e8cb1550cc74b695/) + ).toHaveCount(0); + }); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - - // Define values for first row - const valID = /0f886905874af10a6db412885341ae0b/; - const valSubmitTime = '2019-11-19 15:41:16.985000'; - const valStartTime = '2019-11-19 15:41:16.996000'; - const valEndTime = '2019-11-19 21:58:02.820000'; - const valObjective = '-0.7881121864177159'; - - // Check first row for ID, submit time, start time, end time and objective - expect(screen.queryByTitle(valID)).toBeInTheDocument(); - expect(queryByText(container, valSubmitTime)).toBeInTheDocument(); - expect(queryByText(container, valStartTime)).toBeInTheDocument(); - expect(queryByText(container, valEndTime)).toBeInTheDocument(); - expect(queryByText(container, valObjective)).toBeInTheDocument(); - - // Locate options - const multiSelect = document.getElementById('multiselect-columns'); - await user.click(multiSelect.querySelector('button.bx--list-box__field')); - const optionID = queryByText(multiSelect, 'ID'); - const optionSubmitTime = queryByText(multiSelect, 'Submit time'); - const optionStartTime = queryByText(multiSelect, 'Start time'); - const optionEndTime = queryByText(multiSelect, 'End time'); - const optionObjective = queryByText(multiSelect, 'Objective'); - const optionSelectAll = queryByText(multiSelect, '(select all)'); - expect(optionID).toBeInTheDocument(); - expect(optionSubmitTime).toBeInTheDocument(); - expect(optionStartTime).toBeInTheDocument(); - expect(optionEndTime).toBeInTheDocument(); - expect(optionObjective).toBeInTheDocument(); - expect(optionSelectAll).toBeInTheDocument(); - - // Deselect column Submit time and check first row - await user.click(optionSubmitTime); - expect(screen.queryByTitle(valID)).toBeInTheDocument(); - expect(queryByText(container, valSubmitTime)).toBeNull(); - expect(queryByText(container, valStartTime)).toBeInTheDocument(); - expect(queryByText(container, valEndTime)).toBeInTheDocument(); - expect(queryByText(container, valObjective)).toBeInTheDocument(); - - // Deselect column objective and check first row - await user.click(optionObjective); - expect(screen.queryByTitle(valID)).toBeInTheDocument(); - expect(queryByText(container, valSubmitTime)).toBeNull(); - expect(queryByText(container, valStartTime)).toBeInTheDocument(); - expect(queryByText(container, valEndTime)).toBeInTheDocument(); - expect(queryByText(container, valObjective)).toBeNull(); - - // Deselect columns ID, start time, end time, and check first row - await user.click(optionID); - await user.click(optionStartTime); - await user.click(optionEndTime); - expect(screen.queryByTitle(valID)).toBeNull(); - expect(queryByText(container, valSubmitTime)).toBeNull(); - expect(queryByText(container, valStartTime)).toBeNull(); - expect(queryByText(container, valEndTime)).toBeNull(); - expect(queryByText(container, valObjective)).toBeNull(); - - // Click to 'select all' and check that all columns are now visible in first row - await user.click(optionSelectAll); - expect(screen.queryByTitle(valID)).toBeInTheDocument(); - expect(queryByText(container, valSubmitTime)).toBeInTheDocument(); - expect(queryByText(container, valStartTime)).toBeInTheDocument(); - expect(queryByText(container, valEndTime)).toBeInTheDocument(); - expect(queryByText(container, valObjective)).toBeInTheDocument(); -}); + test('Test (de)select columns', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); -test('Test sort columns', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - // Get sort button from ID column header - // ID column header is first column from second tr element in table - // (first tr contains Parameters column and placeholders) - const sortButton = document - .querySelectorAll('.bx--data-table-content thead tr')[1] - .querySelector('th button.bx--table-sort'); - // Click once to activate sorting (sort ascending) - await user.click(sortButton); - // Click again to sort descending - await user.click(sortButton); - // Check expected rows - let rows = document.querySelectorAll('.bx--data-table-content tbody tr'); - expect(rows).toHaveLength(10); - expect( - queryByTitle(rows[0], /ff3adfecab4d01a5e8cb1550cc74b695/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[1], /fd5104909823804b299548acbd089ca6/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[2], /fced71d7a9bc1b4fe7c0a4029fe73875/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[3], /fac3d17812d82ebd17bd771eae2802bb/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[4], /f584840e70e38f0cd0cfc4ff1b0e5f2b/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[5], /f1f350224ae041550658149b55f6c72a/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[6], /d669de51fe55d524decf50bf5f5819df/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[7], /d2bc2590825ca06cb88e4c54c1142530/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[8], /c315d0d996290d5d5342cfce3e6d6c9e/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[9], /b551c6ff4c4d816cdf93b844007eb707/) - ).toBeInTheDocument(); - // Click again to deactivate sorting (back to default order) - await user.click(sortButton); - // Click again to sort ascending - await user.click(sortButton); - // Check expected rows - rows = document.querySelectorAll('.bx--data-table-content tbody tr'); - expect(rows).toHaveLength(10); - expect( - queryByTitle(rows[0], /0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[1], /13c04ed294010cecf4491b84837d8402/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[2], /1dec3f2f7b72bc707500258d829a7762/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[3], /227a7b2e5e9520d577b4c69c64a212c0/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[4], /4262e3b56f7974e46c5ff5d40c4dc1a6/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[5], /582ba78a94a7fbc3e632a0fc40dc99eb/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[6], /5fa4a08bdbafd9a9b57753569b369c62/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[7], /6479b23d62db27f4563295e68f7aefe1/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[8], /85aa9dcbf825d3fcc90ca11c01fe90e4/) - ).toBeInTheDocument(); - expect( - queryByTitle(rows[9], /a7d1729852ebc0bebdbc2db1e9396fc1/) - ).toBeInTheDocument(); -}); + // Define values for first row + const valID = /0f886905874af10a6db412885341ae0b/; + const valSubmitTime = '2019-11-19 15:41:16.985000'; + const valStartTime = '2019-11-19 15:41:16.996000'; + const valEndTime = '2019-11-19 21:58:02.820000'; + const valStatus = 'completed'; + const valObjective = '-0.7881121864177159'; -test('Test drag-and-drop columns', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - // Get column ID to drag. - const draggableColumnID = document - .querySelectorAll('.bx--data-table-content thead tr')[1] - .querySelector('th .header-dnd'); - // Get column /dropout to drop into. - const droppableColumnDropout = document - .querySelectorAll('.bx--data-table-content thead tr')[1] - .querySelectorAll('th')[1]; - // Check default first row. - // ID in first column, /dropout in second column. - let firstRowCols = document.querySelectorAll( - '.bx--data-table-content tbody tr td' - ); - expect( - queryByTitle(firstRowCols[0], /0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); - expect(queryByText(firstRowCols[1], '0.2')).toBeInTheDocument(); - // Drag-and-drop column ID to column /dropout. - fireEvent.dragStart(draggableColumnID); - fireEvent.dragEnter(droppableColumnDropout); - fireEvent.dragOver(droppableColumnDropout); - fireEvent.drop(droppableColumnDropout); - // Check first row after drag-and-drop. - // /dropout in first column, ID in second column. - firstRowCols = document.querySelectorAll( - '.bx--data-table-content tbody tr td' - ); - expect( - queryByTitle(firstRowCols[0], /0f886905874af10a6db412885341ae0b/) - ).toBeNull(); - expect(queryByText(firstRowCols[1], '0.2')).toBeNull(); - expect(queryByText(firstRowCols[0], '0.2')).toBeInTheDocument(); - expect( - queryByTitle(firstRowCols[1], /0f886905874af10a6db412885341ae0b/) - ).toBeInTheDocument(); + const table = await page.locator('.bx--data-table-container'); + await expect(table).toHaveCount(1); + + // Check first row + await expect(await table.getByTitle(valID)).toHaveCount(1); + await expect(await table.getByText(valSubmitTime)).toHaveCount(1); + await expect(await table.getByText(valStartTime)).toHaveCount(1); + await expect(await table.getByText(valEndTime)).toHaveCount(1); + // NB: All trials have status 'completed', so queryByText() will complain + // returning many elements, so we'd better use queryAllByText(). + await expect(await table.getByText(valStatus)).toHaveCount(10); + await expect(await table.getByText(valObjective)).toHaveCount(1); + + // Locate options + const multiSelect = await page.locator('#multiselect-columns'); + await (await multiSelect.locator('button.bx--list-box__field')).click(); + const optionID = await multiSelect.getByText('ID'); + const optionSubmitTime = await multiSelect.getByText('Submit time'); + const optionStartTime = await multiSelect.getByText('Start time'); + const optionEndTime = await multiSelect.getByText('End time'); + const optionStatus = await multiSelect.getByText('Status'); + const optionObjective = await multiSelect.getByText('Objective'); + const optionSelectAll = await multiSelect.getByText('(select all)'); + await expect(optionID).toHaveCount(1); + await expect(optionSubmitTime).toHaveCount(1); + await expect(optionStartTime).toHaveCount(1); + await expect(optionEndTime).toHaveCount(1); + await expect(optionStatus).toHaveCount(1); + await expect(optionObjective).toHaveCount(1); + await expect(optionSelectAll).toHaveCount(1); + + // Deselect column Submit time and check first row + await optionSubmitTime.click(); + await expect(await table.getByTitle(valID)).toHaveCount(1); + await expect(await table.getByText(valSubmitTime)).toHaveCount(0); + await expect(await table.getByText(valStartTime)).toHaveCount(1); + await expect(await table.getByText(valEndTime)).toHaveCount(1); + await expect(await table.getByText(valStatus)).toHaveCount(10); + await expect(await table.getByText(valObjective)).toHaveCount(1); + + // Deselect column objective and check first row + await optionObjective.click(); + await expect(await table.getByTitle(valID)).toHaveCount(1); + await expect(await table.getByText(valSubmitTime)).toHaveCount(0); + await expect(await table.getByText(valStartTime)).toHaveCount(1); + await expect(await table.getByText(valEndTime)).toHaveCount(1); + await expect(await table.getByText(valStatus)).toHaveCount(10); + await expect(await table.getByText(valObjective)).toHaveCount(0); + + // Deselect columns ID, start time, end time, status, and check first row + await optionID.click(); + await optionStartTime.click(); + await optionEndTime.click(); + await optionStatus.click(); + await expect(await table.getByTitle(valID)).toHaveCount(0); + await expect(await table.getByText(valSubmitTime)).toHaveCount(0); + await expect(await table.getByText(valStartTime)).toHaveCount(0); + await expect(await table.getByText(valEndTime)).toHaveCount(0); + await expect(await table.getByText(valStatus)).toHaveCount(0); + await expect(await table.getByText(valObjective)).toHaveCount(0); + + // Click to 'select all' and check that all columns are now visible in first row + await optionSelectAll.click(); + await expect(await table.getByTitle(valID)).toHaveCount(1); + await expect(await table.getByText(valSubmitTime)).toHaveCount(1); + await expect(await table.getByText(valStartTime)).toHaveCount(1); + await expect(await table.getByText(valEndTime)).toHaveCount(1); + await expect(await table.getByText(valStatus)).toHaveCount(10); + await expect(await table.getByText(valObjective)).toHaveCount(1); + }); + + test('Test sort columns', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + // Get sort button from ID column header + // ID column header is first column from second tr element in table + // (first tr contains Parameters column and placeholders) + const sortButton = await page.locator( + '.bx--data-table-content thead tr:nth-child(2) th:nth-child(1) button.bx--table-sort' + ); + await expect(sortButton).toHaveCount(1); + // const sortButton = document + // .querySelectorAll('.bx--data-table-content thead tr')[1] + // .querySelector('th button.bx--table-sort'); + + // Click once to activate sorting (sort ascending) + await sortButton.click(); + // Click again to sort descending + await sortButton.click(); + // Check expected rows + let rows = await page.locator('.bx--data-table-content tbody tr'); + await expect(rows).toHaveCount(10); + await expect( + await rows.nth(0).getByTitle(/ff3adfecab4d01a5e8cb1550cc74b695/) + ).toHaveCount(1); + await expect( + await rows.nth(1).getByTitle(/fd5104909823804b299548acbd089ca6/) + ).toHaveCount(1); + await expect( + await rows.nth(2).getByTitle(/fced71d7a9bc1b4fe7c0a4029fe73875/) + ).toHaveCount(1); + await expect( + await rows.nth(3).getByTitle(/fac3d17812d82ebd17bd771eae2802bb/) + ).toHaveCount(1); + await expect( + await rows.nth(4).getByTitle(/f584840e70e38f0cd0cfc4ff1b0e5f2b/) + ).toHaveCount(1); + await expect( + await rows.nth(5).getByTitle(/f1f350224ae041550658149b55f6c72a/) + ).toHaveCount(1); + await expect( + await rows.nth(6).getByTitle(/d669de51fe55d524decf50bf5f5819df/) + ).toHaveCount(1); + await expect( + await rows.nth(7).getByTitle(/d2bc2590825ca06cb88e4c54c1142530/) + ).toHaveCount(1); + await expect( + await rows.nth(8).getByTitle(/c315d0d996290d5d5342cfce3e6d6c9e/) + ).toHaveCount(1); + await expect( + await rows.nth(9).getByTitle(/b551c6ff4c4d816cdf93b844007eb707/) + ).toHaveCount(1); + // Click again to deactivate sorting (back to default order) + await sortButton.click(); + // Click again to sort ascending + await sortButton.click(); + // Check expected rows + rows = await page.locator('.bx--data-table-content tbody tr'); + await expect(rows).toHaveCount(10); + await expect( + await rows.nth(0).getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect( + await rows.nth(1).getByTitle(/13c04ed294010cecf4491b84837d8402/) + ).toHaveCount(1); + await expect( + await rows.nth(2).getByTitle(/1dec3f2f7b72bc707500258d829a7762/) + ).toHaveCount(1); + await expect( + await rows.nth(3).getByTitle(/227a7b2e5e9520d577b4c69c64a212c0/) + ).toHaveCount(1); + await expect( + await rows.nth(4).getByTitle(/4262e3b56f7974e46c5ff5d40c4dc1a6/) + ).toHaveCount(1); + await expect( + await rows.nth(5).getByTitle(/582ba78a94a7fbc3e632a0fc40dc99eb/) + ).toHaveCount(1); + await expect( + await rows.nth(6).getByTitle(/5fa4a08bdbafd9a9b57753569b369c62/) + ).toHaveCount(1); + await expect( + await rows.nth(7).getByTitle(/6479b23d62db27f4563295e68f7aefe1/) + ).toHaveCount(1); + await expect( + await rows.nth(8).getByTitle(/85aa9dcbf825d3fcc90ca11c01fe90e4/) + ).toHaveCount(1); + await expect( + await rows.nth(9).getByTitle(/a7d1729852ebc0bebdbc2db1e9396fc1/) + ).toHaveCount(1); + }); + + test('Test drag-and-drop columns', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + + // Get column ID to drag. + const draggableColumnID = await page.locator( + '.bx--data-table-content thead tr:nth-child(2) th:nth-child(1) .header-dnd' + ); + await expect(draggableColumnID).toHaveCount(1); + + // Get column /dropout to drop into. + const droppableColumnDropout = await page.locator( + '.bx--data-table-content thead tr:nth-child(2) th:nth-child(2)' + ); + await expect(droppableColumnDropout).toHaveCount(1); + + // Check default first row. + // ID in first column, /dropout in second column. + let firstRowCols = await page.locator( + '.bx--data-table-content tbody tr:nth-child(1) td' + ); + await expect( + await firstRowCols.nth(0).getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + await expect(await firstRowCols.nth(1).getByText('0.2')).toHaveCount(1); + + // Drag-and-drop column ID to column /dropout. + await draggableColumnID.dragTo(droppableColumnDropout); + + // Check first row after drag-and-drop. + // /dropout in first column, ID in second column. + firstRowCols = await page.locator( + '.bx--data-table-content tbody tr:nth-child(1) td' + ); + await expect( + await firstRowCols.nth(0).getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(0); + await expect(await firstRowCols.nth(1).getByText('0.2')).toHaveCount(0); + await expect(await firstRowCols.nth(0).getByText('0.2')).toHaveCount(1); + await expect( + await firstRowCols.nth(1).getByTitle(/0f886905874af10a6db412885341ae0b/) + ).toHaveCount(1); + }); + + test('Test display trial info in a dialog', async ({ page }) => { + // Select an experiment + const experiment = await page.getByText(/2-dim-shape-exp/); + await experiment.waitFor(); + await experiment.click(); + await waitForExperimentToBeLoaded(page, '2-dim-shape-exp'); + + const trial = await page.getByTitle(/0f886905874af10a6db412885341ae0b/); + await expect(trial).toHaveCount(1); + // Dialogs are pre-rendered but not visible. + + // Check if there is no dialog visible. + await expect(await page.locator('.bx--modal.is-visible')).toHaveCount(0); + // Click on trial. + await trial.click(); + // Check if a dialog is visible now. + const dialog = await page.locator('.bx--modal.is-visible'); + await expect(dialog).toHaveCount(1); + // Check if visible dialog is the one related to this trial. + const dialogTitle = await dialog.getByText(/Trial info/); + const dialogHeader = await dialog.getByText( + /2-dim-shape-exp \/ 0f886905874af10a6db412885341ae0b/ + ); + await expect(dialogTitle).toHaveCount(1); + await expect(dialogHeader).toHaveCount(1); + // Close dialog + const closButton = await dialog.locator('.bx--modal-close'); + await expect(closButton).toHaveCount(1); + await closButton.click(); + // Check if there is no dialog visible. + await expect(await page.locator('.bx--modal.is-visible')).toHaveCount(0); + }); }); -test('Test display trial info in a dialog', async () => { - const user = userEvent.setup(); - // Load page - render(, { wrapper: MemoryRouter }); - // Switch to database page - await user.click(screen.queryByTitle(/Go to experiments database/)); - // Select an experiment - await user.click( - await screen.findByText(/2-dim-shape-exp/, {}, global.CONFIG_WAIT_FOR_LONG) - ); - await waitForExperimentToBeLoaded('2-dim-shape-exp'); - const trial = screen.queryByTitle(/0f886905874af10a6db412885341ae0b/); - expect(trial).toBeInTheDocument(); - // Dialogs are pre-rendered but not visible. - // Check if there is no dialog visible. - expect(document.getElementsByClassName('bx--modal is-visible')).toHaveLength( - 0 - ); - // Click on trial. - await user.click(trial); - // Check if a dialog is visible now. - const dialogs = document.getElementsByClassName('bx--modal is-visible'); - expect(dialogs).toHaveLength(1); - // Check if visible dialog is the one related to this trial. - const dialog = dialogs[0]; - const dialogTitle = queryByText(dialog, /Trial info/); - const dialogHeader = queryByText( - dialog, - /2-dim-shape-exp \/ 0f886905874af10a6db412885341ae0b/ - ); - expect(dialogTitle).toBeInTheDocument(); - expect(dialogHeader).toBeInTheDocument(); - // Close dialog - const closButtons = dialog.getElementsByClassName('bx--modal-close'); - expect(closButtons).toHaveLength(1); - await user.click(closButtons[0]); - // Check if there is no dialog visible. - expect(document.getElementsByClassName('bx--modal is-visible')).toHaveLength( - 0 - ); +test.describe('Test progress bars above trials table in database page', () => { + test.beforeEach(async ({ page }) => { + // Set a hardcoded page size. + await page.setViewportSize({ width: 1920, height: 1080 }); + // Open Dashboard page. + await page.goto('localhost:3000'); + // Switch to database page + const menuExperiments = await page.locator('nav > ul > li:nth-child(1)'); + await menuExperiments.click(); + const menu = await menuExperiments.getByTitle(/Go to experiments database/); + await menu.click(); + await expect( + await page.getByText( + /No trials to display, please select an experiment\./ + ) + ).toHaveCount(1); + // Get nav bar and wait for default experiments to be loaded. + const navBar = await page.locator('.experiment-navbar'); + const firstExperiment = await navBar.getByText(/2-dim-shape-exp/); + await firstExperiment.waitFor(); + // Search uncompleted experiments + const searchField = await page.getByPlaceholder('Search experiment'); + await expect(searchField).toHaveCount(1); + await searchField.type('uncompleted'); + await (await navBar.getByText('uncompleted_experiment')).waitFor(); + }); + + test('Test uncompleted_experiment and all displayed info', async ({ + page, + }) => { + const experiment = await page.getByText('uncompleted_experiment'); + await experiment.click(); + await waitForExperimentToBeLoaded(page, 'uncompleted_experiment'); + + // Check that additional experiment info are displayed in the page + const expectedInfo = [ + ['Best trial ID', '1c31bf5fb0d20680a631ae01c2abc92b'], + ['Best evaluation', '0.9999999999999999'], + ['Start time', '2000-01-01 10:00:00.123000'], + ['Finish time', '2000-01-01 10:41:00.123000'], + ['Trials', '140'], + ['Max trials', '200'], + ]; + const infoRows = await page.locator('.experiment-info .bx--row'); + await expect(infoRows).toHaveCount(expectedInfo.length); + for (let i = 0; i < 0; ++i) { + const row = infoRows.nth(i); + const [infoName, infoValue] = expectedInfo[i]; + await expect(row.getByText(infoName)).toHaveCount(1); + await expect(row.getByText(infoValue)).toHaveCount(1); + } + + // Check info displayed around the experiment bar + const elapsedTime = await page.getByText('Elapsed time: 0:41:00'); + const sumOfTrialsTime = await page.getByText('Sum of trials time: 1:20:00'); + const eta = await page.getByText('ETA: 2:44:00 @ '); + const progress = await page.getByText('Progress: 21 %'); + await expect(elapsedTime).toHaveCount(1); + await expect(sumOfTrialsTime).toHaveCount(1); + await expect(eta).toHaveCount(1); + await expect(progress).toHaveCount(1); + + // Check tooltips + const tooltipElapsedTime = await elapsedTime.locator('.bx--tooltip__label'); + await expect(tooltipElapsedTime).toHaveCount(1); + await expect( + await page.getByText( + 'Time elapsed since the beginning of the HPO execution' + ) + ).toHaveCount(0); + await tooltipElapsedTime.click(); + await expect( + await page.getByText( + 'Time elapsed since the beginning of the HPO execution' + ) + ).toHaveCount(1); + await tooltipElapsedTime.click(); + await expect( + await page.getByText( + 'Time elapsed since the beginning of the HPO execution' + ) + ).toHaveCount(0); + + const tooltipTrialsTime = await sumOfTrialsTime.locator( + '.bx--tooltip__label' + ); + await expect(tooltipTrialsTime).toHaveCount(1); + await tooltipTrialsTime.click(); + await expect( + await page.getByText('Sum of trials execution time') + ).toHaveCount(1); + await tooltipTrialsTime.click(); + + const tooltipETA = await eta.locator('.bx--tooltip__label'); + await expect(tooltipETA).toHaveCount(1); + await tooltipETA.click(); + await expect( + await page.getByText('Estimated time for experiment to finish') + ).toHaveCount(1); + await tooltipETA.click(); + + const tooltipProgress = await progress.locator('.bx--tooltip__label'); + await expect(tooltipProgress).toHaveCount(1); + await tooltipProgress.click(); + await expect( + await page.getByText('Experiment progression percentage') + ).toHaveCount(1); + await tooltipProgress.click(); + + // Check progress bar legend + const legend = await page.locator('.experiment-legend'); + await expect(await legend.getByText('Completed (40)')).toHaveCount(1); + await expect(await legend.getByText('Suspended (20)')).toHaveCount(1); + await expect(await legend.getByText('Interrupted (15)')).toHaveCount(1); + await expect(await legend.getByText('Broken (10)')).toHaveCount(1); + await expect(await legend.getByText('Reserved (25)')).toHaveCount(1); + + // Check progress bar itself + const bar = await page.locator( + '.database-container .experiment-progress-bar .main-bar .progress' + ); + await expect(bar).toHaveCount(1); + await checkNormalBars(bar, [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ]); + + // Check table filtering + const subBarCompleted = await bar.locator('.bg-success'); + const subBarSuspended = await bar.locator('.bg-suspended'); + const table = await page.locator('.bx--data-table-content'); + await expect(await subBarCompleted).toHaveCount(1); + await expect(await subBarSuspended).toHaveCount(1); + await expect(table).toHaveCount(1); + + // Check default vue + await expect(await table.getByText('completed')).toHaveCount(5); + await expect(await table.getByText('reserved')).toHaveCount(2); + await expect(await table.getByText('new')).toHaveCount(2); + await expect(await table.getByText('suspended')).toHaveCount(1); + + // Click on completed bar + await subBarCompleted.click(); + await expect(await table.getByText('completed')).toHaveCount(10); + await expect(await table.getByText('reserved')).toHaveCount(0); + await expect(await table.getByText('new')).toHaveCount(0); + await expect(await table.getByText('suspended')).toHaveCount(0); + + // Re-click to de-select + await subBarCompleted.click(); + await expect(await table.getByText('completed')).toHaveCount(5); + await expect(await table.getByText('reserved')).toHaveCount(2); + await expect(await table.getByText('new')).toHaveCount(2); + await expect(await table.getByText('suspended')).toHaveCount(1); + + // Click on suspended bar + await subBarSuspended.click(); + await expect(await table.getByText('completed')).toHaveCount(0); + await expect(await table.getByText('reserved')).toHaveCount(0); + await expect(await table.getByText('new')).toHaveCount(0); + await expect(await table.getByText('suspended')).toHaveCount(10); + + // Check we switch to completed vue if we immediately click to completed bar + await subBarCompleted.click(); + await expect(await table.getByText('completed')).toHaveCount(10); + await expect(await table.getByText('reserved')).toHaveCount(0); + await expect(await table.getByText('new')).toHaveCount(0); + await expect(await table.getByText('suspended')).toHaveCount(0); + }); + + test('Test uncompleted_max_trials_0', async ({ page }) => { + const experiment = await page.getByText('uncompleted_max_trials_0'); + await experiment.click(); + await waitForExperimentToBeLoaded(page, 'uncompleted_max_trials_0'); + + // Check max trials == 0 + const infoRows = await page.locator('.experiment-info .bx--row'); + const row = await page.locator('.experiment-info .bx--row:nth-child(6)'); + await expect(row.getByText('Max trials')).toHaveCount(1); + await expect(row.getByText('0')).toHaveCount(1); + + // Check info displayed around the experiment bar + const eta = await page.getByText('ETA: (unknown)'); + const progress = await page.getByText('Progress: 100 %'); + await expect(eta).toHaveCount(1); + await expect(progress).toHaveCount(1); + + // Check progress bar itself + const bar = await page.locator( + '.database-container .experiment-progress-bar .main-bar .progress' + ); + await expect(bar).toHaveCount(1); + await checkNormalBars(bar, [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ]); + }); + + test('Test uncompleted_max_trials_infinite', async ({ page }) => { + const experiment = await page.getByText('uncompleted_max_trials_infinite'); + await experiment.click(); + await waitForExperimentToBeLoaded(page, 'uncompleted_max_trials_infinite'); + + // Check info displayed around the experiment bar + const eta = await page.getByText('ETA: (unknown)'); + const progress = await page.getByText('Progress: (unknown)'); + await expect(eta).toHaveCount(1); + await expect(progress).toHaveCount(1); + + // Check progress bar itself + const barInfinite = await page.locator( + '.database-container .experiment-progress-bar .main-bar .progress' + ); + await expect(barInfinite).toHaveCount(1); + await expect(await barInfinite.locator('.bg-success')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-suspended')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-warning')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-danger')).toHaveCount(0); + await expect(await barInfinite.locator('.bg-info')).toHaveCount(0); + const subBarRunning = await barInfinite.locator('.bg-running'); + await expect(subBarRunning).toHaveCount(1); + await expect(subBarRunning).toHaveText(/^N\/A$/); + }); + + test('Test uncompleted_max_trials_lt_completed_trials', async ({ page }) => { + const experiment = await page.getByText( + 'uncompleted_max_trials_lt_completed_trials' + ); + await experiment.click(); + await waitForExperimentToBeLoaded( + page, + 'uncompleted_max_trials_lt_completed_trials' + ); + + // Check max trials + const infoRows = await page.locator('.experiment-info .bx--row'); + const row = await page.locator('.experiment-info .bx--row:nth-child(6)'); + console.log(await row.innerText()); + await expect(row.getByText('Max trials')).toHaveCount(1); + await expect(row.getByText('10')).toHaveCount(1); + + // Check number of completed + const legend = await page.locator('.experiment-legend'); + await expect(await legend.getByText('Completed (20)')).toHaveCount(1); + + // Check info displayed around the experiment bar + const progress = await page.getByText('Progress: 100 %'); + await expect(progress).toHaveCount(1); + + // Check progress bar itself + const bar = await page.locator( + '.database-container .experiment-progress-bar .main-bar .progress' + ); + await expect(bar).toHaveCount(1); + await checkNormalBars(bar, [ + StatusToProgress.completed, + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ]); + }); + + test('Test uncompleted_no_completed_trials', async ({ page }) => { + const experiment = await page.getByText('uncompleted_no_completed_trials'); + await experiment.click(); + await waitForExperimentToBeLoaded(page, 'uncompleted_no_completed_trials'); + + // Check number of completed + const legend = await page.locator('.experiment-legend'); + await expect(await legend.getByText('Completed (0)')).toHaveCount(1); + + // Check info displayed around the experiment bar + const elapsedTime = await page.getByText('Elapsed time: 0:00:00'); + const sumOfTrialsTime = await page.getByText('Sum of trials time: 0:00:00'); + const eta = await page.getByText('ETA: ∞'); + const progress = await page.getByText('Progress: 0 %'); + await expect(elapsedTime).toHaveCount(1); + await expect(sumOfTrialsTime).toHaveCount(1); + await expect(eta).toHaveCount(1); + await expect(progress).toHaveCount(1); + + // Check progress bar itself + const bar = await page.locator( + '.database-container .experiment-progress-bar .main-bar .progress' + ); + await expect(bar).toHaveCount(1); + await checkNormalBars(bar, [ + StatusToProgress.reserved, + StatusToProgress.suspended, + StatusToProgress.interrupted, + StatusToProgress.broken, + ]); + }); }); diff --git a/dashboard/src/src/__tests__/flattenObject.test.js b/dashboard/src/src/__tests__/flattenObject.test.js index 2149137c4..fe74ab39a 100644 --- a/dashboard/src/src/__tests__/flattenObject.test.js +++ b/dashboard/src/src/__tests__/flattenObject.test.js @@ -1,3 +1,4 @@ +import { test, expect } from '@playwright/test'; import { flattenObject } from '../utils/flattenObject'; test('test flatten object', () => { diff --git a/dashboard/src/src/__tests__/queryServer.test.js b/dashboard/src/src/__tests__/queryServer.test.js index a466a9a7b..c60fd2544 100644 --- a/dashboard/src/src/__tests__/queryServer.test.js +++ b/dashboard/src/src/__tests__/queryServer.test.js @@ -1,3 +1,4 @@ +import { test, expect } from '@playwright/test'; import { Backend, DEFAULT_BACKEND } from '../utils/queryServer'; test('test backend call', () => { @@ -15,7 +16,7 @@ test('test backend query experiments', () => { expect.assertions(8); const backend = new Backend(DEFAULT_BACKEND); return backend.query('experiments').then(response => { - expect(response.length).toBe(111); + expect(response.length).toBe(116); const experiments = new Set(response.map(experiment => experiment.name)); expect(experiments.has('2-dim-shape-exp')).toBeTruthy(); expect(experiments.has('4-dim-cat-shape-exp')).toBeTruthy(); diff --git a/dashboard/src/src/experiments/Experiments.js b/dashboard/src/src/experiments/Experiments.js index d2a8f80ac..da187c47d 100644 --- a/dashboard/src/src/experiments/Experiments.js +++ b/dashboard/src/src/experiments/Experiments.js @@ -38,15 +38,15 @@ class Experiments extends Component { renderPage() { switch (this.props.match.params.page || 'landing') { case 'landing': - return ; + return ; case 'status': - return ; + return ; case 'visualizations': - return ; + return ; case 'database': - return ; + return ; case 'configuration': - return ; + return ; default: break; } diff --git a/dashboard/src/src/experiments/components/ExperimentNavBar/ExperimentNavBar.js b/dashboard/src/src/experiments/components/ExperimentNavBar/ExperimentNavBar.js index 1fb19cfa6..5d8a33f24 100644 --- a/dashboard/src/src/experiments/components/ExperimentNavBar/ExperimentNavBar.js +++ b/dashboard/src/src/experiments/components/ExperimentNavBar/ExperimentNavBar.js @@ -1,7 +1,8 @@ import React from 'react'; -import ProgressBar from 'react-bootstrap/ProgressBar'; import { Backend } from '../../../utils/queryServer'; import { BackendContext } from '../../BackendContext'; +import { ExperimentStatusBar } from '../ExperimentStatusBar/ExperimentStatusBar'; +import InfiniteScroll from 'react-infinite-scroller'; import { SideNav, @@ -23,9 +24,14 @@ export class ExperimentNavBar extends React.Component { this.state = { experiments: null, search: '', + message: 'Loading experiments ...', + filteredExperiments: [], + renderedExperiments: [], }; this.onSearch = this.onSearch.bind(this); this.onSwitchSelect = this.onSwitchSelect.bind(this); + this.loadMoreExperiments = this.loadMoreExperiments.bind(this); + this.hasMoreExperimentToLoad = this.hasMoreExperimentToLoad.bind(this); } render() { return ( @@ -36,19 +42,29 @@ export class ExperimentNavBar extends React.Component { isChildOfHeader={false} aria-label="Side navigation">
- - - - - Experiment - - Status - - - - {this.renderExperimentsList()} - - + this.loadMoreExperiments(page)} + hasMore={this.hasMoreExperimentToLoad()} + useWindow={false} + threshold={5} + initialLoad={true}> + + + + + Experiment + + Status + + + + {this.state.message !== null + ? this.renderMessageRow(this.state.message) + : this.renderExperimentsList(this.state.renderedExperiments)} + + +
); } - renderExperimentsList() { - if (this.state.experiments === null) - return this.renderMessageRow('Loading experiments ...'); - if (!this.state.experiments.length) - return this.renderMessageRow('No experiment available'); - // Apply search. - let experiments; - if (this.state.search.length) { - // String to search - experiments = this.state.experiments.filter( - experiment => experiment.toLowerCase().indexOf(this.state.search) >= 0 - ); - if (!experiments.length) - return this.renderMessageRow('No matching experiment'); - } else { - // No string to search, display all experiments - experiments = this.state.experiments; - } + renderExperimentsList(experiments) { return experiments.map(experiment => ( {experiment} - - - - - - + )); @@ -131,21 +125,67 @@ export class ExperimentNavBar extends React.Component { const experiments = results.map(experiment => experiment.name); experiments.sort(); if (this._isMounted) { - this.setState({ experiments }); + this._updateInitialExperiments(experiments); } }) .catch(error => { if (this._isMounted) { - this.setState({ experiments: [] }); + this._updateInitialExperiments([]); } }); } + _updateInitialExperiments(experiments) { + if (!experiments.length) { + this.setState({ + experiments, + search: '', + message: 'No experiment available', + }); + } else { + this.setState({ + experiments, + search: '', + message: null, + filteredExperiments: experiments, + }); + } + } componentWillUnmount() { this._isMounted = false; } - onSearch(event) { - this.setState({ search: (event.target.value || '').toLowerCase() }); + const search = (event.target.value || '').toLowerCase(); + if (this.state.experiments === null || !this.state.experiments.length) { + this.setState({ search }); + } else if (search.length) { + // Apply search. + const experiments = this.state.experiments.filter( + experiment => experiment.toLowerCase().indexOf(search) >= 0 + ); + if (!experiments.length) { + this.setState({ + search, + message: 'No matching experiment', + filteredExperiments: [], + renderedExperiments: [], + }); + } else { + this.setState({ + search, + message: null, + filteredExperiments: experiments, + renderedExperiments: [], + }); + } + } else { + // No string to search, display all experiments + this.setState({ + search, + message: null, + filteredExperiments: this.state.experiments, + renderedExperiments: [], + }); + } } onSwitchSelect(event, experiment, inputID) { // Prevent default behavior, as we entirely handle click here. @@ -154,6 +194,25 @@ export class ExperimentNavBar extends React.Component { document.getElementById(inputID).checked = toBeSelected; this.props.onSelectExperiment(toBeSelected ? experiment : null); } + hasMoreExperimentToLoad() { + return ( + this.state.renderedExperiments.length < + this.state.filteredExperiments.length + ); + } + loadMoreExperiments(page) { + console.log( + `Loading experiment ${this.state.renderedExperiments.length + 1} / ${ + this.state.filteredExperiments.length + } (scrolling iteration ${page})` + ); + this.setState({ + renderedExperiments: this.state.filteredExperiments.slice( + 0, + this.state.renderedExperiments.length + 1 + ), + }); + } } export default ExperimentNavBar; diff --git a/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusBar.js b/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusBar.js new file mode 100644 index 000000000..e906972be --- /dev/null +++ b/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusBar.js @@ -0,0 +1,269 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ProgressBar from 'react-bootstrap/ProgressBar'; +import { Column, Grid, Row, Tooltip } from 'carbon-components-react'; +import { EXPERIMENT_STATUS_PROVIDER } from './ExperimentStatusProvider'; + +function floatToPercent(value) { + return Math.round(value * 100); +} + +export const StatusToProgress = { + new: 'new', // NB: user-made / we don't need to display trial status "new" in progress bar + reserved: 'info', + suspended: 'suspended', // user-made + completed: 'success', + interrupted: 'warning', + broken: 'danger', +}; + +/** + * Format date to locale in format "YYYY/MM/DD HH:MM:SS" + * @param date {Date} + */ +function formatDate(date) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hour = date.getHours(); + const mins = date.getMinutes(); + const secs = date.getSeconds(); + return `${year}/${month + .toString() + .padStart(2, '0')}/${day + .toString() + .padStart(2, '0')} ${hour + .toString() + .padStart(2, '0')}:${mins + .toString() + .padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; +} + +export class ExperimentStatusBar extends React.Component { + _isMounted = false; + constructor(props) { + super(props); + this.state = { status: null }; + this.onFocus = this.onFocus.bind(this); + } + render() { + if (this.state.status === null) + return ( +
+ +
+ ); + if (this.state.status === false) + return ( +
+ +
+ ); + return ( +
+ {this.props.withInfo ? this.renderExperimentInfo() : ''} + {this.props.withInfo ? ( + + + + Elapsed time:  + {this.state.status.elapsed_time} + + Time elapsed since the beginning of the HPO execution + + + + Sum of trials time:  + {this.state.status.sum_of_trials_time} + Sum of trials execution time + + + ETA:  + + {this.state.status.eta === null + ? '(unknown)' + : this.state.status.eta === 0 + ? 0 + : this.state.status.eta === 'infinite' + ? '\u221E' + : `${this.state.status.eta} @ ${formatDate( + new Date( + new Date().getTime() + + this.state.status.eta_milliseconds + ) + )}`} + + Estimated time for experiment to finish + + + + ) : ( + '' + )} +
+ {this.state.status.max_trials === 'infinite' ? ( + + ) : ( + + {[ + 'completed', + 'suspended', + 'interrupted', + 'broken', + 'reserved', + ].map((trialStatus, i) => + this.renderProgressPart(trialStatus, i) + )} + + )} +
+ {this.props.withInfo ? ( + + + + {[ + 'completed', + 'suspended', + 'interrupted', + 'broken', + 'reserved', + ].map((trialStatus, i) => + this.renderLegendPart(trialStatus, i) + )} + + + Progress:  + + {this.state.status.progress === null + ? '(unknown)' + : `${floatToPercent(this.state.status.progress)} %`} + + Experiment progression percentage + + + + ) : ( + '' + )} +
+ ); + } + renderExperimentInfo() { + const stats = this.state.status; + const rows = [ + { + title: 'Best trial ID', + value: + stats.best_trials_id === null ? '(unknown)' : stats.best_trials_id, + }, + { + title: 'Best evaluation', + value: + stats.best_evaluation === null ? '(unknown)' : stats.best_evaluation, + }, + { title: 'Start time', value: stats.start_time }, + { title: 'Finish time', value: stats.finish_time }, + { title: 'Trials', value: stats.nb_trials }, + { + title: 'Max trials', + value: stats.max_trials === 'infinite' ? '\u221E' : stats.max_trials, + }, + ]; + return ( +
+

Experiment "{this.props.name}"

+ + {rows.map((row, indexRow) => ( + + + {row.title} + + {row.value} + + ))} + +
+ ); + } + renderProgressPart(trialStatus, key) { + const stats = this.state.status; + const progressBase = Math.max(stats.max_trials, stats.nb_trials); + return ( + this.onFocus(trialStatus)} + striped={this.props.focus === trialStatus} + /> + ); + } + renderLegendPart(trialStatus, key) { + return ( +
+ +
+ {trialStatus.charAt(0).toUpperCase()} + {trialStatus.slice(1)} ( + {this.state.status.trial_status_count[trialStatus] || 0}) +
+
+ ); + } + componentDidMount() { + this._isMounted = true; + EXPERIMENT_STATUS_PROVIDER.get(this.props.name) + .then(status => { + if (this._isMounted) { + this.setState({ status }); + } + }) + .catch(error => { + console.error(error); + if (this._isMounted) { + this.setState({ status: false }); + } + }); + } + componentWillUnmount() { + this._isMounted = false; + } + onFocus(trialStatus) { + if (this.props.onFocus) { + this.props.onFocus(trialStatus); + } + } +} +ExperimentStatusBar.propTypes = { + name: PropTypes.string.isRequired, + withInfo: PropTypes.bool, + focus: PropTypes.string, + onFocus: PropTypes.func, +}; diff --git a/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusProvider.js b/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusProvider.js new file mode 100644 index 000000000..935fbf853 --- /dev/null +++ b/dashboard/src/src/experiments/components/ExperimentStatusBar/ExperimentStatusProvider.js @@ -0,0 +1,51 @@ +import { Backend, DEFAULT_BACKEND } from '../../../utils/queryServer'; + +/** + * Helper class to provide and cache experiment status. + */ +class ExperimentStatusProvider { + constructor() { + this.backend = new Backend(DEFAULT_BACKEND); + this.resolving = new Map(); + this.resolved = new Map(); + } + + get(experiment) { + if (this.resolved.has(experiment)) { + console.log(`Already resolved: ${experiment}`); + return new Promise((resolve, reject) => { + const resolution = this.resolved.get(experiment); + if (resolution.error) { + reject(resolution.error); + } else { + resolve(resolution.data); + } + }); + } else if (this.resolving.has(experiment)) { + console.log(`Still resolving: ${experiment}`); + return this.resolving.get(experiment); + } else { + console.log(`To resolve: ${experiment}`); + this.resolving.set( + experiment, + new Promise((resolve, reject) => { + this.backend + .query(`experiments/status/${experiment}`) + .then(status => { + this.resolving.delete(experiment); + this.resolved.set(experiment, { error: null, data: status }); + resolve(status); + }) + .catch(error => { + this.resolving.delete(experiment); + this.resolved.set(experiment, { error, data: null }); + reject(error); + }); + }) + ); + return this.resolving.get(experiment); + } + } +} + +export const EXPERIMENT_STATUS_PROVIDER = new ExperimentStatusProvider(); diff --git a/dashboard/src/src/experiments/content/DatabasePage/DatabasePage.js b/dashboard/src/src/experiments/content/DatabasePage/DatabasePage.js index 746dafa96..c31c048c2 100644 --- a/dashboard/src/src/experiments/content/DatabasePage/DatabasePage.js +++ b/dashboard/src/src/experiments/content/DatabasePage/DatabasePage.js @@ -3,6 +3,7 @@ import { DEFAULT_BACKEND } from '../../../utils/queryServer'; import { FeaturedTable } from './FeaturedTable'; import { BackendContext } from '../../BackendContext'; import { TrialsProvider } from './TrialsProvider'; +import { ExperimentStatusBar } from '../../components/ExperimentStatusBar/ExperimentStatusBar'; /** * Singleton to provide experiment trials. @@ -16,7 +17,8 @@ class DatabasePage extends React.Component { static contextType = BackendContext; constructor(props) { super(props); - this.state = { experiment: null, trials: null }; + this.state = { experiment: null, trials: null, trialStatus: null }; + this.onSelectTrialStatus = this.onSelectTrialStatus.bind(this); } render() { if (this.state.experiment === null) @@ -26,16 +28,26 @@ class DatabasePage extends React.Component { if (this.state.trials === false) return `Unable to load trials for experiment "${this.state.experiment}".`; return ( -
-
-
- -
-
+
+ + trial.status === this.state.trialStatus + ) + } + experiment={this.state.experiment} + trialStatus={this.state.trialStatus} + nbTrials={this.state.trials.trials.length} + />
); } @@ -76,6 +88,12 @@ class DatabasePage extends React.Component { }); }); } + onSelectTrialStatus(trialStatus) { + console.log(`Db page on focus ${trialStatus}`); + this.setState({ + trialStatus: this.state.trialStatus === trialStatus ? null : trialStatus, + }); + } } export default DatabasePage; diff --git a/dashboard/src/src/experiments/content/DatabasePage/FeaturedTable.js b/dashboard/src/src/experiments/content/DatabasePage/FeaturedTable.js index 230b67ed8..4a33a0702 100644 --- a/dashboard/src/src/experiments/content/DatabasePage/FeaturedTable.js +++ b/dashboard/src/src/experiments/content/DatabasePage/FeaturedTable.js @@ -160,7 +160,13 @@ const ModalStateManager = ({ ); }; -export function FeaturedTable({ columns, data, experiment }) { +export function FeaturedTable({ + columns, + data, + experiment, + trialStatus, + nbTrials, +}) { const defaultColumnOrder = []; collectLeafColumnIndices(columns, defaultColumnOrder); const [columnOrder, setColumnOrder] = React.useState(defaultColumnOrder); @@ -246,7 +252,10 @@ export function FeaturedTable({ columns, data, experiment }) { Experiment Trials for "{experimentWords}"

- {data.length} trial(s) for experiment "{experimentWords}" + {nbTrials} trial(s) for experiment "{experimentWords}" + {trialStatus && nbTrials !== data.length + ? `, ${data.length} displayed for status "${trialStatus}"` + : ''}

diff --git a/dashboard/src/src/experiments/content/DatabasePage/TrialTable.js b/dashboard/src/src/experiments/content/DatabasePage/TrialTable.js deleted file mode 100644 index 084b758e0..000000000 --- a/dashboard/src/src/experiments/content/DatabasePage/TrialTable.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { - DataTable, - TableContainer, - Table, - TableHead, - TableRow, - TableHeader, - TableBody, - TableCell, -} from 'carbon-components-react'; - -const TrialTable = ({ rows, headers, sortable, experiment }) => { - return ( - ( - - - - - {headers.map((header, indexHeader) => ( - - {header.header} - - ))} - - - - {rows.map(row => ( - - {row.cells.map((cell, cellIndex) => ( - - {cellIndex === 0 && cell.value.length > 7 ? ( - - {cell.value.substr(0, 7)}... - - ) : ( - cell.value - )} - - ))} - - ))} - -
-
- )} - /> - ); -}; - -export default TrialTable; diff --git a/dashboard/src/src/experiments/content/DatabasePage/TrialsProvider.js b/dashboard/src/src/experiments/content/DatabasePage/TrialsProvider.js index 902d34e3b..399ac9513 100644 --- a/dashboard/src/src/experiments/content/DatabasePage/TrialsProvider.js +++ b/dashboard/src/src/experiments/content/DatabasePage/TrialsProvider.js @@ -113,6 +113,11 @@ export class TrialsProvider { accessorKey: 'endTime', header: 'End time', }, + { + id: 'status', + accessorKey: 'status', + header: 'Status', + }, { id: 'objective', accessorKey: 'objective', diff --git a/dashboard/src/src/style.css b/dashboard/src/src/style.css index 61d0b653a..13781c022 100644 --- a/dashboard/src/src/style.css +++ b/dashboard/src/src/style.css @@ -71,3 +71,14 @@ a.bx--header__menu-item.bx--header__menu-title[aria-label='benchmarks (selected) .modal-trial-key { text-align: right; } +.progress-bar.bg-running { + background-color: lightgray; + color: darkslategrey; +} +.progress-bar.bg-suspended { + background-color: pink; +} +.database-container { + padding-left: 2.5rem; + padding-right: 2.5rem; +} diff --git a/dashboard/src/yarn.lock b/dashboard/src/yarn.lock index 709d16e3a..3b9a384e5 100644 --- a/dashboard/src/yarn.lock +++ b/dashboard/src/yarn.lock @@ -6,9 +6,9 @@ __metadata: cacheKey: 8 "@adobe/css-tools@npm:^4.0.1": - version: 4.0.1 - resolution: "@adobe/css-tools@npm:4.0.1" - checksum: 80226e2229024c21da9ffa6b5cd4a34b931f071e06f45aba4777ade071d7a6c94605cf73b13718b0c4b34e8b124c65c607b82eaa53a326d3eb73d9682a04a593 + version: 4.1.0 + resolution: "@adobe/css-tools@npm:4.1.0" + checksum: 50d0098461af146e112bdc700959fac473cce31ad25f5077b11c87bc66529c4aeb5a1e6f30f4a9646efe003c242f52f181f042e21f9cc137703ffde54a9d1fb5 languageName: node linkType: hard @@ -44,33 +44,33 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.0, @babel/compat-data@npm:^7.20.1": - version: 7.20.1 - resolution: "@babel/compat-data@npm:7.20.1" - checksum: 989b9b7a6fe43c547bb8329241bd0ba6983488b83d29cc59de35536272ee6bb4cc7487ba6c8a4bceebb3a57f8c5fea1434f80bbbe75202bc79bc1110f955ff25 +"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.1, @babel/compat-data@npm:^7.20.5": + version: 7.20.14 + resolution: "@babel/compat-data@npm:7.20.14" + checksum: 6c9efe36232094e4ad0b70d165587f21ca718e5d011f7a52a77a18502a7524e90e2855aa5a2e086395bcfd21bd2c7c99128dcd8d9fdffe94316b72acf5c66f2c languageName: node linkType: hard "@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.1, @babel/core@npm:^7.12.3, @babel/core@npm:^7.16.0, @babel/core@npm:^7.7.2, @babel/core@npm:^7.8.0": - version: 7.20.2 - resolution: "@babel/core@npm:7.20.2" + version: 7.20.12 + resolution: "@babel/core@npm:7.20.12" dependencies: "@ampproject/remapping": ^2.1.0 "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.20.2 - "@babel/helper-compilation-targets": ^7.20.0 - "@babel/helper-module-transforms": ^7.20.2 - "@babel/helpers": ^7.20.1 - "@babel/parser": ^7.20.2 - "@babel/template": ^7.18.10 - "@babel/traverse": ^7.20.1 - "@babel/types": ^7.20.2 + "@babel/generator": ^7.20.7 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helpers": ^7.20.7 + "@babel/parser": ^7.20.7 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.12 + "@babel/types": ^7.20.7 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 - json5: ^2.2.1 + json5: ^2.2.2 semver: ^6.3.0 - checksum: 98faaaef26103a276a30a141b951a93bc8418d100d1f668bf7a69d12f3e25df57958e8b6b9100d95663f720db62da85ade736f6629a5ebb1e640251a1b43c0e4 + checksum: 62e6c3e2149a70b5c9729ef5f0d3e2e97e9dcde89fc039c8d8e3463d5d7ba9b29ee84d10faf79b61532ac1645aa62f2bd42338320617e6e3a8a4d8e2a27076e7 languageName: node linkType: hard @@ -88,14 +88,14 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.1, @babel/generator@npm:^7.20.2, @babel/generator@npm:^7.7.2": - version: 7.20.4 - resolution: "@babel/generator@npm:7.20.4" +"@babel/generator@npm:^7.20.7, @babel/generator@npm:^7.7.2": + version: 7.20.14 + resolution: "@babel/generator@npm:7.20.14" dependencies: - "@babel/types": ^7.20.2 + "@babel/types": ^7.20.7 "@jridgewell/gen-mapping": ^0.3.2 jsesc: ^2.5.1 - checksum: 967b59f18e5ce999e5a741825bcecb2be4bbfc1824a92c21b47d0b5694e0eb09314a70f8b9142e9591c149c7fb83d51f73ae8fbd96d30a42666425889e51ceb1 + checksum: 5f6aa2d86af26e76d276923a5c34191124a119b16ee9ccc34aef654a7dec84fbd7d2daed2e6458a6a06bf87f3661deb77c9fea59b8f67faff5c90793c96d76d6 languageName: node linkType: hard @@ -118,46 +118,48 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.0": - version: 7.20.0 - resolution: "@babel/helper-compilation-targets@npm:7.20.0" +"@babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.0, @babel/helper-compilation-targets@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-compilation-targets@npm:7.20.7" dependencies: - "@babel/compat-data": ^7.20.0 + "@babel/compat-data": ^7.20.5 "@babel/helper-validator-option": ^7.18.6 browserslist: ^4.21.3 + lru-cache: ^5.1.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: bc183f2109648849c8fde0b3c5cf08adf2f7ad6dc617b546fd20f34c8ef574ee5ee293c8d1bd0ed0221212e8f5907cdc2c42097870f1dcc769a654107d82c95b + checksum: 8c32c873ba86e2e1805b30e0807abd07188acbe00ebb97576f0b09061cc65007f1312b589eccb4349c5a8c7f8bb9f2ab199d41da7030bf103d9f347dcd3a3cf4 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-create-class-features-plugin@npm:7.20.2" +"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.20.12, @babel/helper-create-class-features-plugin@npm:^7.20.5, @babel/helper-create-class-features-plugin@npm:^7.20.7": + version: 7.20.12 + resolution: "@babel/helper-create-class-features-plugin@npm:7.20.12" dependencies: "@babel/helper-annotate-as-pure": ^7.18.6 "@babel/helper-environment-visitor": ^7.18.9 "@babel/helper-function-name": ^7.19.0 - "@babel/helper-member-expression-to-functions": ^7.18.9 + "@babel/helper-member-expression-to-functions": ^7.20.7 "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/helper-replace-supers": ^7.19.1 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 "@babel/helper-split-export-declaration": ^7.18.6 peerDependencies: "@babel/core": ^7.0.0 - checksum: e89a8841db3f6340996f395fc372ee4bec361230eb9345b763314f768e68421d43461918fdedfb9a69b71f1d0433439f3e318d1b1b9ba04fbd7aac1c84959e37 + checksum: 1e9ed4243b75278fa24deb40dc62bf537b79307987223a2d2d2ae5abf7ba6dc8435d6e3bb55d52ceb30d3e1eba88e7eb6a1885a8bb519e5cfc3e9dedb97d43e6 languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.19.0" +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.20.5": + version: 7.20.5 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.20.5" dependencies: "@babel/helper-annotate-as-pure": ^7.18.6 - regexpu-core: ^5.1.0 + regexpu-core: ^5.2.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: 811cc90afe9fc25a74ed37fc0c1361a4a91b0b940235dd3958e3f03b366d40a903b40fc93b51bcb93be774aba573219f8f215664bea1d1301f58797ca6854f3f + checksum: 7f29c3cb7447cca047b0d394f8ab98e4923d00e86a7afa56e5df9770c48ec107891505d2d1f06b720ecc94ed24bf58d90986cc35fe4a43b549eb7b7a5077b693 languageName: node linkType: hard @@ -212,12 +214,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-member-expression-to-functions@npm:7.18.9" +"@babel/helper-member-expression-to-functions@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.20.7" dependencies: - "@babel/types": ^7.18.9 - checksum: fcf8184e3b55051c4286b2cbedf0eccc781d0f3c9b5cbaba582eca19bf0e8d87806cdb7efc8554fcb969ceaf2b187d5ea748d40022d06ec7739fbb18c1b19a7a + "@babel/types": ^7.20.7 + checksum: cec17aab7e964830b0146e575bd141127032319f26ed864a65b35abd75ad618d264d3e11449b9b4e29cfd95bb1a7e774afddd4884fdcc29c36ac9cbd2b66359f languageName: node linkType: hard @@ -230,19 +232,19 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.19.6, @babel/helper-module-transforms@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-module-transforms@npm:7.20.2" +"@babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.20.11": + version: 7.20.11 + resolution: "@babel/helper-module-transforms@npm:7.20.11" dependencies: "@babel/helper-environment-visitor": ^7.18.9 "@babel/helper-module-imports": ^7.18.6 "@babel/helper-simple-access": ^7.20.2 "@babel/helper-split-export-declaration": ^7.18.6 "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.18.10 - "@babel/traverse": ^7.20.1 - "@babel/types": ^7.20.2 - checksum: 33a60ca115f6fce2c9d98e2a2e5649498aa7b23e2ae3c18745d7a021487708fc311458c33542f299387a0da168afccba94116e077f2cce49ae9e5ab83399e8a2 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.10 + "@babel/types": ^7.20.7 + checksum: 29319ebafa693d48756c6ba0d871677bb0037e0da084fbe221a17c38d57093fc8aa38543c07d76e788266a937976e37ab4901971ca7f237c5ab45f524b9ecca0 languageName: node linkType: hard @@ -262,7 +264,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.18.6, @babel/helper-remap-async-to-generator@npm:^7.18.9": +"@babel/helper-remap-async-to-generator@npm:^7.18.9": version: 7.18.9 resolution: "@babel/helper-remap-async-to-generator@npm:7.18.9" dependencies: @@ -276,20 +278,21 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/helper-replace-supers@npm:7.19.1" +"@babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-replace-supers@npm:7.20.7" dependencies: "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-member-expression-to-functions": ^7.18.9 + "@babel/helper-member-expression-to-functions": ^7.20.7 "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/traverse": ^7.19.1 - "@babel/types": ^7.19.0 - checksum: a0e4bf79ebe7d2bb5947169e47a0b4439c73fb0ec57d446cf3ea81b736721129ec373c3f94d2ebd2716b26dd65f8e6c083dac898170d42905e7ba815a2f52c25 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: b8e0087c9b0c1446e3c6f3f72b73b7e03559c6b570e2cfbe62c738676d9ebd8c369a708cf1a564ef88113b4330750a50232ee1131d303d478b7a5e65e46fbc7c languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.19.4, @babel/helper-simple-access@npm:^7.20.2": +"@babel/helper-simple-access@npm:^7.20.2": version: 7.20.2 resolution: "@babel/helper-simple-access@npm:7.20.2" dependencies: @@ -298,7 +301,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.18.9": +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": version: 7.20.0 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" dependencies: @@ -338,25 +341,25 @@ __metadata: linkType: hard "@babel/helper-wrap-function@npm:^7.18.9": - version: 7.19.0 - resolution: "@babel/helper-wrap-function@npm:7.19.0" + version: 7.20.5 + resolution: "@babel/helper-wrap-function@npm:7.20.5" dependencies: "@babel/helper-function-name": ^7.19.0 "@babel/template": ^7.18.10 - "@babel/traverse": ^7.19.0 - "@babel/types": ^7.19.0 - checksum: 2453a6b134f12cc779179188c4358a66252c29b634a8195c0cf626e17f9806c3c4c40e159cd8056c2ec82b69b9056a088014fa43d6ccc1aca67da8d9605da8fd + "@babel/traverse": ^7.20.5 + "@babel/types": ^7.20.5 + checksum: 11a6fc28334368a193a9cb3ad16f29cd7603bab958433efc82ebe59fa6556c227faa24f07ce43983f7a85df826f71d441638442c4315e90a554fe0a70ca5005b languageName: node linkType: hard -"@babel/helpers@npm:^7.20.1": - version: 7.20.1 - resolution: "@babel/helpers@npm:7.20.1" +"@babel/helpers@npm:^7.20.7": + version: 7.20.13 + resolution: "@babel/helpers@npm:7.20.13" dependencies: - "@babel/template": ^7.18.10 - "@babel/traverse": ^7.20.1 - "@babel/types": ^7.20.0 - checksum: be35f78666bdab895775ed94dbeb098f7b4fa08ce4cfb0c3a9e69b7220cce56960dcdc2b14f5df9d3b80388d4bf7df155c97f6cf6768c0138f4e6931d0f44955 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.13 + "@babel/types": ^7.20.7 + checksum: d62076fa834f342798f8c3fd7aec0870cc1725d273d99e540cbaa8d6c3ed10258228dd14601c8e66bfeabbb9424c3b31090ecc467fe855f7bd72c4734df7fb09 languageName: node linkType: hard @@ -371,12 +374,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.18.10, @babel/parser@npm:^7.20.1, @babel/parser@npm:^7.20.2": - version: 7.20.3 - resolution: "@babel/parser@npm:7.20.3" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.13, @babel/parser@npm:^7.20.7": + version: 7.20.15 + resolution: "@babel/parser@npm:7.20.15" bin: parser: ./bin/babel-parser.js - checksum: 33bcdb45de65a3cf27ed376cb34f32be3c3485a10e3252f8d0126f6a034efc3145c0d219e57fcd5a8956361552008bc30b9bae4a723823fb3633027071be8a45 + checksum: 1d0f47ca67ff2652f1c0ff1570bed8deccbc4b53509e7cd73476af9cc7ed23480c99f1179bd6d0be01612368b92b39e206d330ad6054009d699934848a89298b languageName: node linkType: hard @@ -392,29 +395,29 @@ __metadata: linkType: hard "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.18.9" + version: 7.20.7 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - "@babel/helper-skip-transparent-expression-wrappers": ^7.18.9 - "@babel/plugin-proposal-optional-chaining": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-proposal-optional-chaining": ^7.20.7 peerDependencies: "@babel/core": ^7.13.0 - checksum: 93abb5cb179a13db171bfc2cdf79489598f43c50cc174f97a2b7bb1d44d24ade7109665a20cf4e317ad6c1c730f036f06478f7c7e789b4240be1abdb60d6452f + checksum: d610f532210bee5342f5b44a12395ccc6d904e675a297189bc1e401cc185beec09873da523466d7fec34ae1574f7a384235cba1ccc9fe7b89ba094167897c845 languageName: node linkType: hard "@babel/plugin-proposal-async-generator-functions@npm:^7.20.1": - version: 7.20.1 - resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.1" + version: 7.20.7 + resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.7" dependencies: "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-plugin-utils": ^7.19.0 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/helper-remap-async-to-generator": ^7.18.9 "@babel/plugin-syntax-async-generators": ^7.8.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 518483a68c5618932109913eb7316ed5e656c575cbd9d22667bc0451e35a1be45f8eaeb8e2065834b36c8a93c4840f78cebf8f1d067b07c422f7be16d58eca60 + checksum: 111109ee118c9e69982f08d5e119eab04190b36a0f40e22e873802d941956eee66d2aa5a15f5321e51e3f9aa70a91136451b987fe15185ef8cc547ac88937723 languageName: node linkType: hard @@ -431,30 +434,30 @@ __metadata: linkType: hard "@babel/plugin-proposal-class-static-block@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-class-static-block@npm:7.18.6" + version: 7.20.7 + resolution: "@babel/plugin-proposal-class-static-block@npm:7.20.7" dependencies: - "@babel/helper-create-class-features-plugin": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.20.7 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-class-static-block": ^7.14.5 peerDependencies: "@babel/core": ^7.12.0 - checksum: b8d7ae99ed5ad784f39e7820e3ac03841f91d6ed60ab4a98c61d6112253da36013e12807bae4ffed0ef3cb318e47debac112ed614e03b403fb8b075b09a828ee + checksum: ce1f3e8fd96437d820aa36323b7b3a0cb65b5f2600612665129880d5a4eb7194ce6a298ed2a5a4d3a9ea49bd33089ab95503c4c5b3ba9cea251a07d1706453d9 languageName: node linkType: hard "@babel/plugin-proposal-decorators@npm:^7.16.4": - version: 7.20.2 - resolution: "@babel/plugin-proposal-decorators@npm:7.20.2" + version: 7.20.13 + resolution: "@babel/plugin-proposal-decorators@npm:7.20.13" dependencies: - "@babel/helper-create-class-features-plugin": ^7.20.2 + "@babel/helper-create-class-features-plugin": ^7.20.12 "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-replace-supers": ^7.19.1 + "@babel/helper-replace-supers": ^7.20.7 "@babel/helper-split-export-declaration": ^7.18.6 "@babel/plugin-syntax-decorators": ^7.19.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: bb00e2172a8c0b169f262222b7ca1a9cda4f7a3bc458bb71b39c9209aa261450c077f38c9b51e79fadd4d1962a993e7fd8712042d82658b1a052a084c53e32cd + checksum: 445723b410627d52ab2d195589eb9fe5fbd66a00ebfc9bedcf63b6cbfdfc42e163d77ac391f8738ab9f632779e6f2aa427fe468fbbd6661177ef0cdca735a7d5 languageName: node linkType: hard @@ -495,14 +498,14 @@ __metadata: linkType: hard "@babel/plugin-proposal-logical-assignment-operators@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.18.9" + version: 7.20.7 + resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: dd87fa4a48c6408c5e85dbd6405a65cc8fe909e3090030df46df90df64cdf3e74007381a58ed87608778ee597eff7395d215274009bb3f5d8964b2db5557754f + checksum: cdd7b8136cc4db3f47714d5266f9e7b592a2ac5a94a5878787ce08890e97c8ab1ca8e94b27bfeba7b0f2b1549a026d9fc414ca2196de603df36fb32633bbdc19 languageName: node linkType: hard @@ -531,17 +534,17 @@ __metadata: linkType: hard "@babel/plugin-proposal-object-rest-spread@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.2" + version: 7.20.7 + resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" dependencies: - "@babel/compat-data": ^7.20.1 - "@babel/helper-compilation-targets": ^7.20.0 + "@babel/compat-data": ^7.20.5 + "@babel/helper-compilation-targets": ^7.20.7 "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-object-rest-spread": ^7.8.3 - "@babel/plugin-transform-parameters": ^7.20.1 + "@babel/plugin-transform-parameters": ^7.20.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9764d1a4735fcd384fdb9b6c6ccb20d1bea2f88f648640d26ce5d9cd5880ce1e389d2f852d7bea7e86ff343726225dc16e1deb92c7b3dc5c5721ed905a602318 + checksum: 1329db17009964bc644484c660eab717cb3ca63ac0ab0f67c651a028d1bc2ead51dc4064caea283e46994f1b7221670a35cbc0b4beb6273f55e915494b5aa0b2 languageName: node linkType: hard @@ -557,16 +560,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.16.0, @babel/plugin-proposal-optional-chaining@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-proposal-optional-chaining@npm:7.18.9" +"@babel/plugin-proposal-optional-chaining@npm:^7.16.0, @babel/plugin-proposal-optional-chaining@npm:^7.18.9, @babel/plugin-proposal-optional-chaining@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-proposal-optional-chaining@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - "@babel/helper-skip-transparent-expression-wrappers": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 "@babel/plugin-syntax-optional-chaining": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f2db40e26172f07c50b635cb61e1f36165de3ba868fcf608d967642f0d044b7c6beb0e7ecf17cbd421144b99e1eae7ad6031ded92925343bb0ed1d08707b514f + checksum: 274b8932335bd064ca24cf1a4da2b2c20c92726d4bfa8b0cb5023857479b8481feef33505c16650c7b9239334e5c6959babc924816324c4cf223dd91c7ca79bc languageName: node linkType: hard @@ -583,16 +586,16 @@ __metadata: linkType: hard "@babel/plugin-proposal-private-property-in-object@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.18.6" + version: 7.20.5 + resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.20.5" dependencies: "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-create-class-features-plugin": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-private-property-in-object": ^7.14.5 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c8e56a972930730345f39f2384916fd8e711b3f4b4eae2ca9740e99958980118120d5cc9b6ac150f0965a5a35f825910e2c3013d90be3e9993ab6111df444569 + checksum: 513b5e0e2c1b2846be5336cf680e932ae17924ef885aa1429e1a4f7924724bdd99b15f28d67187d0a006d5f18a0c4b61d96c3ecb4902fed3c8fe2f0abfc9753a languageName: node linkType: hard @@ -840,26 +843,26 @@ __metadata: linkType: hard "@babel/plugin-transform-arrow-functions@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.18.6" + version: 7.20.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 900f5c695755062b91eec74da6f9092f40b8fada099058b92576f1e23c55e9813ec437051893a9b3c05cefe39e8ac06303d4a91b384e1c03dd8dc1581ea11602 + checksum: b43cabe3790c2de7710abe32df9a30005eddb2050dadd5d122c6872f679e5710e410f1b90c8f99a2aff7b614cccfecf30e7fd310236686f60d3ed43fd80b9847 languageName: node linkType: hard "@babel/plugin-transform-async-to-generator@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.18.6" + version: 7.20.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.20.7" dependencies: "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/helper-remap-async-to-generator": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c2cca47468cf1aeefdc7ec35d670e195c86cee4de28a1970648c46a88ce6bd1806ef0bab27251b9e7fb791bb28a64dcd543770efd899f28ee5f7854e64e873d3 + checksum: fe9ee8a5471b4317c1b9ea92410ace8126b52a600d7cfbfe1920dcac6fb0fad647d2e08beb4fd03c630eb54430e6c72db11e283e3eddc49615c68abd39430904 languageName: node linkType: hard @@ -875,54 +878,55 @@ __metadata: linkType: hard "@babel/plugin-transform-block-scoping@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/plugin-transform-block-scoping@npm:7.20.2" + version: 7.20.15 + resolution: "@babel/plugin-transform-block-scoping@npm:7.20.15" dependencies: "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 550b983277557ecfa3ef1e7a2367eaa9e0616a56f0d4106812cbc8aeca057b0f0b8bbc5c548b9b3b57399868f916e89e17303c802c8c46d18fba5bc174d4e794 + checksum: 1dddf7be578306837074cb5059f8408af0b1c0bfcf922ed920d4aa65d08fb7c6e6129ca254e9879c4c6d2a6be4937111551f51922e8b0e071ed16eb6564a4dbb languageName: node linkType: hard "@babel/plugin-transform-classes@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/plugin-transform-classes@npm:7.20.2" + version: 7.20.7 + resolution: "@babel/plugin-transform-classes@npm:7.20.7" dependencies: "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-compilation-targets": ^7.20.0 + "@babel/helper-compilation-targets": ^7.20.7 "@babel/helper-environment-visitor": ^7.18.9 "@babel/helper-function-name": ^7.19.0 "@babel/helper-optimise-call-expression": ^7.18.6 "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-replace-supers": ^7.19.1 + "@babel/helper-replace-supers": ^7.20.7 "@babel/helper-split-export-declaration": ^7.18.6 globals: ^11.1.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 57f3467a8eb7853cdb61cda963cfb6c6568ad276d77c9de2ff5a2194650010217aa318ef3733975537c6fb906b73a019afb6ea650b01852e7d2e1fab4034361b + checksum: 4cf55ad88e52c7c66a991add4c8e1c3324384bd52df7085962d396879561456a44352e5ab1725cc80f4e83737a2931e847c4a96c7aa4a549357f23631ff31799 languageName: node linkType: hard "@babel/plugin-transform-computed-properties@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-computed-properties@npm:7.18.9" + version: 7.20.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/template": ^7.20.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a6bfbea207827d77592628973c0e8cc3319db636506bdc6e81e21582de2e767890e6975b382d0511e9ec3773b9f43691185df90832883bbf9251f688d27fbc1d + checksum: be70e54bda8b469146459f429e5f2bd415023b87b2d5af8b10e48f465ffb02847a3ed162ca60378c004b82db848e4d62e90010d41ded7e7176b6d8d1c2911139 languageName: node linkType: hard "@babel/plugin-transform-destructuring@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/plugin-transform-destructuring@npm:7.20.2" + version: 7.20.7 + resolution: "@babel/plugin-transform-destructuring@npm:7.20.7" dependencies: "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 09033e09b28ca1b0d46a8d82f5a677b1d718a739b3c199886908c3ef1af23369317d0c429b21507d480ee82721c15892a9893be18e50ad6fc219e69312f4b097 + checksum: bd8affdb142c77662037215e37128b2110a786c92a67e1f00b38223c438c1610bd84cbc0386e9cd3479245ea811c5ca6c9838f49be4729b592159a30ce79add2 languageName: node linkType: hard @@ -1020,41 +1024,41 @@ __metadata: linkType: hard "@babel/plugin-transform-modules-amd@npm:^7.19.6": - version: 7.19.6 - resolution: "@babel/plugin-transform-modules-amd@npm:7.19.6" + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-amd@npm:7.20.11" dependencies: - "@babel/helper-module-transforms": ^7.19.6 - "@babel/helper-plugin-utils": ^7.19.0 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4236aad970025bc10c772c1589b1e2eab8b7681933bb5ffa6e395d4c1a52532b28c47c553e3011b4272ea81e5ab39fe969eb5349584e8390e59771055c467d42 + checksum: 23665c1c20c8f11c89382b588fb9651c0756d130737a7625baeaadbd3b973bc5bfba1303bedffa8fb99db1e6d848afb01016e1df2b69b18303e946890c790001 languageName: node linkType: hard "@babel/plugin-transform-modules-commonjs@npm:^7.19.6": - version: 7.19.6 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.19.6" + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.20.11" dependencies: - "@babel/helper-module-transforms": ^7.19.6 - "@babel/helper-plugin-utils": ^7.19.0 - "@babel/helper-simple-access": ^7.19.4 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-simple-access": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 85d46945ab5ba3fff89e962d560a5d40253f228b9659a697683db3de07c0236e8cd60e5eb41958007359951a42bc268bf32350fcdb5b4a86f58dff1e032c096e + checksum: ddd0623e2ad4b5c0faaa0ae30d3407a3fa484d911c968ed33cfb1b339ac3691321c959db60b66dc136dbd67770fff586f7e48a7ce0d7d357f92d6ef6fb7ed1a7 languageName: node linkType: hard "@babel/plugin-transform-modules-systemjs@npm:^7.19.6": - version: 7.19.6 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.19.6" + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.20.11" dependencies: "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-module-transforms": ^7.19.6 - "@babel/helper-plugin-utils": ^7.19.0 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/helper-validator-identifier": ^7.19.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8526431cc81ea3eb232ad50862d0ed1cbb422b5251d14a8d6610d0ca0617f6e75f35179e98eb1235d0cccb980120350b9f112594e5646dd45378d41eaaf87342 + checksum: 4546c47587f88156d66c7eb7808e903cf4bb3f6ba6ac9bc8e3af2e29e92eb9f0b3f44d52043bfd24eb25fa7827fd7b6c8bfeac0cac7584e019b87e1ecbd0e673 languageName: node linkType: hard @@ -1071,14 +1075,14 @@ __metadata: linkType: hard "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.19.1" + version: 7.20.5 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.20.5" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.19.0 - "@babel/helper-plugin-utils": ^7.19.0 + "@babel/helper-create-regexp-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0 - checksum: 8a40f5d04f2140c44fe890a5a3fd72abc2a88445443ac2bd92e1e85d9366d3eb8f1ebb7e2c89d2daeaf213d9b28cb65605502ac9b155936d48045eeda6053494 + checksum: 528c95fb1087e212f17e1c6456df041b28a83c772b9c93d2e407c9d03b72182b0d9d126770c1d6e0b23aab052599ceaf25ed6a2c0627f4249be34a83f6fae853 languageName: node linkType: hard @@ -1105,14 +1109,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.20.1": - version: 7.20.3 - resolution: "@babel/plugin-transform-parameters@npm:7.20.3" +"@babel/plugin-transform-parameters@npm:^7.20.1, @babel/plugin-transform-parameters@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-parameters@npm:7.20.7" dependencies: "@babel/helper-plugin-utils": ^7.20.2 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 69054c93d744574e06b0244623140718ecba87e1cc34bd5c7bd5824fd4dbef764ac4832046ea1ba5d2c6a2f12e03289555c9f65f0aafae4871f3d740ff61b9ec + checksum: 6ffe0dd9afb2d2b9bc247381aa2e95dd9997ff5568a0a11900528919a4e073ac68f46409431455badb8809644d47cff180045bc2b9700e3f36e3b23554978947 languageName: node linkType: hard @@ -1161,17 +1165,17 @@ __metadata: linkType: hard "@babel/plugin-transform-react-jsx@npm:^7.18.6": - version: 7.19.0 - resolution: "@babel/plugin-transform-react-jsx@npm:7.19.0" + version: 7.20.13 + resolution: "@babel/plugin-transform-react-jsx@npm:7.20.13" dependencies: "@babel/helper-annotate-as-pure": ^7.18.6 "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-plugin-utils": ^7.19.0 + "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-jsx": ^7.18.6 - "@babel/types": ^7.19.0 + "@babel/types": ^7.20.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d7d6f0b8f24b1f6b7cf8062c4e91c59af82489a993e51859bd49c2d62a2d2b77fd40b02a9a1d0e6d874cf4ce56a05fa3564b964587d00c94ebc62593524052ec + checksum: b1daaa9b093ab59f71572dde7ad05ed3490433a47de103fc866f60347da55fa7fe84cf9b4c9fa22917517d52f70ab5e05ec631bba1c348733c0d8ebbd7de8c68 languageName: node linkType: hard @@ -1188,14 +1192,14 @@ __metadata: linkType: hard "@babel/plugin-transform-regenerator@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-regenerator@npm:7.18.6" + version: 7.20.5 + resolution: "@babel/plugin-transform-regenerator@npm:7.20.5" dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - regenerator-transform: ^0.15.0 + "@babel/helper-plugin-utils": ^7.20.2 + regenerator-transform: ^0.15.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 60bd482cb0343c714f85c3e19a13b3b5fa05ee336c079974091c0b35e263307f4e661f4555dff90707a87d5efe19b1d51835db44455405444ac1813e268ad750 + checksum: 13164861e71fb23d84c6270ef5330b03c54d5d661c2c7468f28e21c4f8598558ca0c8c3cb1d996219352946e849d270a61372bc93c8fbe9676e78e3ffd0dea07 languageName: node linkType: hard @@ -1238,14 +1242,14 @@ __metadata: linkType: hard "@babel/plugin-transform-spread@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/plugin-transform-spread@npm:7.19.0" + version: 7.20.7 + resolution: "@babel/plugin-transform-spread@npm:7.20.7" dependencies: - "@babel/helper-plugin-utils": ^7.19.0 - "@babel/helper-skip-transparent-expression-wrappers": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e73a4deb095999185e70b524d0ff4e35df50fcda58299e700a6149a15bbc1a9b369ef1cef384e15a54b3c3ce316cc0f054dbf249dcd0d1ca59f4281dd4df9718 + checksum: 8ea698a12da15718aac7489d4cde10beb8a3eea1f66167d11ab1e625033641e8b328157fd1a0b55dd6531933a160c01fc2e2e61132a385cece05f26429fd0cc2 languageName: node linkType: hard @@ -1283,15 +1287,15 @@ __metadata: linkType: hard "@babel/plugin-transform-typescript@npm:^7.18.6": - version: 7.20.2 - resolution: "@babel/plugin-transform-typescript@npm:7.20.2" + version: 7.20.13 + resolution: "@babel/plugin-transform-typescript@npm:7.20.13" dependencies: - "@babel/helper-create-class-features-plugin": ^7.20.2 + "@babel/helper-create-class-features-plugin": ^7.20.12 "@babel/helper-plugin-utils": ^7.20.2 "@babel/plugin-syntax-typescript": ^7.20.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 14434eb77cb3c8c4187a055eabdd5ff8b3e90a37ac95ecc7c9007ea8fc5660e0652c445646a2a25836a02d91944e0dc1e8b58ef55b063a901e54a24fdb4168af + checksum: 0b0c3a3e53268d4feb35eb17d57873f2488392e404a0b32735d51c49b08462dc738ebd860f0ff3a3dc5cd1b1fa70340bb6c072239c86afca635831b930593b3b languageName: node linkType: hard @@ -1447,62 +1451,52 @@ __metadata: languageName: node linkType: hard -"@babel/runtime-corejs3@npm:^7.10.2": - version: 7.20.1 - resolution: "@babel/runtime-corejs3@npm:7.20.1" +"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.1, @babel/runtime@npm:^7.9.2": + version: 7.20.13 + resolution: "@babel/runtime@npm:7.20.13" dependencies: - core-js-pure: ^3.25.1 - regenerator-runtime: ^0.13.10 - checksum: bac1463304deb0e395f78aef2bf0e042d0ae303285b9f55e443d8ce4d3d05ccb92ac0aa5ca4bf83526695d21b12a239317537b00918d6ebf7a4132e5ec2f6f33 + regenerator-runtime: ^0.13.11 + checksum: 09b7a97a05c80540db6c9e4ddf8c5d2ebb06cae5caf3a87e33c33f27f8c4d49d9c67a2d72f1570e796045288fad569f98a26ceba0c4f5fad2af84b6ad855c4fb languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.1, @babel/runtime@npm:^7.9.2": - version: 7.20.1 - resolution: "@babel/runtime@npm:7.20.1" - dependencies: - regenerator-runtime: ^0.13.10 - checksum: 00567a333d3357925742a6f5e39394dcc0af6e6029103fe188158bf7ae8b0b3ee3c6c0f68fccc217f0a6cfa455f6be252298baf56b3f5ff37b34313b170cd9f6 - languageName: node - linkType: hard - -"@babel/template@npm:^7.18.10, @babel/template@npm:^7.3.3": - version: 7.18.10 - resolution: "@babel/template@npm:7.18.10" +"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" dependencies: "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.18.10 - "@babel/types": ^7.18.10 - checksum: 93a6aa094af5f355a72bd55f67fa1828a046c70e46f01b1606e6118fa1802b6df535ca06be83cc5a5e834022be95c7b714f0a268b5f20af984465a71e28f1473 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e languageName: node linkType: hard -"@babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.19.1, @babel/traverse@npm:^7.20.1, @babel/traverse@npm:^7.7.2": - version: 7.20.1 - resolution: "@babel/traverse@npm:7.20.1" +"@babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.13, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.7.2": + version: 7.20.13 + resolution: "@babel/traverse@npm:7.20.13" dependencies: "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.20.1 + "@babel/generator": ^7.20.7 "@babel/helper-environment-visitor": ^7.18.9 "@babel/helper-function-name": ^7.19.0 "@babel/helper-hoist-variables": ^7.18.6 "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.20.1 - "@babel/types": ^7.20.0 + "@babel/parser": ^7.20.13 + "@babel/types": ^7.20.7 debug: ^4.1.0 globals: ^11.1.0 - checksum: 6696176d574b7ff93466848010bc7e94b250169379ec2a84f1b10da46a7cc2018ea5e3a520c3078487db51e3a4afab9ecff48f25d1dbad8c1319362f4148fb4b + checksum: 30ca6e0bd18233fda48fa09315efd14dfc61dcf5b8fa3712b343bfc61b32bc63b5e85ea1773cc9576c9b293b96f46b4589aaeb0a52e1f3eeac4edc076d049fc7 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.20.2 - resolution: "@babel/types@npm:7.20.2" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.5, @babel/types@npm:^7.20.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.20.7 + resolution: "@babel/types@npm:7.20.7" dependencies: "@babel/helper-string-parser": ^7.19.4 "@babel/helper-validator-identifier": ^7.19.1 to-fast-properties: ^2.0.0 - checksum: 57e76e5f21876135f481bfd4010c87f2d38196bb0a2bc60a28d6e55e3afa90cdd9accf164e4cb71bdfb620517fa0a0cb5600cdce36c21d59fdaccfbb899c024c + checksum: b39af241f0b72bba67fd6d0d23914f6faec8c0eba8015c181cbd5ea92e59fc91a52a1ab490d3520c7dbd19ddb9ebb76c476308f6388764f16d8201e37fae6811 languageName: node linkType: hard @@ -1531,9 +1525,9 @@ __metadata: linkType: hard "@carbon/icon-helpers@npm:^10.28.0": - version: 10.34.0 - resolution: "@carbon/icon-helpers@npm:10.34.0" - checksum: ed06c8c098e3bcb5b31d83e59794c1079a3a0bf379cb1088c7523f48aad0ad3704b274644214720d78b863809882884c361690a59f327749dab70cb918d61db0 + version: 10.38.0 + resolution: "@carbon/icon-helpers@npm:10.38.0" + checksum: 04277dcb45891d09ec5bd8ba43fcaaa3bea72d2cb24e02017a712873cabc81cba6efaf3932198ef1496092f33a853b5c66aec737a59c2a6eb576a5628d28951d languageName: node linkType: hard @@ -1611,80 +1605,80 @@ __metadata: languageName: node linkType: hard -"@commitlint/config-validator@npm:^17.1.0": - version: 17.1.0 - resolution: "@commitlint/config-validator@npm:17.1.0" +"@commitlint/config-validator@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/config-validator@npm:17.4.0" dependencies: - "@commitlint/types": ^17.0.0 + "@commitlint/types": ^17.4.0 ajv: ^8.11.0 - checksum: 18b4779837979bf9e240de689c49b9d0dc1e053e677ec13826204594edc052510f37a30bcd8826a054cbcb42a7285fc23e160082b281e0089f18039958ec6a53 + checksum: 4e8885cf8f35a6dbff7b504cabadf2c38bba3b05dc78b40a0403e9a06cc14cf3d29e088b76a19d5f7510e09132f4070c35a586b0e6e52590c1a7b1dfd47982c4 languageName: node linkType: hard -"@commitlint/ensure@npm:^17.3.0": - version: 17.3.0 - resolution: "@commitlint/ensure@npm:17.3.0" +"@commitlint/ensure@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/ensure@npm:17.4.0" dependencies: - "@commitlint/types": ^17.0.0 + "@commitlint/types": ^17.4.0 lodash.camelcase: ^4.3.0 lodash.kebabcase: ^4.1.1 lodash.snakecase: ^4.1.1 lodash.startcase: ^4.4.0 lodash.upperfirst: ^4.3.1 - checksum: 55f880497fd5858d60e1664372c644819c8095f29b8587b7151d6c75d4d22fcfa201b159f6b8c917e13f5960479ec0daaae89b2b72fdd2ba2abc383f609d0798 + checksum: 836a5fc23752ae19981f97008ec255782ac59da3a37d69ca8b1f8d89b873ce086cb4b9170df2edf420729e2e017f00c8f4c9a305a14a953eded8c4900e99ebc0 languageName: node linkType: hard -"@commitlint/execute-rule@npm:^17.0.0": - version: 17.0.0 - resolution: "@commitlint/execute-rule@npm:17.0.0" - checksum: cb37e5c6e0e16bf04e8f344094146ed2de8155456191da88fb9a1b943a9b5a98e0f6ef49c55b239104eb68634df681fd3be05311bf2da0cb6b171fdd24371669 +"@commitlint/execute-rule@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/execute-rule@npm:17.4.0" + checksum: 17d8e56ab00bd45fdecb0ed33186d2020ce261250d6a516204b6509610b75af8c930e7226b1111af3de298db32a7e4d0ba2c9cc7ed67db5ba5159eeed634f067 languageName: node linkType: hard "@commitlint/format@npm:^17.0.0": - version: 17.0.0 - resolution: "@commitlint/format@npm:17.0.0" + version: 17.4.0 + resolution: "@commitlint/format@npm:17.4.0" dependencies: - "@commitlint/types": ^17.0.0 + "@commitlint/types": ^17.4.0 chalk: ^4.1.0 - checksum: e54705bdc91741632bac6ae330ba5d08110ec7575900585f4947487e7189a3d586396a3da3f1622fd3b6a49be9af1f71519a1ffeaa562d4cc7349bde3846eb8a + checksum: 59dc069e587b99482944e404b9d140929421eb4f91716df200f921b2662a0ca9b25f8825bb07d0bc6ffe6f71796771b70ff0deb89a17831c9e4894d79e41b2b7 languageName: node linkType: hard -"@commitlint/is-ignored@npm:^17.2.0": - version: 17.2.0 - resolution: "@commitlint/is-ignored@npm:17.2.0" +"@commitlint/is-ignored@npm:^17.4.2": + version: 17.4.2 + resolution: "@commitlint/is-ignored@npm:17.4.2" dependencies: - "@commitlint/types": ^17.0.0 - semver: 7.3.7 - checksum: ae88eae5f4661d963a46ed39ae58dd3e3b0a1139cbab59f76f535170eb263c203e25d67286f3a0dedb7cfd77606d65d65a9eaa8e4a1949cd82d342064c4e5cc3 + "@commitlint/types": ^17.4.0 + semver: 7.3.8 + checksum: 4b210d6ce0f9dd66f27d925d151c88845a2f1128b10865f5808e113f31be6ab359c58c1259664c888961e7bc1b71d3e8a2125eda8b8e4be1d32618a7772603c6 languageName: node linkType: hard "@commitlint/lint@npm:^17.0.0": - version: 17.3.0 - resolution: "@commitlint/lint@npm:17.3.0" + version: 17.4.2 + resolution: "@commitlint/lint@npm:17.4.2" dependencies: - "@commitlint/is-ignored": ^17.2.0 - "@commitlint/parse": ^17.2.0 - "@commitlint/rules": ^17.3.0 - "@commitlint/types": ^17.0.0 - checksum: 71a7b2cbb0eaeebcf50d66260112abe30c1118a55c7c4c89f72c40bdc52149455023877a9906d272d53f6c9673713dbd3baac14a7bcf6c81e618db3fc8d83e05 + "@commitlint/is-ignored": ^17.4.2 + "@commitlint/parse": ^17.4.2 + "@commitlint/rules": ^17.4.2 + "@commitlint/types": ^17.4.0 + checksum: efcb5fbee6f8cad5b619deabde598f1f1ac253cf1162eeda4de01e41ae13b7caa651d6fe5eea75d32a20fa7975bb27d13d9e0c9a422ebd158485311e6fb8c8a9 languageName: node linkType: hard "@commitlint/load@npm:^17.0.0": - version: 17.3.0 - resolution: "@commitlint/load@npm:17.3.0" + version: 17.4.2 + resolution: "@commitlint/load@npm:17.4.2" dependencies: - "@commitlint/config-validator": ^17.1.0 - "@commitlint/execute-rule": ^17.0.0 - "@commitlint/resolve-extends": ^17.3.0 - "@commitlint/types": ^17.0.0 - "@types/node": ^14.0.0 + "@commitlint/config-validator": ^17.4.0 + "@commitlint/execute-rule": ^17.4.0 + "@commitlint/resolve-extends": ^17.4.0 + "@commitlint/types": ^17.4.0 + "@types/node": "*" chalk: ^4.1.0 - cosmiconfig: ^7.0.0 + cosmiconfig: ^8.0.0 cosmiconfig-typescript-loader: ^4.0.0 lodash.isplainobject: ^4.0.6 lodash.merge: ^4.6.2 @@ -1692,90 +1686,90 @@ __metadata: resolve-from: ^5.0.0 ts-node: ^10.8.1 typescript: ^4.6.4 - checksum: 7049eb87f53af960761bcabb04a5b05cde0d41a540d9d7138e766dd4489a067d70bfd1c558892d87bc30ccceb1b8db1ff019ca9966caff94c6fa83c5ea836c18 + checksum: 7c0498040611abbc2c9f2af03bc6360ca44ff85943dd49012b90b5a5d9308997d782b75e164ad2c39c5d522e94c93214e5cc4fd3b4122c5788c3c869ee91eae0 languageName: node linkType: hard -"@commitlint/message@npm:^17.2.0": - version: 17.2.0 - resolution: "@commitlint/message@npm:17.2.0" - checksum: 504760cfb1004d571f198d60641d2dc3e59e0ac28a244ba767fe938ee1124399acbe5be3b074da9ec88a9cb6b0378e198833c4b983feaeb0e4f1f886bbe927b6 +"@commitlint/message@npm:^17.4.2": + version: 17.4.2 + resolution: "@commitlint/message@npm:17.4.2" + checksum: 55b6cfeb57f7c9f913e18821aa4d972a6b6faa78c62741390996151f99554396f6df68ccfee86c163d24d8c27a4dbbcb50ef03c2972ab0a7a21d89daa2f9a519 languageName: node linkType: hard -"@commitlint/parse@npm:^17.2.0": - version: 17.2.0 - resolution: "@commitlint/parse@npm:17.2.0" +"@commitlint/parse@npm:^17.4.2": + version: 17.4.2 + resolution: "@commitlint/parse@npm:17.4.2" dependencies: - "@commitlint/types": ^17.0.0 + "@commitlint/types": ^17.4.0 conventional-changelog-angular: ^5.0.11 conventional-commits-parser: ^3.2.2 - checksum: a6be0e9124debb2e2d97dd442a855c9dafcc86999b970f52e77bddf4a5e5ff569011ea1a2f5ab6075ec1f5634b8354e68033fd01542abf9c72b026ae77306189 + checksum: d6808cc9c9ffcf8b06f938392a7428bb017c5e43d13510edad2c5885468bf0eae23e02c4d9611c200c498adb33eaf8abee797f32d437557101ddee02922f3572 languageName: node linkType: hard "@commitlint/read@npm:^17.0.0": - version: 17.2.0 - resolution: "@commitlint/read@npm:17.2.0" + version: 17.4.2 + resolution: "@commitlint/read@npm:17.4.2" dependencies: - "@commitlint/top-level": ^17.0.0 - "@commitlint/types": ^17.0.0 - fs-extra: ^10.0.0 + "@commitlint/top-level": ^17.4.0 + "@commitlint/types": ^17.4.0 + fs-extra: ^11.0.0 git-raw-commits: ^2.0.0 minimist: ^1.2.6 - checksum: b2adcbe1f1853a0d6b477c245a22ce18eda0e15c47d0211aa141f5101acf84b77e4c9bace076021e8d0a78b3d05c1f7f4e04e550ea0317992b592686e07b81ac + checksum: ed509f913bd9790bb3abfde0886abdc4e2569eb7651e666d2d70705954f98f14e2c621ffe8ee17bb8a9bee36e65e4d4d01d5cd2792c8e08e69248d31808830fa languageName: node linkType: hard -"@commitlint/resolve-extends@npm:^17.3.0": - version: 17.3.0 - resolution: "@commitlint/resolve-extends@npm:17.3.0" +"@commitlint/resolve-extends@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/resolve-extends@npm:17.4.0" dependencies: - "@commitlint/config-validator": ^17.1.0 - "@commitlint/types": ^17.0.0 + "@commitlint/config-validator": ^17.4.0 + "@commitlint/types": ^17.4.0 import-fresh: ^3.0.0 lodash.mergewith: ^4.6.2 resolve-from: ^5.0.0 resolve-global: ^1.0.0 - checksum: 9f4a89f412d6505a7154dd27fbfd428cb261e3aa39bd825c1f3d6257b5674a9cb3dcdaf65e6dab7b64f379b2984fea7fb4a37142cec7bb8df8a6df8e0761763c + checksum: 44d77c343c519f92d3f595508c7f8b07df4a33880ab3c32631cf77101c51bf444e1b03d50505f68ce677ff62729e9e44e81bb1fec8b6d87b831d6137f3d5c5a8 languageName: node linkType: hard -"@commitlint/rules@npm:^17.3.0": - version: 17.3.0 - resolution: "@commitlint/rules@npm:17.3.0" +"@commitlint/rules@npm:^17.4.2": + version: 17.4.2 + resolution: "@commitlint/rules@npm:17.4.2" dependencies: - "@commitlint/ensure": ^17.3.0 - "@commitlint/message": ^17.2.0 - "@commitlint/to-lines": ^17.0.0 - "@commitlint/types": ^17.0.0 + "@commitlint/ensure": ^17.4.0 + "@commitlint/message": ^17.4.2 + "@commitlint/to-lines": ^17.4.0 + "@commitlint/types": ^17.4.0 execa: ^5.0.0 - checksum: bc8c16701af4634e7ef260c41602d628dc49bcaaa0cae97674d9ce303db68b703a5fa7f2e8edfc67dfb115e4d0d8616261d11a472833d61c248b54bee9d84748 + checksum: 2d53f470b511c41359b66886db054cb43fff748281a236a860bf21c3ba666b9d0b346932e8f0ac90e0dfc5ccdea10abda855ea9faa0f3fe3ef0f3fbc6992c141 languageName: node linkType: hard -"@commitlint/to-lines@npm:^17.0.0": - version: 17.0.0 - resolution: "@commitlint/to-lines@npm:17.0.0" - checksum: ccad787a3baf567c6c589e96e110aa2582103b50eaa9b70493116c08a0e5c6c50669c05e67b0a77cd803d66c031b1dcb9805b752d604178dbc4c744fc7f9bb04 +"@commitlint/to-lines@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/to-lines@npm:17.4.0" + checksum: 841f90f606238e145ab4ba02940662d511fc04fe553619900152a8542170fe664031b95d820ffaeb8864d4851344278e662ef29637d763fc19fd828e0f8d139b languageName: node linkType: hard -"@commitlint/top-level@npm:^17.0.0": - version: 17.0.0 - resolution: "@commitlint/top-level@npm:17.0.0" +"@commitlint/top-level@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/top-level@npm:17.4.0" dependencies: find-up: ^5.0.0 - checksum: 2e43d021a63faee67aa0e63b86a3ab9347ccda1b81f1f0722841223bd6bf127de954933c2ca3172fac0a1ce07a8b3bed62ac8f4afa04d50281dc5f80b43b61fb + checksum: 14cd77e982d2dd7989718dafdbf7a2168a5fb387005e0686c2dfa9ffc36bb9a749e5d80a151884459e4d8c88564339688dca26e9c711abe043beeb3f30c3dfd6 languageName: node linkType: hard -"@commitlint/types@npm:^17.0.0": - version: 17.0.0 - resolution: "@commitlint/types@npm:17.0.0" +"@commitlint/types@npm:^17.0.0, @commitlint/types@npm:^17.4.0": + version: 17.4.0 + resolution: "@commitlint/types@npm:17.4.0" dependencies: chalk: ^4.1.0 - checksum: 210636d3923f93f7cfc409eac04376b0fe50356a0e08f25a37b43d5cd9ca4363f7b03ca2e7736cbf95b62d67733fe8e1028269d35b4fddd1b3f2a653c90ca85c + checksum: 58e1743780a0d76b380dc6ebfe6deb530ed5a7ee82d746d73586fe5186c84bf7e07aa0ca0523ca910915d573ed522c2b7b7037c11c9ea49c8a9d90c2b8c48173 languageName: node linkType: hard @@ -1953,29 +1947,29 @@ __metadata: linkType: hard "@csstools/selector-specificity@npm:^2.0.0, @csstools/selector-specificity@npm:^2.0.2": - version: 2.0.2 - resolution: "@csstools/selector-specificity@npm:2.0.2" + version: 2.1.1 + resolution: "@csstools/selector-specificity@npm:2.1.1" peerDependencies: - postcss: ^8.2 + postcss: ^8.4 postcss-selector-parser: ^6.0.10 - checksum: a2045a27276a6cfe645b6e212afc217d9a43174ea7a1fa1ab8918d5a0ace72380fbd9837fe1920c547985c11a9070dc48c5c80d483d3f581ddf7aa688204d44f + checksum: 392ab62732e93aa8cbea445bf3485c1acbbecc8ec087b200e06c9ddd2acf740fd1fe46abdacf813e7a50a95a60346377ee3eecb4e1fe3709582e2851430b376a languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.3": - version: 1.3.3 - resolution: "@eslint/eslintrc@npm:1.3.3" +"@eslint/eslintrc@npm:^1.4.1": + version: 1.4.1 + resolution: "@eslint/eslintrc@npm:1.4.1" dependencies: ajv: ^6.12.4 debug: ^4.3.2 espree: ^9.4.0 - globals: ^13.15.0 + globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: f03e9d6727efd3e0719da2051ea80c0c73d20e28c171121527dbb868cd34232ca9c1d0525a66e517a404afea26624b1e47895b6a92474678418c2f50c9566694 + checksum: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7 languageName: node linkType: hard @@ -1986,14 +1980,14 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.6": - version: 0.11.7 - resolution: "@humanwhocodes/config-array@npm:0.11.7" +"@humanwhocodes/config-array@npm:^0.11.8": + version: 0.11.8 + resolution: "@humanwhocodes/config-array@npm:0.11.8" dependencies: "@humanwhocodes/object-schema": ^1.2.1 debug: ^4.1.1 minimatch: ^3.0.5 - checksum: cf506dc45d9488af7fbf108ea6ac2151ba1a25e6d2b94b9b4fc36d2c1e4099b89ff560296dbfa13947e44604d4ca4a90d97a4fb167370bf8dd01a6ca2b6d83ac + checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 languageName: node linkType: hard @@ -2112,12 +2106,12 @@ __metadata: languageName: node linkType: hard -"@jest/expect-utils@npm:^29.3.1": - version: 29.3.1 - resolution: "@jest/expect-utils@npm:29.3.1" +"@jest/expect-utils@npm:^29.4.1": + version: 29.4.1 + resolution: "@jest/expect-utils@npm:29.4.1" dependencies: jest-get-type: ^29.2.0 - checksum: 7f3b853eb1e4299988f66b9aa49c1aacb7b8da1cf5518dca4ccd966e865947eed8f1bde6c8f5207d8400e9af870112a44b57aa83515ad6ea5e4a04a971863adb + checksum: 865b4ee79d43e2457efb8ce3f58108f2fe141ce620350fe21d0baaf7e2f00b9b67f6e9c1c89760b1008c100e844fb03a6dda264418ed378243956904d9a88c69 languageName: node linkType: hard @@ -2193,12 +2187,12 @@ __metadata: languageName: node linkType: hard -"@jest/schemas@npm:^29.0.0": - version: 29.0.0 - resolution: "@jest/schemas@npm:29.0.0" +"@jest/schemas@npm:^29.4.0": + version: 29.4.0 + resolution: "@jest/schemas@npm:29.4.0" dependencies: - "@sinclair/typebox": ^0.24.1 - checksum: 41355c78f09eb1097e57a3c5d0ca11c9099e235e01ea5fa4e3953562a79a6a9296c1d300f1ba50ca75236048829e056b00685cd2f1ff8285e56fd2ce01249acb + "@sinclair/typebox": ^0.25.16 + checksum: 005c90b7b641af029133fa390c0c8a75b63edf651da6253d7c472a8f15ddd18aa139edcd4236e57f974006e39c67217925768115484dbd7bfed2eba224de8b7d languageName: node linkType: hard @@ -2299,17 +2293,17 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^29.3.1": - version: 29.3.1 - resolution: "@jest/types@npm:29.3.1" +"@jest/types@npm:^29.4.1": + version: 29.4.1 + resolution: "@jest/types@npm:29.4.1" dependencies: - "@jest/schemas": ^29.0.0 + "@jest/schemas": ^29.4.0 "@types/istanbul-lib-coverage": ^2.0.0 "@types/istanbul-reports": ^3.0.0 "@types/node": "*" "@types/yargs": ^17.0.8 chalk: ^4.0.0 - checksum: 6f9faf27507b845ff3839c1adc6dbd038d7046d03d37e84c9fc956f60718711a801a5094c7eeee6b39ccf42c0ab61347fdc0fa49ab493ae5a8efd2fd41228ee8 + checksum: 0aa0b6a210b3474289e5dcaa8e7abb2238dba8d0baf2eb5a3f080fb95e9a39e71e8abc96811d4ef7011f5d993755bb54515e9d827d7ebc2a2d4d9579d84f5a04 languageName: node linkType: hard @@ -2520,6 +2514,18 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.30.0": + version: 1.30.0 + resolution: "@playwright/test@npm:1.30.0" + dependencies: + "@types/node": "*" + playwright-core: 1.30.0 + bin: + playwright: cli.js + checksum: 777432ac9cf3d0341fcd8dd265cb4c0775619d0ef48252b32a7c4d632d8038449756ec34bec873828cadbc08ba634e81176cb193304d34e699472771b7fb4d1e + languageName: node + linkType: hard + "@plotly/d3-sankey-circular@npm:0.33.1": version: 0.33.1 resolution: "@plotly/d3-sankey-circular@npm:0.33.1" @@ -2645,13 +2651,13 @@ __metadata: linkType: hard "@restart/hooks@npm:^0.4.7": - version: 0.4.7 - resolution: "@restart/hooks@npm:0.4.7" + version: 0.4.8 + resolution: "@restart/hooks@npm:0.4.8" dependencies: dequal: ^2.0.2 peerDependencies: react: ">=16.8.0" - checksum: 1aec4bfb00704c1c31b0c2af04aa28dff1714e36bb8043f7553e06d594aa376b0ba9e0f56b6df532c64b293bf8052c4f8f5bdd5cdad286e9dbaba422a94e21cb + checksum: 570b268330e61c2c43072dba6b050fd2cafe6f2fa7a193456a52034d1ff7100f1ae05b526eb480ace09c5cd22e403f3fb14a07f19a1473af42b0c0f1b3d590ad languageName: node linkType: hard @@ -2727,12 +2733,19 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.25.16": + version: 0.25.21 + resolution: "@sinclair/typebox@npm:0.25.21" + checksum: 763af1163fe4eabee9b914d4e4548a39fbba3287d2b3b1ff043c1da3c5a321e99d50a3ca94eb182988131e00b006a6f019799cde8da2f61e2f118b30b0276a00 + languageName: node + linkType: hard + "@sinonjs/commons@npm:^1.7.0": - version: 1.8.5 - resolution: "@sinonjs/commons@npm:1.8.5" + version: 1.8.6 + resolution: "@sinonjs/commons@npm:1.8.6" dependencies: type-detect: 4.0.8 - checksum: 74cb49e2f245dc0bfac990553dad0583884321f249522b3f73a6474ee9d7abe251814ebaab8094de7e94489d8efe415902fa67c47f637b751c121591b3cf5c39 + checksum: 7d3f8c1e85f30cd4e83594fc19b7a657f14d49eb8d95a30095631ce15e906c869e0eff96c5b93dffea7490c00418b07f54582ba49c6560feb2a8c34c0b16832d languageName: node linkType: hard @@ -2889,37 +2902,37 @@ __metadata: linkType: hard "@tanstack/react-table@npm:^8.3.3": - version: 8.7.0 - resolution: "@tanstack/react-table@npm:8.7.0" + version: 8.7.9 + resolution: "@tanstack/react-table@npm:8.7.9" dependencies: - "@tanstack/table-core": 8.7.0 + "@tanstack/table-core": 8.7.9 peerDependencies: react: ">=16" react-dom: ">=16" - checksum: 5f739f619dacfd134449bc8cff819d7e1b9549b8c8b20c293f2c786098a53d20fe5755727d8abb6fa56a64583792bbc89d8b1c7cd24ac3497495d2c20bac9e77 + checksum: 3c704ac903405972641c9857e1466025bcdac04ee4890d64b018386cad5a778aca3a8d3f78542ae7a0ed609841d32ef3a7e563b079e87fa93b9ae8570a310499 languageName: node linkType: hard -"@tanstack/table-core@npm:8.7.0": - version: 8.7.0 - resolution: "@tanstack/table-core@npm:8.7.0" - checksum: 835511727ab5651066a4cbc98916fbc2463ec6d6ddc5c74c1fec5713eeba8e26641653c1a35b69b303f0c58f574416a09e26da44cf7e9a884cb954fc26b12afd +"@tanstack/table-core@npm:8.7.9": + version: 8.7.9 + resolution: "@tanstack/table-core@npm:8.7.9" + checksum: 78d2314928c29559088e4bada0248cc7f94e93756e1a2c1f37a651db30276e9ae960d647bd3a61b67b3f0f9f7e4dec5dd58eb49b8adb80ee5952ef417b6e581f languageName: node linkType: hard "@testing-library/dom@npm:^8.17.1, @testing-library/dom@npm:^8.5.0": - version: 8.19.0 - resolution: "@testing-library/dom@npm:8.19.0" + version: 8.20.0 + resolution: "@testing-library/dom@npm:8.20.0" dependencies: "@babel/code-frame": ^7.10.4 "@babel/runtime": ^7.12.5 - "@types/aria-query": ^4.2.0 + "@types/aria-query": ^5.0.1 aria-query: ^5.0.0 chalk: ^4.1.0 dom-accessibility-api: ^0.5.9 lz-string: ^1.4.4 pretty-format: ^27.0.2 - checksum: 6bb93fef96703b6c47cf1b7cc8f71d402a9576084a94ba4e9926f51bd7bb1287fbb4f6942d82bd03fc6f3d998ae97e60f6aea4618f3a1ce6139597d2a4ecb7b9 + checksum: 1e599129a2fe91959ce80900a0a4897232b89e2a8e22c1f5950c36d39c97629ea86b4986b60b173b5525a05de33fde1e35836ea597b03de78cc51b122835c6f0 languageName: node linkType: hard @@ -3058,23 +3071,23 @@ __metadata: languageName: node linkType: hard -"@types/aria-query@npm:^4.2.0": - version: 4.2.2 - resolution: "@types/aria-query@npm:4.2.2" - checksum: 6f2ce11d91e2d665f3873258db19da752d91d85d3679eb5efcdf9c711d14492287e1e4eb52613b28e60375841a9e428594e745b68436c963d8bad4bf72188df3 +"@types/aria-query@npm:^5.0.1": + version: 5.0.1 + resolution: "@types/aria-query@npm:5.0.1" + checksum: 69fd7cceb6113ed370591aef04b3fd0742e9a1b06dd045c43531448847b85de181495e4566f98e776b37c422a12fd71866e0a1dfd904c5ec3f84d271682901de languageName: node linkType: hard "@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.14": - version: 7.1.20 - resolution: "@types/babel__core@npm:7.1.20" + version: 7.20.0 + resolution: "@types/babel__core@npm:7.20.0" dependencies: - "@babel/parser": ^7.1.0 - "@babel/types": ^7.0.0 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 "@types/babel__generator": "*" "@types/babel__template": "*" "@types/babel__traverse": "*" - checksum: a09c4f0456552547a5b8a5a009a3daec4d362f622168f8e08bda0ded2da0a65ab0b1642e23c433b3616721f5701dc34a996c5bde5baeaea53eda98f438043f2c + checksum: 49b601a0a7637f1f387442c8156bd086cfd10ff4b82b0e1994e73a6396643b5435366fb33d6b604eade8467cca594ef97adcbc412aede90bb112ebe88d0ad6df languageName: node linkType: hard @@ -3098,11 +3111,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.4, @types/babel__traverse@npm:^7.0.6": - version: 7.18.2 - resolution: "@types/babel__traverse@npm:7.18.2" + version: 7.18.3 + resolution: "@types/babel__traverse@npm:7.18.3" dependencies: "@babel/types": ^7.3.0 - checksum: 05972775e21cf07753b3bec725bf76f5a9804f99f660d323040746e3c8a4fe1b4ef6df17d7a80c4e2e335382cc72c62fc5a7079af836871ff9cbf0c21804e6d9 + checksum: d20953338b2f012ab7750932ece0a78e7d1645b0a6ff42d49be90f55e9998085da1374a9786a7da252df89555c6586695ba4d1d4b4e88ab2b9f306bcd35e00d3 languageName: node linkType: hard @@ -3155,12 +3168,12 @@ __metadata: linkType: hard "@types/eslint@npm:*, @types/eslint@npm:^7.29.0 || ^8.4.1": - version: 8.4.10 - resolution: "@types/eslint@npm:8.4.10" + version: 8.21.0 + resolution: "@types/eslint@npm:8.21.0" dependencies: "@types/estree": "*" "@types/json-schema": "*" - checksum: 21e009ed9ed9bc8920fdafc6e11ff321c4538b4cc18a56fdd59dc5184ea7bbf363c71638c9bdb59fc1254dddcdd567485136ed68b0ee4750948d4e32cb79c689 + checksum: 48823b13e1ffbc6fe22c96d99f691a17507ef5a498c4aed95e3a9076ec6d44ff48ce8a632928b6f82bea92701ac8967bba0d78a5c9de4dfa3f2e12d26dae7da4 languageName: node linkType: hard @@ -3185,35 +3198,35 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.31": + version: 4.17.33 + resolution: "@types/express-serve-static-core@npm:4.17.33" dependencies: "@types/node": "*" "@types/qs": "*" "@types/range-parser": "*" - checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df + checksum: dce580d16b85f207445af9d4053d66942b27d0c72e86153089fa00feee3e96ae336b7bedb31ed4eea9e553c99d6dd356ed6e0928f135375d9f862a1a8015adf2 languageName: node linkType: hard "@types/express@npm:*, @types/express@npm:^4.17.13": - version: 4.17.14 - resolution: "@types/express@npm:4.17.14" + version: 4.17.16 + resolution: "@types/express@npm:4.17.16" dependencies: "@types/body-parser": "*" - "@types/express-serve-static-core": ^4.17.18 + "@types/express-serve-static-core": ^4.17.31 "@types/qs": "*" "@types/serve-static": "*" - checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 + checksum: 43f3ed2cea6e5e83c7c1098c5152f644e975fd764443717ff9c812a1518416a9e7e9f824ffe852c118888cbfb994ed023cad08331f49b19ced469bb185cdd5cd languageName: node linkType: hard "@types/graceful-fs@npm:^4.1.2": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" dependencies: "@types/node": "*" - checksum: d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 languageName: node linkType: hard @@ -3266,12 +3279,12 @@ __metadata: linkType: hard "@types/jest@npm:*": - version: 29.2.3 - resolution: "@types/jest@npm:29.2.3" + version: 29.4.0 + resolution: "@types/jest@npm:29.4.0" dependencies: expect: ^29.0.0 pretty-format: ^29.0.0 - checksum: 55370906711b600a05b9e497c22aa74d80d8adcdbe4ac2f1bc9311f6f6ca0dd192862b6f38df6ac0d45e92396bcd796e377b1d8058c10bdfd584aeee580c3ce1 + checksum: 23760282362a252e6690314584d83a47512d4cd61663e957ed3398ecf98195fe931c45606ee2f9def12f8ed7d8aa102d492ec42d26facdaf8b78094a31e6568e languageName: node linkType: hard @@ -3304,16 +3317,9 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 18.11.9 - resolution: "@types/node@npm:18.11.9" - checksum: cc0aae109e9b7adefc32eecb838d6fad931663bb06484b5e9cbbbf74865c721b03d16fd8d74ad90e31dbe093d956a7c2c306ba5429ba0c00f3f7505103d7a496 - languageName: node - linkType: hard - -"@types/node@npm:^14.0.0": - version: 14.18.33 - resolution: "@types/node@npm:14.18.33" - checksum: 4e23f95186d8ae1d38c999bc6b46fe94e790da88744b0a3bfeedcbd0d9ffe2cb0ff39e85f43014f6739e5270292c1a1f6f97a1fc606fd573a0c17fda9a1d42de + version: 18.11.18 + resolution: "@types/node@npm:18.11.18" + checksum: 03f17f9480f8d775c8a72da5ea7e9383db5f6d85aa5fefde90dd953a1449bd5e4ffde376f139da4f3744b4c83942166d2a7603969a6f8ea826edfb16e6e3b49d languageName: node linkType: hard @@ -3332,9 +3338,9 @@ __metadata: linkType: hard "@types/prettier@npm:^2.1.5": - version: 2.7.1 - resolution: "@types/prettier@npm:2.7.1" - checksum: 5e3f58e229d6c73b5f5cae2e8f96c1c4a5b5805f83459e17a045ba8e96152b1d38e86b63e3172fb159dac923388699660862b75b2d37e54220805f0e691e26f1 + version: 2.7.2 + resolution: "@types/prettier@npm:2.7.2" + checksum: b47d76a5252265f8d25dd2fe2a5a61dc43ba0e6a96ffdd00c594cb4fd74c1982c2e346497e3472805d97915407a09423804cc2110a0b8e1b22cffcab246479b7 languageName: node linkType: hard @@ -3367,11 +3373,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.0.0": - version: 18.0.9 - resolution: "@types/react-dom@npm:18.0.9" + version: 18.0.10 + resolution: "@types/react-dom@npm:18.0.10" dependencies: "@types/react": "*" - checksum: e744e3feba25fc43733289d4df4d9c0e59fcca7f34e8c89d75f81a339accb2bd70236d69382d47d2c0ad06a1529b2e56aa6171fe175854d60e07156ddceedfcb + checksum: ff8282d5005a0b1cd95fb65bf79d3d8485e4cfe2aaf052129033a178684b940014a3f4536bc20d573f8a01cf4c6f4770c74988cef7c2b5cac3041d9f172647e3 languageName: node linkType: hard @@ -3385,13 +3391,13 @@ __metadata: linkType: hard "@types/react@npm:*, @types/react@npm:>=16.14.8, @types/react@npm:>=16.9.11": - version: 18.0.25 - resolution: "@types/react@npm:18.0.25" + version: 18.0.27 + resolution: "@types/react@npm:18.0.27" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 231d658c45abdef044a716b4502774f1585d8336d73b2f5bd68f181acbfc874b7a457686ecd29b415b43ed0922c309bab7e2cf96832d188a3f4f1b02f2af760a + checksum: 600fdbc39a92ea4a77047db3e12f05f67776a710f5918248c0189a59ac2a38900c9db5a5d2e433a16df528a3ecab1aa114b322cacea573bb1ca2fc0b094c52d1 languageName: node linkType: hard @@ -3484,11 +3490,11 @@ __metadata: linkType: hard "@types/ws@npm:^8.5.1": - version: 8.5.3 - resolution: "@types/ws@npm:8.5.3" + version: 8.5.4 + resolution: "@types/ws@npm:8.5.4" dependencies: "@types/node": "*" - checksum: 0ce46f850d41383fcdc2149bcacc86d7232fa7a233f903d2246dff86e31701a02f8566f40af5f8b56d1834779255c04ec6ec78660fe0f9b2a69cf3d71937e4ae + checksum: fefbad20d211929bb996285c4e6f699b12192548afedbe4930ab4384f8a94577c9cd421acaad163cacd36b88649509970a05a0b8f20615b30c501ed5269038d1 languageName: node linkType: hard @@ -3500,31 +3506,32 @@ __metadata: linkType: hard "@types/yargs@npm:^16.0.0": - version: 16.0.4 - resolution: "@types/yargs@npm:16.0.4" + version: 16.0.5 + resolution: "@types/yargs@npm:16.0.5" dependencies: "@types/yargs-parser": "*" - checksum: caa21d2c957592fe2184a8368c8cbe5a82a6c2e2f2893722e489f842dc5963293d2f3120bc06fe3933d60a3a0d1e2eb269649fd6b1947fe1820f8841ba611dd9 + checksum: 22697f7cc8aa32dcc10981a87f035e183303a58351c537c81fb450270d5c494b1d918186210e445b0eb2e4a8b34a8bda2a595f346bdb1c9ed2b63d193cb00430 languageName: node linkType: hard "@types/yargs@npm:^17.0.8": - version: 17.0.14 - resolution: "@types/yargs@npm:17.0.14" + version: 17.0.22 + resolution: "@types/yargs@npm:17.0.22" dependencies: "@types/yargs-parser": "*" - checksum: 7c571560434e1295b31dfd7f8d3b4a88dbb8bfe8db0054cea35f829f86b1251f67e41eaa0cb5990f17e5ed9b1a508c686b2b9f15c3bafc467ba58989ac9223e8 + checksum: 0773523fda71bafdc52f13f5970039e535a353665a60ba9261149a5c9c2b908242e6e77fbb7a8c06931ec78ce889d64d09673c68ba23eb5f5742d5385d0d1982 languageName: node linkType: hard "@typescript-eslint/eslint-plugin@npm:^5.5.0": - version: 5.44.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.44.0" + version: 5.50.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.50.0" dependencies: - "@typescript-eslint/scope-manager": 5.44.0 - "@typescript-eslint/type-utils": 5.44.0 - "@typescript-eslint/utils": 5.44.0 + "@typescript-eslint/scope-manager": 5.50.0 + "@typescript-eslint/type-utils": 5.50.0 + "@typescript-eslint/utils": 5.50.0 debug: ^4.3.4 + grapheme-splitter: ^1.0.4 ignore: ^5.2.0 natural-compare-lite: ^1.4.0 regexpp: ^3.2.0 @@ -3536,54 +3543,54 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 88784e77e8e35ea50ca9c49d46df94cabc3447f4b332f3ca53974d3b5370cb5dcd85cc9ee0e317b91083812012369209574725dcfc3b2b4056b60371b68ca854 + checksum: 351c4a157a7d717cc3835bdc09324b20d649463738a029c5701e5a38cdb162305ff7d56adff196a0c3245c24ea3167bbdac7f1c30399b8c1d495abbdbc1c53d6 languageName: node linkType: hard "@typescript-eslint/experimental-utils@npm:^5.0.0": - version: 5.44.0 - resolution: "@typescript-eslint/experimental-utils@npm:5.44.0" + version: 5.50.0 + resolution: "@typescript-eslint/experimental-utils@npm:5.50.0" dependencies: - "@typescript-eslint/utils": 5.44.0 + "@typescript-eslint/utils": 5.50.0 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: cd4e8c272577dc98546bc792c5c543ba8f3097161715343c84f7ec032e69f45a105e68e40b255da16a65582eed425cd8caed28f5f3671c03942271708c32474a + checksum: 9d71c359512ee541f3a60070e8de4b8ac075b4699f671429d893823e1bbea4ce8cd1f5ee3dcda82478f52d8210bce41f05afc62342b1f088e00d070987cb493b languageName: node linkType: hard "@typescript-eslint/parser@npm:^5.5.0": - version: 5.44.0 - resolution: "@typescript-eslint/parser@npm:5.44.0" + version: 5.50.0 + resolution: "@typescript-eslint/parser@npm:5.50.0" dependencies: - "@typescript-eslint/scope-manager": 5.44.0 - "@typescript-eslint/types": 5.44.0 - "@typescript-eslint/typescript-estree": 5.44.0 + "@typescript-eslint/scope-manager": 5.50.0 + "@typescript-eslint/types": 5.50.0 + "@typescript-eslint/typescript-estree": 5.50.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 2d09a1a1547a7ae3f76c9a33a54e11d79a194fbb9dbae69988e7aed3370bdf12bafde669211152769d89db822e0cdee4173affc126664fa6f17abba56daa7261 + checksum: 816a421ce9a5c61a2e92499d6d400aed4211ca5b685e0212844b6659f7acfeba1cca0418b462236c44eea6e8a2574cd51ccb7abc2bf4a8cad5b7a275d71ae9bf languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.44.0": - version: 5.44.0 - resolution: "@typescript-eslint/scope-manager@npm:5.44.0" +"@typescript-eslint/scope-manager@npm:5.50.0": + version: 5.50.0 + resolution: "@typescript-eslint/scope-manager@npm:5.50.0" dependencies: - "@typescript-eslint/types": 5.44.0 - "@typescript-eslint/visitor-keys": 5.44.0 - checksum: 4cfe4b55eb428eda740e6b967e3a87f3e1f9c4bbd8e1d6b8d64a11666abe33ffe7a21e4e614444ccde2da6930fa85f3e0ffca43d6e339943ff7a4fbccb09c8fc + "@typescript-eslint/types": 5.50.0 + "@typescript-eslint/visitor-keys": 5.50.0 + checksum: bd49447a834c82cb130e6900644042c3a84195bf7a63483385e90b6454c65856d6f276c997cad6bf9c36c9d0cb168fdde625ce4c78c3b8bcce42da782270794b languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.44.0": - version: 5.44.0 - resolution: "@typescript-eslint/type-utils@npm:5.44.0" +"@typescript-eslint/type-utils@npm:5.50.0": + version: 5.50.0 + resolution: "@typescript-eslint/type-utils@npm:5.50.0" dependencies: - "@typescript-eslint/typescript-estree": 5.44.0 - "@typescript-eslint/utils": 5.44.0 + "@typescript-eslint/typescript-estree": 5.50.0 + "@typescript-eslint/utils": 5.50.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -3591,23 +3598,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4c7b594f8afa52d57d0512951a874fa390eb791dcefcd0e1efff8817872293b2e4e04eff3c54d1595c1720a34d5fd315729af4e459882033d13cb6069ae9d28f + checksum: d2fc2fd10ef300865fd6a902ae92aef6c45cddc4359445f1e5c6dc9511063b52d2170cc6b525763395d4171c177b3d0fffd77cf9a2ab7e01fcd7109bd1a5a585 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.44.0": - version: 5.44.0 - resolution: "@typescript-eslint/types@npm:5.44.0" - checksum: ced7d32abecfc62ccb67cf27e30c0785b9c153ec7b1a05153ced58fa5a2031ab3845bc2e477b83e4cebdcc5881c5845d23053c6739c62549d41ae6208e547e85 +"@typescript-eslint/types@npm:5.50.0": + version: 5.50.0 + resolution: "@typescript-eslint/types@npm:5.50.0" + checksum: 1189c63d35abeec685dd519fd923926b884e63d5e10e4a9fe995aebfde59b8a2e10773090ec3ba32a0ec408746b18f6a454d9bedb0b6c7ce8b6066547144fb4d languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.44.0": - version: 5.44.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.44.0" +"@typescript-eslint/typescript-estree@npm:5.50.0": + version: 5.50.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.50.0" dependencies: - "@typescript-eslint/types": 5.44.0 - "@typescript-eslint/visitor-keys": 5.44.0 + "@typescript-eslint/types": 5.50.0 + "@typescript-eslint/visitor-keys": 5.50.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -3616,35 +3623,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 758731108497cca7ff81cf0a78d086b5a85757a983979d6bb25ad8252b7acbc738c642ecb5f5df82f925a45926b9846e431d5cf9fee5ed2613300b4d0c5d6c3f + checksum: cb1ac8d39647da6d52750c713d9635750ed41245ec82f937a159a71ad3bf490ebabfad3b43eeca07bca39d60df30d3a2f31f8bed0061381731d92a62e284b867 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.44.0, @typescript-eslint/utils@npm:^5.13.0": - version: 5.44.0 - resolution: "@typescript-eslint/utils@npm:5.44.0" +"@typescript-eslint/utils@npm:5.50.0, @typescript-eslint/utils@npm:^5.43.0": + version: 5.50.0 + resolution: "@typescript-eslint/utils@npm:5.50.0" dependencies: "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.44.0 - "@typescript-eslint/types": 5.44.0 - "@typescript-eslint/typescript-estree": 5.44.0 + "@typescript-eslint/scope-manager": 5.50.0 + "@typescript-eslint/types": 5.50.0 + "@typescript-eslint/typescript-estree": 5.50.0 eslint-scope: ^5.1.1 eslint-utils: ^3.0.0 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: bc5bb28e41898464d35b8eb47cc452103852541e3b6be56252c15a5a81c45e10aad3db4c749eb92d752b0c358df8074e23ec6f9e65f8089baadeda7f395c7e31 + checksum: 4471ae8b24449300e009f1cc09ee0d38cce20ae9171e8fbf4ef752ce4eb87104cc0d813d8f7051b619fa05e1e7c12b748dad49832911685297b1bbfef3c01f0b languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.44.0": - version: 5.44.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.44.0" +"@typescript-eslint/visitor-keys@npm:5.50.0": + version: 5.50.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.50.0" dependencies: - "@typescript-eslint/types": 5.44.0 + "@typescript-eslint/types": 5.50.0 eslint-visitor-keys: ^3.3.0 - checksum: a012c888209e1d6ae684b2a44fd460ae5a80f5faf07bca4bda6c9c0d8c063ad3297d4c53f7151ae86cf1a43dee09625dc3ee72183323c91089c7288fd573c6f4 + checksum: 55319cb7ee7b78d07d9dc67a388d69fe0b7f11cbc79190e17e7f87a39c9992d08dab3b5872d5a7f01094dda28ad6ac61d3573e59015ef70bf138d4c4f8c45b88 languageName: node linkType: hard @@ -3813,6 +3820,13 @@ __metadata: languageName: node linkType: hard +"@zeit/schemas@npm:2.29.0": + version: 2.29.0 + resolution: "@zeit/schemas@npm:2.29.0" + checksum: 3cea06bb67d790336aca0cc17580fd492ff3fc66ef4d180dce7053ff7ff54ab81b56bf718ba6f537148c581161d06306a481ec218d540bff922e0e009844ffd1 + languageName: node + linkType: hard + "JSONStream@npm:^1.0.4": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -3919,18 +3933,18 @@ __metadata: linkType: hard "acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0": - version: 8.8.1 - resolution: "acorn@npm:8.8.1" + version: 8.8.2 + resolution: "acorn@npm:8.8.2" bin: acorn: bin/acorn - checksum: 4079b67283b94935157698831967642f24a075c52ce3feaaaafe095776dfbe15d86a1b33b1e53860fc0d062ed6c83f4284a5c87c85b9ad51853a01173da6097f + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 languageName: node linkType: hard "address@npm:^1.0.1, address@npm:^1.1.2": - version: 1.2.1 - resolution: "address@npm:1.2.1" - checksum: e4c0f961464ccad09c3f7ed3a8d12f609354a87dd1ad379e43661e9684446fbf158be3edeef85e1590dfc6c88c0897c5908bc18f232eb86e43993a2ada5820fa + version: 1.2.2 + resolution: "address@npm:1.2.2" + checksum: ace439960c1e3564d8f523aff23a841904bf33a2a7c2e064f7f60a064194075758b9690e65bd9785692a4ef698a998c57eb74d145881a1cecab8ba658ddb1607 languageName: node linkType: hard @@ -4008,6 +4022,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:8.11.0": + version: 8.11.0 + resolution: "ajv@npm:8.11.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + languageName: node + linkType: hard + "ajv@npm:^6.10.0, ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -4021,14 +4047,14 @@ __metadata: linkType: hard "ajv@npm:^8.0.0, ajv@npm:^8.11.0, ajv@npm:^8.6.0, ajv@npm:^8.8.0": - version: 8.11.2 - resolution: "ajv@npm:8.11.2" + version: 8.12.0 + resolution: "ajv@npm:8.12.0" dependencies: fast-deep-equal: ^3.1.1 json-schema-traverse: ^1.0.0 require-from-string: ^2.0.2 uri-js: ^4.2.2 - checksum: 53435bf79ee7d1eabba8085962dba4c08d08593334b304db7772887f0b7beebc1b3d957432f7437ed4b60e53b5d966a57b439869890209c50fed610459999e3e + checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001 languageName: node linkType: hard @@ -4039,6 +4065,15 @@ __metadata: languageName: node linkType: hard +"ansi-align@npm:^3.0.1": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: ^4.1.0 + checksum: 6abfa08f2141d231c257162b15292467081fa49a208593e055c866aa0455b57f3a86b5a678c190c618faa79b4c59e254493099cb700dd9cf2293c6be2c8f5d8d + languageName: node + linkType: hard + "ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.1": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -4096,7 +4131,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.0.0": +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 @@ -4120,6 +4155,13 @@ __metadata: languageName: node linkType: hard +"arch@npm:^2.2.0": + version: 2.2.0 + resolution: "arch@npm:2.2.0" + checksum: e21b7635029fe8e9cdd5a026f9a6c659103e63fff423834323cdf836a1bb240a72d0c39ca8c470f84643385cf581bd8eda2cad8bf493e27e54bd9783abe9101f + languageName: node + linkType: hard + "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -4130,6 +4172,13 @@ __metadata: languageName: node linkType: hard +"arg@npm:5.0.2, arg@npm:^5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 6c69ada1a9943d332d9e5382393e897c500908d91d5cb735a01120d5f71daf1b339b7b8980cbeaba8fd1afc68e658a739746179e4315a26e8a28951ff9930078 + languageName: node + linkType: hard + "arg@npm:^4.1.0": version: 4.1.3 resolution: "arg@npm:4.1.3" @@ -4137,13 +4186,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:^5.0.2": - version: 5.0.2 - resolution: "arg@npm:5.0.2" - checksum: 6c69ada1a9943d332d9e5382393e897c500908d91d5cb735a01120d5f71daf1b339b7b8980cbeaba8fd1afc68e658a739746179e4315a26e8a28951ff9930078 - languageName: node - linkType: hard - "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -4160,17 +4202,7 @@ __metadata: languageName: node linkType: hard -"aria-query@npm:^4.2.2": - version: 4.2.2 - resolution: "aria-query@npm:4.2.2" - dependencies: - "@babel/runtime": ^7.10.2 - "@babel/runtime-corejs3": ^7.10.2 - checksum: 38401a9a400f26f3dcc24b84997461a16b32869a9893d323602bed8da40a8bcc0243b8d2880e942249a1496cea7a7de769e93d21c0baa439f01e1ee936fed665 - languageName: node - linkType: hard - -"aria-query@npm:^5.0.0": +"aria-query@npm:^5.0.0, aria-query@npm:^5.1.3": version: 5.1.3 resolution: "aria-query@npm:5.1.3" dependencies: @@ -4221,7 +4253,7 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.4, array-includes@npm:^3.1.5, array-includes@npm:^3.1.6": +"array-includes@npm:^3.1.5, array-includes@npm:^3.1.6": version: 3.1.6 resolution: "array-includes@npm:3.1.6" dependencies: @@ -4264,7 +4296,7 @@ __metadata: languageName: node linkType: hard -"array.prototype.flat@npm:^1.2.5": +"array.prototype.flat@npm:^1.3.1": version: 1.3.1 resolution: "array.prototype.flat@npm:1.3.1" dependencies: @@ -4395,10 +4427,10 @@ __metadata: languageName: node linkType: hard -"axe-core@npm:^4.4.3": - version: 4.5.2 - resolution: "axe-core@npm:4.5.2" - checksum: 4068f183b2ef1db7e5a75606032c238781abfaa34ab4c23177e17f7dff8cc83f175e887b52689d20d88d2d4f001cbf632bd98925850026fe1d9abc739cabcf16 +"axe-core@npm:^4.6.2": + version: 4.6.3 + resolution: "axe-core@npm:4.6.3" + checksum: d0c46be92b9707c48b88a53cd5f471b155a2bfc8bf6beffb514ecd14e30b4863e340b5fc4f496d82a3c562048088c1f3ff5b93b9b3b026cb9c3bfacfd535da10 languageName: node linkType: hard @@ -4412,10 +4444,12 @@ __metadata: languageName: node linkType: hard -"axobject-query@npm:^2.2.0": - version: 2.2.0 - resolution: "axobject-query@npm:2.2.0" - checksum: 96b8c7d807ca525f41ad9b286186e2089b561ba63a6d36c3e7d73dc08150714660995c7ad19cda05784458446a0793b45246db45894631e13853f48c1aa3117f +"axobject-query@npm:^3.1.1": + version: 3.1.1 + resolution: "axobject-query@npm:3.1.1" + dependencies: + deep-equal: ^2.0.5 + checksum: c12a5da10dc7bab75e1cda9b6a3b5fcf10eba426ddf1a17b71ef65a434ed707ede7d1c4f013ba1609e970bc8c0cddac01365080d376204314e9b294719acd8a5 languageName: node linkType: hard @@ -4704,14 +4738,14 @@ __metadata: linkType: hard "bonjour-service@npm:^1.0.11": - version: 1.0.14 - resolution: "bonjour-service@npm:1.0.14" + version: 1.1.0 + resolution: "bonjour-service@npm:1.1.0" dependencies: array-flatten: ^2.1.2 dns-equal: ^1.0.0 fast-deep-equal: ^3.1.3 multicast-dns: ^7.2.5 - checksum: 4a825bbf1824147ba8295a182fb3e86a8bae5159a08e2f118e829a0c988043a559f1f6e4eab425fe17ee9a1f080115d30430e78962e53f75bb03e2021ee7c5b2 + checksum: c0cdf6f6438ef4873ffd17768a9e62300ca30ac2bc3437bcfb6c75a3efd70ad80418c38ec19af2f5fe3a9f1dee725b83ff8e0c4a473b1b9f1718a39033b34cbf languageName: node linkType: hard @@ -4732,6 +4766,22 @@ __metadata: languageName: node linkType: hard +"boxen@npm:7.0.0": + version: 7.0.0 + resolution: "boxen@npm:7.0.0" + dependencies: + ansi-align: ^3.0.1 + camelcase: ^7.0.0 + chalk: ^5.0.1 + cli-boxes: ^3.0.0 + string-width: ^5.1.2 + type-fest: ^2.13.0 + widest-line: ^4.0.1 + wrap-ansi: ^8.0.1 + checksum: b917cf7a168ef3149635a8c02d5c9717d66182348bd27038d85328ad12655151e3324db0f2815253846c33e5f0ddf28b6cd52d56a12b9f88617b7f8f722b946a + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -4768,16 +4818,16 @@ __metadata: linkType: hard "browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.20.2, browserslist@npm:^4.21.3, browserslist@npm:^4.21.4": - version: 4.21.4 - resolution: "browserslist@npm:4.21.4" + version: 4.21.5 + resolution: "browserslist@npm:4.21.5" dependencies: - caniuse-lite: ^1.0.30001400 - electron-to-chromium: ^1.4.251 - node-releases: ^2.0.6 - update-browserslist-db: ^1.0.9 + caniuse-lite: ^1.0.30001449 + electron-to-chromium: ^1.4.284 + node-releases: ^2.0.8 + update-browserslist-db: ^1.0.10 bin: browserslist: cli.js - checksum: 4af3793704dbb4615bcd29059ab472344dc7961c8680aa6c4bb84f05340e14038d06a5aead58724eae69455b8fade8b8c69f1638016e87e5578969d74c078b79 + checksum: 9755986b22e73a6a1497fd8797aedd88e04270be33ce66ed5d85a1c8a798292a65e222b0f251bafa1c2522261e237d73b08b58689d4920a607e5a53d56dc4706 languageName: node linkType: hard @@ -4903,6 +4953,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^7.0.0": + version: 7.0.1 + resolution: "camelcase@npm:7.0.1" + checksum: 86ab8f3ebf08bcdbe605a211a242f00ed30d8bfb77dab4ebb744dd36efbc84432d1c4adb28975ba87a1b8be40a80fbd1e60e2f06565315918fa7350011a26d3d + languageName: node + linkType: hard + "caniuse-api@npm:^3.0.0": version: 3.0.0 resolution: "caniuse-api@npm:3.0.0" @@ -4915,10 +4972,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001400": - version: 1.0.30001434 - resolution: "caniuse-lite@npm:1.0.30001434" - checksum: 7c9d2641e8e8f3ddf9af14c4ce47266a9d8fd1fc0243626049ff1b2eca4bf02938ff440813cc3feae3fa8d851ec8d1b9718044340c8d09bb4372d92d4f6b519c +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001449": + version: 1.0.30001450 + resolution: "caniuse-lite@npm:1.0.30001450" + checksum: 511b360bfc907b2e437699364cf96b83507bc45043926450056642332bcd6f65a1e72540c828534ae15e0ac906e3e9af46cb2bb84458dd580bc31478e9dce282 languageName: node linkType: hard @@ -4991,6 +5048,7 @@ __metadata: "@carbon/icons-react": 10.49.0 "@commitlint/cli": 17.0.1 "@commitlint/config-conventional": 7.6.0 + "@playwright/test": ^1.30.0 "@tanstack/react-table": ^8.3.3 "@testing-library/dom": ^8.17.1 "@testing-library/jest-dom": ^5.16.5 @@ -5011,11 +5069,13 @@ __metadata: react-dnd: ^16.0.1 react-dnd-html5-backend: ^16.0.1 react-dom: 18.2.0 + react-infinite-scroller: ^1.2.6 react-plotly.js: ^2.5.1 react-router-dom: 5.3.3 react-scripts: 5.0.1 react-sizeme: ^2.6.12 sass: 1.52.1 + serve: ^14.2.0 wait-for-expect: 3.0.2 languageName: unknown linkType: soft @@ -5027,6 +5087,22 @@ __metadata: languageName: node linkType: hard +"chalk-template@npm:0.4.0": + version: 0.4.0 + resolution: "chalk-template@npm:0.4.0" + dependencies: + chalk: ^4.1.2 + checksum: 6c706802a79a7963cbce18f022b046fe86e438a67843151868852f80ea7346e975a6a9749991601e7e5d3b6a6c4852a04c53dc966a9a3d04031bd0e0ed53c819 + languageName: node + linkType: hard + +"chalk@npm:5.0.1": + version: 5.0.1 + resolution: "chalk@npm:5.0.1" + checksum: 7b45300372b908f0471fbf7389ce2f5de8d85bb949026fd51a1b95b10d0ed32c7ed5aab36dd5e9d2bf3191867909b4404cef75c5f4d2d1daeeacd301dd280b76 + languageName: node + linkType: hard + "chalk@npm:^2.0.0, chalk@npm:^2.4.1": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -5058,6 +5134,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.0.1": + version: 5.2.0 + resolution: "chalk@npm:5.2.0" + checksum: 03d8060277de6cf2fd567dc25fcf770593eb5bb85f460ce443e49255a30ff1242edd0c90a06a03803b0466ff0687a939b41db1757bec987113e83de89a003caa + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -5113,9 +5196,9 @@ __metadata: linkType: hard "ci-info@npm:^3.2.0": - version: 3.7.0 - resolution: "ci-info@npm:3.7.0" - checksum: 6e5df0250382ff3732703b36b958d2d892dd3c481f9671666f96c2ab7888be744bc4dca81395be958dcb828502d94f18fa9aa8901c5a3c9923cda212df02724c + version: 3.7.1 + resolution: "ci-info@npm:3.7.1" + checksum: 72d93d5101ea1c186511277fbd8d06ae8a6e028cc2fb94361e92bf735b39c5ebd192e8d15a66ff8c4e3ed569f87c2f844e96f90e141b2de5c649f77ec34ff601 languageName: node linkType: hard @@ -5148,11 +5231,11 @@ __metadata: linkType: hard "clean-css@npm:^5.2.2": - version: 5.3.1 - resolution: "clean-css@npm:5.3.1" + version: 5.3.2 + resolution: "clean-css@npm:5.3.2" dependencies: source-map: ~0.6.0 - checksum: 860696c60503cbfec480b5f92f62729246304b55950571af7292f2687b57f86b277f2b9fefe6f64643d409008018b78383972b55c2cc859792dcc8658988fb16 + checksum: 8787b281acc9878f309b5f835d410085deedfd4e126472666773040a6a8a72f472a1d24185947d23b87b1c419bf2c5ed429395d5c5ff8279c98b05d8011e9758 languageName: node linkType: hard @@ -5163,6 +5246,13 @@ __metadata: languageName: node linkType: hard +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 637d84419d293a9eac40a1c8c96a2859e7d98b24a1a317788e13c8f441be052fc899480c6acab3acc82eaf1bccda6b7542d7cdcf5c9c3cc39227175dc098d5b2 + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -5192,6 +5282,17 @@ __metadata: languageName: node linkType: hard +"clipboardy@npm:3.0.0": + version: 3.0.0 + resolution: "clipboardy@npm:3.0.0" + dependencies: + arch: ^2.2.0 + execa: ^5.1.1 + is-wsl: ^2.2.0 + checksum: 2c292acb59705494cbe07d7df7c8becff4f01651514d32ebd80f4aec2d20946d8f3824aac67ecdf2d09ef21fdf0eb24b6a7f033c137ccdceedc4661c54455c94 + languageName: node + linkType: hard + "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -5470,7 +5571,7 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.7.4": +"compression@npm:1.7.4, compression@npm:^1.7.4": version: 1.7.4 resolution: "compression@npm:1.7.4" dependencies: @@ -5499,9 +5600,9 @@ __metadata: linkType: hard "compute-scroll-into-view@npm:^1.0.13": - version: 1.0.17 - resolution: "compute-scroll-into-view@npm:1.0.17" - checksum: b20c05a10c37813c5a6e4bf053c00f65c88d23afed7a6bd7a2a69e05e2ffc2df3483ecfe407d36bf16b8cec8be21ae1966c9c523093a03117e567156cd79a51e + version: 1.0.20 + resolution: "compute-scroll-into-view@npm:1.0.20" + checksum: f15fab29221953620735393ac1866541aab0d27d28078bedbba071a291ee9c8cc1a72bee302cf0bc06ed83c5e55afb74ebcbd634a63671ba33cf1fb1c51d3308 languageName: node linkType: hard @@ -5559,6 +5660,13 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:0.5.2": + version: 0.5.2 + resolution: "content-disposition@npm:0.5.2" + checksum: 298d7da63255a38f7858ee19c7b6aae32b167e911293174b4c1349955e97e78e1d0b0d06c10e229405987275b417cf36ff65cbd4821a98bc9df4e41e9372cde7 + languageName: node + linkType: hard + "content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -5569,9 +5677,9 @@ __metadata: linkType: hard "content-type@npm:~1.0.4": - version: 1.0.4 - resolution: "content-type@npm:1.0.4" - checksum: 3d93585fda985d1554eca5ebd251994327608d2e200978fdbfba21c0c679914d5faf266d17027de44b34a72c7b0745b18584ecccaa7e1fdfb6a68ac7114f12e0 + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 languageName: node linkType: hard @@ -5632,25 +5740,25 @@ __metadata: linkType: hard "core-js-compat@npm:^3.25.1": - version: 3.26.1 - resolution: "core-js-compat@npm:3.26.1" + version: 3.27.2 + resolution: "core-js-compat@npm:3.27.2" dependencies: browserslist: ^4.21.4 - checksum: f222bce0002eae405327d68286e1d566037e8ac21906a47d7ecd15858adca7b12e82140db11dc43c8cc1fc066c5306120f3c27bfb2d7dbc2d20a72a2d90d38dc + checksum: 4574d4507de8cba9a75e37401b3ca6e5908ab066ec717e3b34866d25f623e1aa614fb886e10973be64a6250f325dcba6809e4fae4ed43375cc3e4276c5514c13 languageName: node linkType: hard -"core-js-pure@npm:^3.23.3, core-js-pure@npm:^3.25.1": - version: 3.26.1 - resolution: "core-js-pure@npm:3.26.1" - checksum: d88c40e5e29e413c11d1ef991a8d5b6a63f00bd94707af0f649d3fc18b3524108b202f4ae75ce77620a1557d1ba340bc3362b4f25d590eccc37cf80fc75f7cd4 +"core-js-pure@npm:^3.23.3": + version: 3.27.2 + resolution: "core-js-pure@npm:3.27.2" + checksum: 7cb24502a782a032ffa2af6e84abfcfeffa0c30e84c38f4d0a1d7567c8c86e2d36a7554a00ca47762606c84d2a86d99662a7158e9f4df989f3fe3c7e7c09fa45 languageName: node linkType: hard "core-js@npm:^3.19.2": - version: 3.26.1 - resolution: "core-js@npm:3.26.1" - checksum: 0a01149f51ff1e9f41d1ea49cc4c9222047949ea597189ede7c4cf8cde3b097766b9c7615acc77c86fe65b4002f20b638a133dfba7b41dba830d707aeeed45ad + version: 3.27.2 + resolution: "core-js@npm:3.27.2" + checksum: 718debd426f55a6b97cf9b757c936be258afd6d4f7052f89d0f96c982d7013e9000b0b006df42831a0cf32adad298e34d6a19052dce9ae1c7ab87162c0c665e0 languageName: node linkType: hard @@ -5662,14 +5770,14 @@ __metadata: linkType: hard "cosmiconfig-typescript-loader@npm:^4.0.0": - version: 4.2.0 - resolution: "cosmiconfig-typescript-loader@npm:4.2.0" + version: 4.3.0 + resolution: "cosmiconfig-typescript-loader@npm:4.3.0" peerDependencies: "@types/node": "*" cosmiconfig: ">=7" ts-node: ">=10" typescript: ">=3" - checksum: bbfe0dd4b8afe93880dbd85aeae551799ff05ecec23b7490bab56366d8362024ee12da954c86c16448d5919c47f0ac23d5d4e64062cda09e6f0ff63c9e080346 + checksum: ea61dfd8e112cf2bb18df0ef89280bd3ae3dd5b997b4a9fc22bbabdc02513aadfbc6d4e15e922b6a9a5d987e9dad42286fa38caf77a9b8dcdbe7d4ce1c9db4fb languageName: node linkType: hard @@ -5699,6 +5807,18 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^8.0.0": + version: 8.0.0 + resolution: "cosmiconfig@npm:8.0.0" + dependencies: + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + parse-json: ^5.0.0 + path-type: ^4.0.0 + checksum: ff4cdf89ac1ae52e7520816622c21a9e04380d04b82d653f5139ec581aa4f7f29e096d46770bc76c4a63c225367e88a1dfa233ea791669a35101f5f9b972c7d1 + languageName: node + linkType: hard + "country-regex@npm:^1.1.0": version: 1.1.0 resolution: "country-regex@npm:1.1.0" @@ -5819,11 +5939,11 @@ __metadata: linkType: hard "css-loader@npm:^6.5.1": - version: 6.7.2 - resolution: "css-loader@npm:6.7.2" + version: 6.7.3 + resolution: "css-loader@npm:6.7.3" dependencies: icss-utils: ^5.1.0 - postcss: ^8.4.18 + postcss: ^8.4.19 postcss-modules-extract-imports: ^3.0.0 postcss-modules-local-by-default: ^4.0.0 postcss-modules-scope: ^3.0.0 @@ -5832,7 +5952,7 @@ __metadata: semver: ^7.3.8 peerDependencies: webpack: ^5.0.0 - checksum: f3c980cc9c033a02e60df7e5a2f33a1e8c2c3dd552f017485d2d81b383be623ae8c4189404e7a4a7403b52744683ae4b516def0f7ccf125c2b198cb647e46543 + checksum: 473cc32b6c837c2848e2051ad1ba331c1457449f47442e75a8c480d9891451434ada241f7e3de2347e57de17fcd84610b3bcfc4a9da41102cdaedd1e17902d31 languageName: node linkType: hard @@ -5960,9 +6080,9 @@ __metadata: linkType: hard "cssdb@npm:^7.1.0": - version: 7.1.0 - resolution: "cssdb@npm:7.1.0" - checksum: 19870ddc2f51d10179bd6278c3c3bf7c54abc342b1070ea496cd9eceb564a4f1820bc9133e5c796fd53d974b68dc30d0f4093a70733d215d9c722663800ef151 + version: 7.4.1 + resolution: "cssdb@npm:7.4.1" + checksum: c58803ce3e0e60af8a5a1101f365a5ea0cf1a6a5eddcd5c6fb5d92aaf187e2c11c5620185fd7096cb920c3ce087f9edb2234348a33d06433e12c1ce7fcf93197 languageName: node linkType: hard @@ -6096,10 +6216,10 @@ __metadata: languageName: node linkType: hard -"d3-color@npm:1": - version: 1.4.1 - resolution: "d3-color@npm:1.4.1" - checksum: a214b61458b5fcb7ad1a84faed0e02918037bab6be37f2d437bf0e2915cbd854d89fbf93754f17b0781c89e39d46704633d05a2bfae77e6209f0f4b140f9894b +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 4931fbfda5d7c4b5cfa283a13c91a954f86e3b69d75ce588d06cde6c3628cebfc3af2069ccf225e982e8987c612aa7948b3932163ce15eb3c11cd7c003f3ee3b languageName: node linkType: hard @@ -6163,12 +6283,12 @@ __metadata: languageName: node linkType: hard -"d3-interpolate@npm:^1.4.0": - version: 1.4.0 - resolution: "d3-interpolate@npm:1.4.0" +"d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" dependencies: - d3-color: 1 - checksum: d98988bd1e2f59d01f100d0a19315ad8f82ef022aa09a65aff76f747a44f9b52f2d64c6578b8f47e01f2b14a8f0ef88f5460d11173c0dd2d58238c217ac0ec03 + d3-color: 1 - 3 + checksum: a42ba314e295e95e5365eff0f604834e67e4a3b3c7102458781c477bd67e9b24b6bb9d8e41ff5521050a3f2c7c0c4bbbb6e187fd586daa3980943095b267e78b languageName: node linkType: hard @@ -6253,7 +6373,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2, debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:^2.6.9": +"debug@npm:2, debug@npm:2.6.9, debug@npm:^2.6.0": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -6301,9 +6421,9 @@ __metadata: linkType: hard "decimal.js@npm:^10.2.1": - version: 10.4.2 - resolution: "decimal.js@npm:10.4.2" - checksum: 536cd6816a3197f2e1aa3da4860856cb5a2db73f6fafe8cb3b924ccc63f9b7d78296acc13dccbd419bd958ccc6357921fb15467f883b37cab04bfba7044cada2 + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae languageName: node linkType: hard @@ -6315,15 +6435,17 @@ __metadata: linkType: hard "deep-equal@npm:^2.0.5": - version: 2.1.0 - resolution: "deep-equal@npm:2.1.0" + version: 2.2.0 + resolution: "deep-equal@npm:2.2.0" dependencies: call-bind: ^1.0.2 es-get-iterator: ^1.1.2 get-intrinsic: ^1.1.3 is-arguments: ^1.1.1 + is-array-buffer: ^3.0.1 is-date-object: ^1.0.5 is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 isarray: ^2.0.5 object-is: ^1.1.5 object-keys: ^1.1.1 @@ -6332,8 +6454,15 @@ __metadata: side-channel: ^1.0.4 which-boxed-primitive: ^1.0.2 which-collection: ^1.0.1 - which-typed-array: ^1.1.8 - checksum: a3efc772f14372d2a88bb1e414ab2218cf23cc77673521bbccbb2fc128dd8b6cccfad05eb35b9a8a4669bd7f3ecebaa137beebdf549b7be56c617bd5488ca987 + which-typed-array: ^1.1.9 + checksum: 46a34509d2766d6c6dc5aec4756089cf0cc137e46787e91f08f1ee0bb570d874f19f0493146907df0cf18aed4a7b4b50f6f62c899240a76c323f057528b122e3 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 languageName: node linkType: hard @@ -6345,9 +6474,9 @@ __metadata: linkType: hard "deepmerge@npm:^4.2.2": - version: 4.2.2 - resolution: "deepmerge@npm:4.2.2" - checksum: a8c43a1ed8d6d1ed2b5bf569fa4c8eb9f0924034baf75d5d406e47e157a451075c4db353efea7b6bcc56ec48116a8ce72fccf867b6e078e7c561904b5897530b + version: 4.3.0 + resolution: "deepmerge@npm:4.3.0" + checksum: c7980eb5c5be040b371f1df0d566473875cfabed9f672ccc177b81ba8eee5686ce2478de2f1d0076391621cbe729e5eacda397179a59ef0f68901849647db126 languageName: node linkType: hard @@ -6563,9 +6692,9 @@ __metadata: linkType: hard "dom-accessibility-api@npm:^0.5.6, dom-accessibility-api@npm:^0.5.9": - version: 0.5.14 - resolution: "dom-accessibility-api@npm:0.5.14" - checksum: 782c813f75a09ba6735ef03b5e1624406a3829444ae49d5bdedd272a49d437ae3354f53e02ffc8c9fd9165880250f41546538f27461f839dd4ea1234e77e8d5e + version: 0.5.16 + resolution: "dom-accessibility-api@npm:0.5.16" + checksum: 005eb283caef57fc1adec4d5df4dd49189b628f2f575af45decb210e04d634459e3f1ee64f18b41e2dcf200c844bc1d9279d80807e686a30d69a4756151ad248 languageName: node linkType: hard @@ -6784,10 +6913,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.251": - version: 1.4.284 - resolution: "electron-to-chromium@npm:1.4.284" - checksum: be496e9dca6509dbdbb54dc32146fc99f8eb716d28a7ee8ccd3eba0066561df36fc51418d8bd7cf5a5891810bf56c0def3418e74248f51ea4a843d423603d10a +"electron-to-chromium@npm:^1.4.284": + version: 1.4.286 + resolution: "electron-to-chromium@npm:1.4.286" + checksum: 6b53e2aea63892cb4af85ea4ee5ed2b6d848713519987efcf4c1177a32e2fe6d04a7f591f5bcd1feab0b3c88890c6eaf65b6feb16c0e0319bf07e31de31930af languageName: node linkType: hard @@ -6926,34 +7055,43 @@ __metadata: linkType: hard "es-abstract@npm:^1.17.2, es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4": - version: 1.20.4 - resolution: "es-abstract@npm:1.20.4" + version: 1.21.1 + resolution: "es-abstract@npm:1.21.1" dependencies: + available-typed-arrays: ^1.0.5 call-bind: ^1.0.2 + es-set-tostringtag: ^2.0.1 es-to-primitive: ^1.2.1 function-bind: ^1.1.1 function.prototype.name: ^1.1.5 get-intrinsic: ^1.1.3 get-symbol-description: ^1.0.0 + globalthis: ^1.0.3 + gopd: ^1.0.1 has: ^1.0.3 has-property-descriptors: ^1.0.0 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - internal-slot: ^1.0.3 + internal-slot: ^1.0.4 + is-array-buffer: ^3.0.1 is-callable: ^1.2.7 is-negative-zero: ^2.0.2 is-regex: ^1.1.4 is-shared-array-buffer: ^1.0.2 is-string: ^1.0.7 + is-typed-array: ^1.1.10 is-weakref: ^1.0.2 object-inspect: ^1.12.2 object-keys: ^1.1.1 object.assign: ^4.1.4 regexp.prototype.flags: ^1.4.3 safe-regex-test: ^1.0.0 - string.prototype.trimend: ^1.0.5 - string.prototype.trimstart: ^1.0.5 + string.prototype.trimend: ^1.0.6 + string.prototype.trimstart: ^1.0.6 + typed-array-length: ^1.0.4 unbox-primitive: ^1.0.2 - checksum: 89297cc785c31aedf961a603d5a07ed16471e435d3a1b6d070b54f157cf48454b95cda2ac55e4b86ff4fe3276e835fcffd2771578e6fa634337da49b26826141 + which-typed-array: ^1.1.9 + checksum: 23ff60d42d17a55d150e7bcedbdb065d4077a8b98c436e0e2e1ef4dd532a6d78a56028673de0bd8ed464a43c46ba781c50d9af429b6a17e44dbd14c7d7fb7926 languageName: node linkType: hard @@ -6965,18 +7103,19 @@ __metadata: linkType: hard "es-get-iterator@npm:^1.1.2": - version: 1.1.2 - resolution: "es-get-iterator@npm:1.1.2" + version: 1.1.3 + resolution: "es-get-iterator@npm:1.1.3" dependencies: call-bind: ^1.0.2 - get-intrinsic: ^1.1.0 - has-symbols: ^1.0.1 - is-arguments: ^1.1.0 + get-intrinsic: ^1.1.3 + has-symbols: ^1.0.3 + is-arguments: ^1.1.1 is-map: ^2.0.2 is-set: ^2.0.2 - is-string: ^1.0.5 + is-string: ^1.0.7 isarray: ^2.0.5 - checksum: f75e66acb6a45686fa08b3ade9c9421a70d36a0c43ed4363e67f4d7aab2226cb73dd977cb48abbaf75721b946d3cd810682fcf310c7ad0867802fbf929b17dcf + stop-iteration-iterator: ^1.0.0 + checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d languageName: node linkType: hard @@ -6987,6 +7126,17 @@ __metadata: languageName: node linkType: hard +"es-set-tostringtag@npm:^2.0.1": + version: 2.0.1 + resolution: "es-set-tostringtag@npm:2.0.1" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + has-tostringtag: ^1.0.0 + checksum: ec416a12948cefb4b2a5932e62093a7cf36ddc3efd58d6c58ca7ae7064475ace556434b869b0bbeb0c365f1032a8ccd577211101234b69837ad83ad204fff884 + languageName: node + linkType: hard + "es-shim-unscopables@npm:^1.0.0": version: 1.0.0 resolution: "es-shim-unscopables@npm:1.0.0" @@ -7148,17 +7298,18 @@ __metadata: languageName: node linkType: hard -"eslint-import-resolver-node@npm:^0.3.6": - version: 0.3.6 - resolution: "eslint-import-resolver-node@npm:0.3.6" +"eslint-import-resolver-node@npm:^0.3.7": + version: 0.3.7 + resolution: "eslint-import-resolver-node@npm:0.3.7" dependencies: debug: ^3.2.7 - resolve: ^1.20.0 - checksum: 6266733af1e112970e855a5bcc2d2058fb5ae16ad2a6d400705a86b29552b36131ffc5581b744c23d550de844206fb55e9193691619ee4dbf225c4bde526b1c8 + is-core-module: ^2.11.0 + resolve: ^1.22.1 + checksum: 3379aacf1d2c6952c1b9666c6fa5982c3023df695430b0d391c0029f6403a7775414873d90f397e98ba6245372b6c8960e16e74d9e4a3b0c0a4582f3bdbe3d6e languageName: node linkType: hard -"eslint-module-utils@npm:^2.7.3": +"eslint-module-utils@npm:^2.7.4": version: 2.7.4 resolution: "eslint-module-utils@npm:2.7.4" dependencies: @@ -7185,25 +7336,27 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.25.3": - version: 2.26.0 - resolution: "eslint-plugin-import@npm:2.26.0" + version: 2.27.5 + resolution: "eslint-plugin-import@npm:2.27.5" dependencies: - array-includes: ^3.1.4 - array.prototype.flat: ^1.2.5 - debug: ^2.6.9 + array-includes: ^3.1.6 + array.prototype.flat: ^1.3.1 + array.prototype.flatmap: ^1.3.1 + debug: ^3.2.7 doctrine: ^2.1.0 - eslint-import-resolver-node: ^0.3.6 - eslint-module-utils: ^2.7.3 + eslint-import-resolver-node: ^0.3.7 + eslint-module-utils: ^2.7.4 has: ^1.0.3 - is-core-module: ^2.8.1 + is-core-module: ^2.11.0 is-glob: ^4.0.3 minimatch: ^3.1.2 - object.values: ^1.1.5 - resolve: ^1.22.0 + object.values: ^1.1.6 + resolve: ^1.22.1 + semver: ^6.3.0 tsconfig-paths: ^3.14.1 peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 0bf77ad80339554481eafa2b1967449e1f816b94c7a6f9614ce33fb4083c4e6c050f10d241dd50b4975d47922880a34de1e42ea9d8e6fd663ebb768baa67e655 + checksum: f500571a380167e25d72a4d925ef9a7aae8899eada57653e5f3051ec3d3c16d08271fcefe41a30a9a2f4fefc232f066253673ee4ea77b30dba65ae173dade85d languageName: node linkType: hard @@ -7225,25 +7378,28 @@ __metadata: linkType: hard "eslint-plugin-jsx-a11y@npm:^6.5.1": - version: 6.6.1 - resolution: "eslint-plugin-jsx-a11y@npm:6.6.1" + version: 6.7.1 + resolution: "eslint-plugin-jsx-a11y@npm:6.7.1" dependencies: - "@babel/runtime": ^7.18.9 - aria-query: ^4.2.2 - array-includes: ^3.1.5 + "@babel/runtime": ^7.20.7 + aria-query: ^5.1.3 + array-includes: ^3.1.6 + array.prototype.flatmap: ^1.3.1 ast-types-flow: ^0.0.7 - axe-core: ^4.4.3 - axobject-query: ^2.2.0 + axe-core: ^4.6.2 + axobject-query: ^3.1.1 damerau-levenshtein: ^1.0.8 emoji-regex: ^9.2.2 has: ^1.0.3 - jsx-ast-utils: ^3.3.2 - language-tags: ^1.0.5 + jsx-ast-utils: ^3.3.3 + language-tags: =1.0.5 minimatch: ^3.1.2 + object.entries: ^1.1.6 + object.fromentries: ^2.0.6 semver: ^6.3.0 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: baae7377f0e25a0cc9b34dc333a3dc6ead9ee8365e445451eff554c3ca267a0a6cb88127fe90395c578ab1b92cfed246aef7dc8d2b48b603389e10181799e144 + checksum: f166dd5fe7257c7b891c6692e6a3ede6f237a14043ae3d97581daf318fc5833ddc6b4871aa34ab7656187430170500f6d806895747ea17ecdf8231a666c3c2fd languageName: node linkType: hard @@ -7257,8 +7413,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.27.1": - version: 7.31.11 - resolution: "eslint-plugin-react@npm:7.31.11" + version: 7.32.2 + resolution: "eslint-plugin-react@npm:7.32.2" dependencies: array-includes: ^3.1.6 array.prototype.flatmap: ^1.3.1 @@ -7272,23 +7428,23 @@ __metadata: object.hasown: ^1.1.2 object.values: ^1.1.6 prop-types: ^15.8.1 - resolve: ^2.0.0-next.3 + resolve: ^2.0.0-next.4 semver: ^6.3.0 string.prototype.matchall: ^4.0.8 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: a3d612f6647bef33cf2a67c81a6b37b42c075300ed079cffecf5fb475c0d6ab855c1de340d1cbf361a0126429fb906dda597527235d2d12c4404453dbc712fc6 + checksum: 2232b3b8945aa50b7773919c15cd96892acf35d2f82503667a79e2f55def90f728ed4f0e496f0f157acbe1bd4397c5615b676ae7428fe84488a544ca53feb944 languageName: node linkType: hard "eslint-plugin-testing-library@npm:^5.0.1": - version: 5.9.1 - resolution: "eslint-plugin-testing-library@npm:5.9.1" + version: 5.10.0 + resolution: "eslint-plugin-testing-library@npm:5.10.0" dependencies: - "@typescript-eslint/utils": ^5.13.0 + "@typescript-eslint/utils": ^5.43.0 peerDependencies: eslint: ^7.5.0 || ^8.0.0 - checksum: d09f9486945807e9587d52b6979117bc41b750df741567381a06219671096afb318696a0e0db63e253e150fead40e77ef9653ee00f1dda83fc8920e3b3c47107 + checksum: 3278fc4683a99d24ac2b6d2ed0359db1b0509674350e4b9a958a226f57b4b90e070c02e1f4c2806da885d8025c1e8c952cb9a5e9751e69baac3d12cfe6804000 languageName: node linkType: hard @@ -7354,11 +7510,11 @@ __metadata: linkType: hard "eslint@npm:^8.3.0": - version: 8.28.0 - resolution: "eslint@npm:8.28.0" + version: 8.33.0 + resolution: "eslint@npm:8.33.0" dependencies: - "@eslint/eslintrc": ^1.3.3 - "@humanwhocodes/config-array": ^0.11.6 + "@eslint/eslintrc": ^1.4.1 + "@humanwhocodes/config-array": ^0.11.8 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 ajv: ^6.10.0 @@ -7377,7 +7533,7 @@ __metadata: file-entry-cache: ^6.0.1 find-up: ^5.0.0 glob-parent: ^6.0.2 - globals: ^13.15.0 + globals: ^13.19.0 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 import-fresh: ^3.0.0 @@ -7398,7 +7554,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 1b793486b2ec80f0602d75fff7116f7c39a3286f523608a999eead9bec4154a06841785d2b4fb87f8292a94cf85778c1dbfaec727772a09c4d604fdb9ff0809a + checksum: 727e63ab8b7acf281442323c5971f6afdd5b656fbcebc4476cf54e35af51b2f180617433fc5e1952f0449ca3f43a905527f9407ea4b8a7ea7562fc9c3f278d4c languageName: node linkType: hard @@ -7527,15 +7683,15 @@ __metadata: linkType: hard "expect@npm:^29.0.0": - version: 29.3.1 - resolution: "expect@npm:29.3.1" + version: 29.4.1 + resolution: "expect@npm:29.4.1" dependencies: - "@jest/expect-utils": ^29.3.1 + "@jest/expect-utils": ^29.4.1 jest-get-type: ^29.2.0 - jest-matcher-utils: ^29.3.1 - jest-message-util: ^29.3.1 - jest-util: ^29.3.1 - checksum: e9588c2a430b558b9a3dc72d4ad05f36b047cb477bc6a7bb9cfeef7614fe7e5edbab424c2c0ce82739ee21ecbbbd24596259528209f84cd72500cc612d910d30 + jest-matcher-utils: ^29.4.1 + jest-message-util: ^29.4.1 + jest-util: ^29.4.1 + checksum: 5918f69371557bbceb01bc163cd0ac03e8cbbc5de761892a9c27ef17a1f9e94dc91edd8298b4eaca18b71ba4a9d521c74b072f0a46950b13d6b61123b0431836 languageName: node linkType: hard @@ -7640,12 +7796,21 @@ __metadata: languageName: node linkType: hard +"fast-url-parser@npm:1.1.3": + version: 1.1.3 + resolution: "fast-url-parser@npm:1.1.3" + dependencies: + punycode: ^1.3.2 + checksum: 5043d0c4a8d775ff58504d56c096563c11b113e4cb8a2668c6f824a1cd4fb3812e2fdf76537eb24a7ce4ae7def6bd9747da630c617cf2a4b6ce0c42514e4f21c + languageName: node + linkType: hard + "fastq@npm:^1.6.0": - version: 1.13.0 - resolution: "fastq@npm:1.13.0" + version: 1.15.0 + resolution: "fastq@npm:1.15.0" dependencies: reusify: ^1.0.4 - checksum: 32cf15c29afe622af187d12fc9cd93e160a0cb7c31a3bb6ace86b7dea3b28e7b72acde89c882663f307b2184e14782c6c664fa315973c03626c7d4bff070bb0b + checksum: 0170e6bfcd5d57a70412440b8ef600da6de3b2a6c5966aeaf0a852d542daff506a0ee92d6de7679d1de82e644bce69d7a574a6c93f0b03964b5337eed75ada1a languageName: node linkType: hard @@ -7947,6 +8112,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.0.0": + version: 11.1.0 + resolution: "fs-extra@npm:11.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: 5ca476103fa1f5ff4a9b3c4f331548f8a3c1881edaae323a4415d3153b5dc11dc6a981c8d1dd93eec8367ceee27b53f8bd27eecbbf66ffcdd04927510c171e7f + languageName: node + linkType: hard + "fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" @@ -8071,14 +8247,14 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3": - version: 1.1.3 - resolution: "get-intrinsic@npm:1.1.3" +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3": + version: 1.2.0 + resolution: "get-intrinsic@npm:1.2.0" dependencies: function-bind: ^1.1.1 has: ^1.0.3 has-symbols: ^1.0.3 - checksum: 152d79e87251d536cf880ba75cfc3d6c6c50e12b3a64e1ea960e73a3752b47c69f46034456eae1b0894359ce3bc64c55c186f2811f8a788b75b638b06fab228a + checksum: 78fc0487b783f5c58cf2dccafc3ae656ee8d2d8062a8831ce4a95e7057af4587a1d4882246c033aca0a7b4965276f4802b45cc300338d1b77a73d3e3e3f4877d languageName: node linkType: hard @@ -8222,15 +8398,15 @@ __metadata: linkType: hard "glob@npm:^8.0.1": - version: 8.0.3 - resolution: "glob@npm:8.0.3" + version: 8.1.0 + resolution: "glob@npm:8.1.0" dependencies: fs.realpath: ^1.0.0 inflight: ^1.0.4 inherits: 2 minimatch: ^5.0.1 once: ^1.3.0 - checksum: 50bcdea19d8e79d8de5f460b1939ffc2b3299eac28deb502093fdca22a78efebc03e66bf54f0abc3d3d07d8134d19a32850288b7440d77e072aa55f9d33b18c5 + checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 languageName: node linkType: hard @@ -8270,12 +8446,21 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.18.0 - resolution: "globals@npm:13.18.0" +"globals@npm:^13.19.0": + version: 13.20.0 + resolution: "globals@npm:13.20.0" dependencies: type-fest: ^0.20.2 - checksum: 9fdaa74cfd5d4ac91319662f512c29b11d1d2deb9c8a20d3998097671deba83d195f20730b2345887de3ddab958a6fa68952feed9ae836ee4594a82ace62fdb4 + checksum: ad1ecf914bd051325faad281d02ea2c0b1df5d01bd94d368dcc5513340eac41d14b3c61af325768e3c7f8d44576e72780ec0b6f2d366121f8eec6e03c3a3b97a + languageName: node + linkType: hard + +"globalthis@npm:^1.0.3": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998 languageName: node linkType: hard @@ -8560,6 +8745,13 @@ __metadata: languageName: node linkType: hard +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + "has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" @@ -8734,9 +8926,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -8907,9 +9099,9 @@ __metadata: linkType: hard "ignore@npm:^5.2.0": - version: 5.2.0 - resolution: "ignore@npm:5.2.0" - checksum: 6b1f926792d614f64c6c83da3a1f9c83f6196c2839aa41e1e32dd7b8d174cef2e329d75caabb62cb61ce9dc432f75e67d07d122a037312db7caa73166a1bdb77 + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef languageName: node linkType: hard @@ -8925,16 +9117,16 @@ __metadata: linkType: hard "immer@npm:^9.0.7": - version: 9.0.16 - resolution: "immer@npm:9.0.16" - checksum: e9a5ca65c929b329da7a3b7beccf7984271cda7bdd47b2cab619eac3277dcd56598c211b55cc340786b6eff0c06652ac018808d9fd744443f06882364dece6bc + version: 9.0.19 + resolution: "immer@npm:9.0.19" + checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 languageName: node linkType: hard "immutable@npm:^4.0.0": - version: 4.1.0 - resolution: "immutable@npm:4.1.0" - checksum: b9bc1f14fb18eb382d48339c064b24a1f97ae4cf43102e0906c0a6e186a27afcd18b55ca4a0b63c98eefb58143e2b5ebc7755a5fb4da4a7ad84b7a6096ac5b13 + version: 4.2.3 + resolution: "immutable@npm:4.2.3" + checksum: 11be9a328d5e3a35de47b7b7835e7d89f790bf19595a8de11e1ae19b2bb3be5def00c69928969f1607aa66fe4a3f48e95460b2b58894a83ce70b65c872466940 languageName: node linkType: hard @@ -9005,21 +9197,21 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.4, ini@npm:^1.3.5": +"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:~1.3.0": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 languageName: node linkType: hard -"internal-slot@npm:^1.0.3": - version: 1.0.3 - resolution: "internal-slot@npm:1.0.3" +"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.4": + version: 1.0.4 + resolution: "internal-slot@npm:1.0.4" dependencies: - get-intrinsic: ^1.1.0 + get-intrinsic: ^1.1.3 has: ^1.0.3 side-channel: ^1.0.4 - checksum: 1944f92e981e47aebc98a88ff0db579fd90543d937806104d0b96557b10c1f170c51fb777b97740a8b6ddeec585fca8c39ae99fd08a8e058dfc8ab70937238bf + checksum: 8974588d06bab4f675573a3b52975370facf6486df51bc0567a982c7024fa29495f10b76c0d4dc742dd951d1b72024fdc1e31bb0bedf1678dc7aacacaf5a4f73 languageName: node linkType: hard @@ -9053,7 +9245,7 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.0, is-arguments@npm:^1.1.1": +"is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" dependencies: @@ -9063,6 +9255,17 @@ __metadata: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.1": + version: 3.0.1 + resolution: "is-array-buffer@npm:3.0.1" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-typed-array: ^1.1.10 + checksum: f26ab87448e698285daf707e52a533920449f7abf63714140ffab9d5571aa5a71ac2fa2677e8b793ad0d5d3e40078d4d2c8a0ab39c957e3cfc6513bb6c9dfdc9 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -9133,7 +9336,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.5.0, is-core-module@npm:^2.8.1, is-core-module@npm:^2.9.0": +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.5.0, is-core-module@npm:^2.9.0": version: 2.11.0 resolution: "is-core-module@npm:2.11.0" dependencies: @@ -9239,10 +9442,10 @@ __metadata: languageName: node linkType: hard -"is-mobile@npm:^2.2.2": - version: 2.2.2 - resolution: "is-mobile@npm:2.2.2" - checksum: dcc428c9fb2af40eca896cb83d7d61e89d9750767aa5a18c757e5c331ad81dc0e229a859b74824e7fdf0add4f8c4884bd59ef0016450bdcddc60ee3a76e95de3 +"is-mobile@npm:^3.1.1": + version: 3.1.1 + resolution: "is-mobile@npm:3.1.1" + checksum: b7c549020ac4674520378623afc4976694ff686eb3761cfad12da936ba9c2d675687bdc3c82eadf5a25147ce51c682800679bf835e31de272f05c026cd2b2f14 languageName: node linkType: hard @@ -9311,6 +9514,13 @@ __metadata: languageName: node linkType: hard +"is-port-reachable@npm:4.0.0": + version: 4.0.0 + resolution: "is-port-reachable@npm:4.0.0" + checksum: 47b7e10db8edcef27fbf9e50f0de85ad368d35688790ca64a13db67260111ac5f4b98989b11af06199fa93f25d810bd09a5b21b2c2646529668638f7c34d3c04 + languageName: node + linkType: hard + "is-potential-custom-element-name@npm:^1.0.1": version: 1.0.1 resolution: "is-potential-custom-element-name@npm:1.0.1" @@ -9406,7 +9616,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10": +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": version: 1.1.10 resolution: "is-typed-array@npm:1.1.10" dependencies: @@ -9679,15 +9889,15 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^29.3.1": - version: 29.3.1 - resolution: "jest-diff@npm:29.3.1" +"jest-diff@npm:^29.4.1": + version: 29.4.1 + resolution: "jest-diff@npm:29.4.1" dependencies: chalk: ^4.0.0 diff-sequences: ^29.3.1 jest-get-type: ^29.2.0 - pretty-format: ^29.3.1 - checksum: ac5c09745f2b1897e6f53216acaf6ed44fc4faed8e8df053ff4ac3db5d2a1d06a17b876e49faaa15c8a7a26f5671bcbed0a93781dcc2835f781c79a716a591a9 + pretty-format: ^29.4.1 + checksum: 359af2d11a75bbb3c91e3def8cfd0ede00afc6fb5d69d9495f2af5f6e18f692adb940d8338a186159f75afe48088d82bce14e2cc272cad9a5c2148bf0bc7f6bf languageName: node linkType: hard @@ -9827,15 +10037,15 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^29.3.1": - version: 29.3.1 - resolution: "jest-matcher-utils@npm:29.3.1" +"jest-matcher-utils@npm:^29.4.1": + version: 29.4.1 + resolution: "jest-matcher-utils@npm:29.4.1" dependencies: chalk: ^4.0.0 - jest-diff: ^29.3.1 + jest-diff: ^29.4.1 jest-get-type: ^29.2.0 - pretty-format: ^29.3.1 - checksum: 311e8d9f1e935216afc7dd8c6acf1fbda67a7415e1afb1bf72757213dfb025c1f2dc5e2c185c08064a35cdc1f2d8e40c57616666774ed1b03e57eb311c20ec77 + pretty-format: ^29.4.1 + checksum: ea84dbcae82241cb28e94ff586660aeec51196d9245413dc516ce3aa78140b3ea728b1168b242281b59ad513b0148b9f12d674729bd043a894a3ba9d6ec164f4 languageName: node linkType: hard @@ -9873,20 +10083,20 @@ __metadata: languageName: node linkType: hard -"jest-message-util@npm:^29.3.1": - version: 29.3.1 - resolution: "jest-message-util@npm:29.3.1" +"jest-message-util@npm:^29.4.1": + version: 29.4.1 + resolution: "jest-message-util@npm:29.4.1" dependencies: "@babel/code-frame": ^7.12.13 - "@jest/types": ^29.3.1 + "@jest/types": ^29.4.1 "@types/stack-utils": ^2.0.0 chalk: ^4.0.0 graceful-fs: ^4.2.9 micromatch: ^4.0.4 - pretty-format: ^29.3.1 + pretty-format: ^29.4.1 slash: ^3.0.0 stack-utils: ^2.0.3 - checksum: 15d0a2fca3919eb4570bbf575734780c4b9e22de6aae903c4531b346699f7deba834c6c86fe6e9a83ad17fac0f7935511cf16dce4d71a93a71ebb25f18a6e07b + checksum: 7d49823401b6d42f0d2d63dd9c0f11d2f64783416f82a68634190abee46e600e25bb0b380c746726acc56e854687bb03a76e26e617fcdda78e8c6316423b694f languageName: node linkType: hard @@ -10082,17 +10292,17 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.3.1": - version: 29.3.1 - resolution: "jest-util@npm:29.3.1" +"jest-util@npm:^29.4.1": + version: 29.4.1 + resolution: "jest-util@npm:29.4.1" dependencies: - "@jest/types": ^29.3.1 + "@jest/types": ^29.4.1 "@types/node": "*" chalk: ^4.0.0 ci-info: ^3.2.0 graceful-fs: ^4.2.9 picomatch: ^2.2.3 - checksum: f67c60f062b94d21cb60e84b3b812d64b7bfa81fe980151de5c17a74eb666042d0134e2e756d099b7606a1fcf1d633824d2e58197d01d76dde1e2dc00dfcd413 + checksum: 10a0e6c448ace1386f728ee3b7669f67878bb0c2e668a902d11140cc3f75c89a18f4142a37a24ccb587ede20dad86d497b3e8df4f26848a9be50a44779d92bc9 languageName: node linkType: hard @@ -10210,9 +10420,9 @@ __metadata: linkType: hard "js-sdsl@npm:^4.1.4": - version: 4.2.0 - resolution: "js-sdsl@npm:4.2.0" - checksum: 2cd0885f7212afb355929d72ca105cb37de7e95ad6031e6a32619eaefa46735a7d0fb682641a0ba666e1519cb138fe76abc1eea8a34e224140c9d94c995171f1 + version: 4.3.0 + resolution: "js-sdsl@npm:4.3.0" + checksum: ce908257cf6909e213af580af3a691a736f5ee8b16315454768f917a682a4ea0c11bde1b241bbfaecedc0eb67b72101b2c2df2ffaed32aed5d539fca816f054e languageName: node linkType: hard @@ -10350,12 +10560,12 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.1": - version: 2.2.1 - resolution: "json5@npm:2.2.1" +"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 languageName: node linkType: hard @@ -10386,7 +10596,7 @@ __metadata: languageName: node linkType: hard -"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.2": +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.3": version: 3.3.3 resolution: "jsx-ast-utils@npm:3.3.3" dependencies: @@ -10418,9 +10628,9 @@ __metadata: linkType: hard "klona@npm:^2.0.4, klona@npm:^2.0.5": - version: 2.0.5 - resolution: "klona@npm:2.0.5" - checksum: 8c976126ea252b766e648a4866e1bccff9d3b08432474ad80c559f6c7265cf7caede2498d463754d8c88c4759895edd8210c85c0d3155e6aae4968362889466f + version: 2.0.6 + resolution: "klona@npm:2.0.6" + checksum: ac9ee3732e42b96feb67faae4d27cf49494e8a3bf3fa7115ce242fe04786788e0aff4741a07a45a2462e2079aa983d73d38519c85d65b70ef11447bbc3c58ce7 languageName: node linkType: hard @@ -10431,7 +10641,7 @@ __metadata: languageName: node linkType: hard -"language-tags@npm:^1.0.5": +"language-tags@npm:=1.0.5": version: 1.0.5 resolution: "language-tags@npm:1.0.5" dependencies: @@ -10737,6 +10947,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: ^3.0.2 + checksum: c154ae1cbb0c2206d1501a0e94df349653c92c8cbb25236d7e85190bcaf4567a03ac6eb43166fabfa36fd35623694da7233e88d9601fbf411a9a481d85dbd2cb + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -10903,11 +11122,11 @@ __metadata: linkType: hard "memfs@npm:^3.1.2, memfs@npm:^3.4.3": - version: 3.4.12 - resolution: "memfs@npm:3.4.12" + version: 3.4.13 + resolution: "memfs@npm:3.4.13" dependencies: fs-monkey: ^1.0.3 - checksum: dab8dec1ae0b2a92e4d563ac86846047cd7aeb17cde4ad51da85cff6e580c32d12b886354527788e36eb75f733dd8edbaf174476b7cea73fed9c5a0e45a6b428 + checksum: 3f9717d6f060919d53f211acb6096a0ea2f566a8cbcc4ef7e1f2561e31e33dc456053fdf951c90a49c8ec55402de7f01b006b81683ab7bd4bdbbd8c9b9cdae5f languageName: node linkType: hard @@ -10975,6 +11194,22 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:~1.33.0": + version: 1.33.0 + resolution: "mime-db@npm:1.33.0" + checksum: 281a0772187c9b8f6096976cb193ac639c6007ac85acdbb8dc1617ed7b0f4777fa001d1b4f1b634532815e60717c84b2f280201d55677fb850c9d45015b50084 + languageName: node + linkType: hard + +"mime-types@npm:2.1.18": + version: 2.1.18 + resolution: "mime-types@npm:2.1.18" + dependencies: + mime-db: ~1.33.0 + checksum: 729265eff1e5a0e87cb7f869da742a610679585167d2f2ec997a7387fc6aedf8e5cad078e99b0164a927bdf3ace34fca27430d6487456ad090cba5594441ba43 + languageName: node + linkType: hard + "mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" @@ -11021,13 +11256,13 @@ __metadata: linkType: hard "mini-css-extract-plugin@npm:^2.4.5": - version: 2.7.0 - resolution: "mini-css-extract-plugin@npm:2.7.0" + version: 2.7.2 + resolution: "mini-css-extract-plugin@npm:2.7.2" dependencies: schema-utils: ^4.0.0 peerDependencies: webpack: ^5.0.0 - checksum: e6b111d4289132bd496286eec42801c74c83af7eac71af630a5babae48ca65377f47d8b39e1f0653b449e68e67b57dbd0d36917242f08fc3e7770e70c0cb56cd + checksum: cd65611d6dc452f230c6ebba8a47bc5f5146b813b13b0b402c6f4a69f6451242eeea781152bebd31cad8ca7c7e95dac91e7e464087f18fb65b2d1097b58cf4ae languageName: node linkType: hard @@ -11038,7 +11273,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -11048,11 +11283,11 @@ __metadata: linkType: hard "minimatch@npm:^5.0.1": - version: 5.1.0 - resolution: "minimatch@npm:5.1.0" + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" dependencies: brace-expansion: ^2.0.1 - checksum: 15ce53d31a06361e8b7a629501b5c75491bc2b59712d53e802b1987121d91b433d73fcc5be92974fde66b2b51d8fb28d75a9ae900d249feb792bb1ba2a4f0a90 + checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 languageName: node linkType: hard @@ -11126,11 +11361,18 @@ __metadata: linkType: hard "minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": - version: 3.3.5 - resolution: "minipass@npm:3.3.5" + version: 3.3.6 + resolution: "minipass@npm:3.3.6" dependencies: yallist: ^4.0.0 - checksum: f89f02bcaa0e0e4bb4c44ec796008e69fbca62db0aba6ead1bc57d25bdaefdf42102130f4f9ecb7d9c6b6cd35ff7b0c7b97d001d3435da8e629fb68af3aea57e + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass@npm:4.0.1" + checksum: 48eb3141cc247b44f738944cbd789aedeed9288ebdb64c7de9b3bf23e9e71d611381bfecf643d877d25f7ca9f3d5ab7b6757ef6f46282086812ac5372b7cd291 languageName: node linkType: hard @@ -11338,8 +11580,8 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 9.3.0 - resolution: "node-gyp@npm:9.3.0" + version: 9.3.1 + resolution: "node-gyp@npm:9.3.1" dependencies: env-paths: ^2.2.0 glob: ^7.1.4 @@ -11353,7 +11595,7 @@ __metadata: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: 589ddd3ed967724ef425f9624bfa47cf73022640ab3eba6d556e92cdc4ddef33b63fce3a467c93b995a3f61df92eafd3c3d1e8dbe4a2c00c383334487dea99c3 + checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7 languageName: node linkType: hard @@ -11364,10 +11606,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.6": - version: 2.0.6 - resolution: "node-releases@npm:2.0.6" - checksum: e86a926dc9fbb3b41b4c4a89d998afdf140e20a4e8dbe6c0a807f7b2948b42ea97d7fd3ad4868041487b6e9ee98409829c6e4d84a734a4215dff060a7fbeb4bf +"node-releases@npm:^2.0.8": + version: 2.0.9 + resolution: "node-releases@npm:2.0.9" + checksum: 3ae6b1b300dc72c1a628861093d339a01aa017d3ad9017b0478384be29d6f9c93b9e26c91fce79728cecaadc04d0f16834b7ae1a018730e3e54962ec8c6aa86f languageName: node linkType: hard @@ -11504,9 +11746,9 @@ __metadata: linkType: hard "object-inspect@npm:^1.12.0, object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": - version: 1.12.2 - resolution: "object-inspect@npm:1.12.2" - checksum: a534fc1b8534284ed71f25ce3a496013b7ea030f3d1b77118f6b7b1713829262be9e6243acbcb3ef8c626e2b64186112cb7f6db74e37b2789b9c789ca23048b2 + version: 1.12.3 + resolution: "object-inspect@npm:1.12.3" + checksum: dabfd824d97a5f407e6d5d24810d888859f6be394d8b733a77442b277e0808860555176719c5905e765e3743a7cada6b8b0a3b85e5331c530fd418cc8ae991db languageName: node linkType: hard @@ -11583,7 +11825,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.0, object.values@npm:^1.1.5, object.values@npm:^1.1.6": +"object.values@npm:^1.1.0, object.values@npm:^1.1.6": version: 1.1.6 resolution: "object.values@npm:1.1.6" dependencies: @@ -11860,6 +12102,13 @@ __metadata: languageName: node linkType: hard +"path-is-inside@npm:1.0.2": + version: 1.0.2 + resolution: "path-is-inside@npm:1.0.2" + checksum: 0b5b6c92d3018b82afb1f74fe6de6338c4c654de4a96123cb343f2b747d5606590ac0c890f956ed38220a4ab59baddfd7b713d78a62d240b20b14ab801fa02cb + languageName: node + linkType: hard + "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -11881,6 +12130,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:2.2.1": + version: 2.2.1 + resolution: "path-to-regexp@npm:2.2.1" + checksum: b921a74e7576e25b06ad1635abf7e8125a29220d2efc2b71d74b9591f24a27e6f09078fa9a1b27516a097ea0637b7cab79d19b83d7f36a8ef3ef5422770e89d9 + languageName: node + linkType: hard + "path-to-regexp@npm:^1.7.0": version: 1.8.0 resolution: "path-to-regexp@npm:1.8.0" @@ -11985,16 +12241,25 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.30.0": + version: 1.30.0 + resolution: "playwright-core@npm:1.30.0" + bin: + playwright: cli.js + checksum: 4c5693f27245a1168f94708ecd8e1eb0d200de435b25cc07cfa25b97a094633818954dc00baf24e0ff551825f672050b83d1309362c1f97213fe8ebd2a147ed9 + languageName: node + linkType: hard + "plotly.js-cartesian-dist-min@npm:^2.12.1": - version: 2.16.3 - resolution: "plotly.js-cartesian-dist-min@npm:2.16.3" - checksum: 49842d56b896466c1574d76073bef96ac8cab4e79b94a1252c8891f2fff7074c17394e9d89d005877ae3e5e76a4396b3ce068b58bb961e39d923ef43cd2223f9 + version: 2.18.1 + resolution: "plotly.js-cartesian-dist-min@npm:2.18.1" + checksum: cc3a8658b42683d6f4c8eaa384e51e8feb53f042fa5dd7315dff2b6c8c236779c7cc94f0f2a06890af8fbeb7ca35cf111d93f9275f63f3e566644f279983bc23 languageName: node linkType: hard "plotly.js@npm:^2.12.1": - version: 2.16.3 - resolution: "plotly.js@npm:2.16.3" + version: 2.18.1 + resolution: "plotly.js@npm:2.18.1" dependencies: "@plotly/d3": 3.8.0 "@plotly/d3-sankey": 0.7.2 @@ -12013,7 +12278,7 @@ __metadata: d3-geo: ^1.12.1 d3-geo-projection: ^2.9.0 d3-hierarchy: ^1.1.9 - d3-interpolate: ^1.4.0 + d3-interpolate: ^3.0.1 d3-time: ^1.1.0 d3-time-format: ^2.2.3 fast-isnumeric: ^1.1.4 @@ -12022,7 +12287,7 @@ __metadata: glslify: ^7.1.1 has-hover: ^1.0.1 has-passive-events: ^1.0.0 - is-mobile: ^2.2.2 + is-mobile: ^3.1.1 mapbox-gl: 1.10.1 mouse-change: ^1.4.0 mouse-event-offset: ^3.0.2 @@ -12045,7 +12310,7 @@ __metadata: topojson-client: ^3.1.0 webgl-context: ^2.2.0 world-calendars: ^1.0.3 - checksum: f718a552cf0e1e2af8f3d70cae3bb5b01e8a8d2d98cb5681cccd2e572ef7f04eb8c81d6b5136a8e731f3d078baa18bb96ac69e1305fbaeea97a86cb485204d62 + checksum: d050ac1b49486cb6d57b3fb270840e189e76f60ddd54d8b0df99f30aa42b77ae18d40cf97fd38546de444891d2823ce8f2a4ecd0e83e9939bbf4aeb463d83f1b languageName: node linkType: hard @@ -12178,13 +12443,13 @@ __metadata: linkType: hard "postcss-custom-properties@npm:^12.1.10": - version: 12.1.10 - resolution: "postcss-custom-properties@npm:12.1.10" + version: 12.1.11 + resolution: "postcss-custom-properties@npm:12.1.11" dependencies: postcss-value-parser: ^4.2.0 peerDependencies: postcss: ^8.2 - checksum: 8460d52a493ae7806f30d821f13ae56024e30169f6cb625197752fdde5bce39fb54870733a63b250996b9063214c9ef4dfa01e56de34123a0614069ee71ebcba + checksum: 421f9d8d6b9c9066919f39251859232efc4dc5dd406c01e62e08734319a6ccda6d03dd6b46063ba0971053ac6ad3f7abade56d67650b3e370851b2291e8e45e6 languageName: node linkType: hard @@ -12679,9 +12944,11 @@ __metadata: linkType: hard "postcss-opacity-percentage@npm:^1.1.2": - version: 1.1.2 - resolution: "postcss-opacity-percentage@npm:1.1.2" - checksum: b582f6d4efb6a14aa09ba49869774c2f060558a68af8a0c3aa9efc0e01b35a4985e783640806a76d4e26d2ba97556f9b5e88dde91d1664a2e2c24688e4bbcf61 + version: 1.1.3 + resolution: "postcss-opacity-percentage@npm:1.1.3" + peerDependencies: + postcss: ^8.2 + checksum: 54d1b8ca68035bc1a5788aaabdbc3b66ffee34b5a2412cecf073627dad7e3f2bae07c01fac3bc7f46bbac5da3291ac9ddcf74bfee26dfd86f9f96c847a0afc13 languageName: node linkType: hard @@ -12891,14 +13158,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.3.5, postcss@npm:^8.4.18, postcss@npm:^8.4.4": - version: 8.4.19 - resolution: "postcss@npm:8.4.19" +"postcss@npm:^8.3.5, postcss@npm:^8.4.18, postcss@npm:^8.4.19, postcss@npm:^8.4.4": + version: 8.4.21 + resolution: "postcss@npm:8.4.21" dependencies: nanoid: ^3.3.4 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: 62782723a385f92b7525f66d29614624de7c5643855423db3a5efd9287e677650300192749adddbbb6734cea9b1d5f5fd4f6ea00ca3f9a95dbbb88f835f5ca64 + checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 languageName: node linkType: hard @@ -12972,14 +13239,14 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.3.1": - version: 29.3.1 - resolution: "pretty-format@npm:29.3.1" +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.4.1": + version: 29.4.1 + resolution: "pretty-format@npm:29.4.1" dependencies: - "@jest/schemas": ^29.0.0 + "@jest/schemas": ^29.4.0 ansi-styles: ^5.0.0 react-is: ^18.0.0 - checksum: 9917a0bb859cd7a24a343363f70d5222402c86d10eb45bcc2f77b23a4e67586257390e959061aec22762a782fe6bafb59bf34eb94527bc2e5d211afdb287eb4e + checksum: bcc8e86bcf8e7f5106c96e2ea7905912bd17ae2aac76e4e0745d2a50df4b340638ed95090ee455a1c0f78189efa05077bd655ca08bf66292e83ebd7035fc46fd languageName: node linkType: hard @@ -13049,7 +13316,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.8, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -13084,10 +13351,17 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.3.2": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: fa6e698cb53db45e4628559e557ddaf554103d2a96a1d62892c8f4032cd3bc8871796cae9eabc1bc700e2b6677611521ce5bb1d9a27700086039965d0cf34518 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1": - version: 2.1.1 - resolution: "punycode@npm:2.1.1" - checksum: 823bf443c6dd14f669984dea25757b37993f67e8d94698996064035edd43bed8a5a17a9f12e439c2b35df1078c6bec05a6c86e336209eb1061e8025c481168e8 + version: 2.3.0 + resolution: "punycode@npm:2.3.0" + checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 languageName: node linkType: hard @@ -13181,6 +13455,13 @@ __metadata: languageName: node linkType: hard +"range-parser@npm:1.2.0": + version: 1.2.0 + resolution: "range-parser@npm:1.2.0" + checksum: bdf397f43fedc15c559d3be69c01dedf38444ca7a1610f5bf5955e3f3da6057a892f34691e7ebdd8c7e1698ce18ef6c4d4811f70e658dda3ff230ef741f8423a + languageName: node + linkType: hard + "range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" @@ -13200,6 +13481,20 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.0.1, rc@npm:^1.1.6": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + "react-app-polyfill@npm:^3.0.0": version: 3.0.0 resolution: "react-app-polyfill@npm:3.0.0" @@ -13327,6 +13622,17 @@ __metadata: languageName: node linkType: hard +"react-infinite-scroller@npm:^1.2.6": + version: 1.2.6 + resolution: "react-infinite-scroller@npm:1.2.6" + dependencies: + prop-types: ^15.5.8 + peerDependencies: + react: ^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 5f5aae76c484b4039bada87070d5f8c54d2786697ae600be19a68f4e85158ff550122f15dccc5997d8b01fa487da47b9ffbc3e9d9a29f2cf71c77275f72551fd + languageName: node + linkType: hard + "react-is@npm:^16.13.1, react-is@npm:^16.3.2, react-is@npm:^16.6.0, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -13635,11 +13941,11 @@ __metadata: linkType: hard "redux@npm:^4.2.0": - version: 4.2.0 - resolution: "redux@npm:4.2.0" + version: 4.2.1 + resolution: "redux@npm:4.2.1" dependencies: "@babel/runtime": ^7.9.2 - checksum: 75f3955c89b3f18edf5411e5fb482aa2e4f41a416183e8802a6bf6472c4fc3d47675b8b321d147f8af8e0f616436ac507bf5a25f1c4d6180e797b549c7db2c1d + checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd languageName: node linkType: hard @@ -13659,14 +13965,14 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.10, regenerator-runtime@npm:^0.13.9": +"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.9": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 languageName: node linkType: hard -"regenerator-transform@npm:^0.15.0": +"regenerator-transform@npm:^0.15.1": version: 0.15.1 resolution: "regenerator-transform@npm:0.15.1" dependencies: @@ -13707,7 +14013,7 @@ __metadata: languageName: node linkType: hard -"regexpu-core@npm:^5.1.0": +"regexpu-core@npm:^5.2.1": version: 5.2.2 resolution: "regexpu-core@npm:5.2.2" dependencies: @@ -13721,6 +14027,25 @@ __metadata: languageName: node linkType: hard +"registry-auth-token@npm:3.3.2": + version: 3.3.2 + resolution: "registry-auth-token@npm:3.3.2" + dependencies: + rc: ^1.1.6 + safe-buffer: ^5.0.1 + checksum: c9d7ae160a738f1fa825556e3669e6c771d2c0239ce37679f7e8646157a97d0a76464738be075002a1f754ef9bfb913b689f4bbfd5296d28f136fbf98c8c2217 + languageName: node + linkType: hard + +"registry-url@npm:3.1.0": + version: 3.1.0 + resolution: "registry-url@npm:3.1.0" + dependencies: + rc: ^1.0.1 + checksum: 6d223da41b04e1824f5faa63905c6f2e43b216589d72794111573f017352b790aef42cd1f826463062f89d804abb2027e3d9665d2a9a0426a11eedd04d470af3 + languageName: node + linkType: hard + "regjsgen@npm:^0.7.1": version: 0.7.1 resolution: "regjsgen@npm:0.7.1" @@ -13946,9 +14271,9 @@ __metadata: linkType: hard "resolve.exports@npm:^1.1.0": - version: 1.1.0 - resolution: "resolve.exports@npm:1.1.0" - checksum: 52865af8edb088f6c7759a328584a5de6b226754f004b742523adcfe398cfbc4559515104bc2ae87b8e78b1e4de46c9baec400b3fb1f7d517b86d2d48a098a2d + version: 1.1.1 + resolution: "resolve.exports@npm:1.1.1" + checksum: 485aa10082eb388a569d696e17ad7b16f4186efc97dd34eadd029d95b811f21ffee13b1b733198bb4584dbb3cb296aa6f141835221fb7613b9606b84f1386655 languageName: node linkType: hard @@ -13959,7 +14284,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.0.0, resolve@npm:^1.1.10, resolve@npm:^1.1.5, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1": +"resolve@npm:^1.0.0, resolve@npm:^1.1.10, resolve@npm:^1.1.5, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1": version: 1.22.1 resolution: "resolve@npm:1.22.1" dependencies: @@ -13972,7 +14297,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^2.0.0-next.3": +"resolve@npm:^2.0.0-next.4": version: 2.0.0-next.4 resolution: "resolve@npm:2.0.0-next.4" dependencies: @@ -13992,7 +14317,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.0.0#~builtin, resolve@patch:resolve@^1.1.10#~builtin, resolve@patch:resolve@^1.1.5#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": +"resolve@patch:resolve@^1.0.0#~builtin, resolve@patch:resolve@^1.1.10#~builtin, resolve@patch:resolve@^1.1.5#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": version: 1.22.1 resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=07638b" dependencies: @@ -14005,7 +14330,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^2.0.0-next.3#~builtin": +"resolve@patch:resolve@^2.0.0-next.4#~builtin": version: 2.0.0-next.4 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin::version=2.0.0-next.4&hash=07638b" dependencies: @@ -14119,11 +14444,11 @@ __metadata: linkType: hard "rxjs@npm:^7.5.5": - version: 7.5.7 - resolution: "rxjs@npm:7.5.7" + version: 7.8.0 + resolution: "rxjs@npm:7.8.0" dependencies: tslib: ^2.1.0 - checksum: edabcdb73b0f7e0f5f6e05c2077aff8c52222ac939069729704357d6406438acca831c24210db320aba269e86dbe1a400f3769c89101791885121a342fb15d9c + checksum: 61b4d4fd323c1043d8d6ceb91f24183b28bcf5def4f01ca111511d5c6b66755bc5578587fe714ef5d67cf4c9f2e26f4490d4e1d8cabf9bd5967687835e9866a2 languageName: node linkType: hard @@ -14134,7 +14459,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -14299,14 +14624,14 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" +"semver@npm:7.3.8, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": + version: 7.3.8 + resolution: "semver@npm:7.3.8" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 languageName: node linkType: hard @@ -14319,17 +14644,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": - version: 7.3.8 - resolution: "semver@npm:7.3.8" - dependencies: - lru-cache: ^6.0.0 - bin: - semver: bin/semver.js - checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 - languageName: node - linkType: hard - "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -14361,11 +14675,27 @@ __metadata: linkType: hard "serialize-javascript@npm:^6.0.0": - version: 6.0.0 - resolution: "serialize-javascript@npm:6.0.0" + version: 6.0.1 + resolution: "serialize-javascript@npm:6.0.1" dependencies: randombytes: ^2.1.0 - checksum: 56f90b562a1bdc92e55afb3e657c6397c01a902c588c0fe3d4c490efdcc97dcd2a3074ba12df9e94630f33a5ce5b76a74784a7041294628a6f4306e0ec84bf93 + checksum: 3c4f4cb61d0893b988415bdb67243637333f3f574e9e9cc9a006a2ced0b390b0b3b44aef8d51c951272a9002ec50885eefdc0298891bc27eb2fe7510ea87dc4f + languageName: node + linkType: hard + +"serve-handler@npm:6.1.5": + version: 6.1.5 + resolution: "serve-handler@npm:6.1.5" + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + fast-url-parser: 1.1.3 + mime-types: 2.1.18 + minimatch: 3.1.2 + path-is-inside: 1.0.2 + path-to-regexp: 2.2.1 + range-parser: 1.2.0 + checksum: 7a98ca9cbf8692583b6cde4deb3941cff900fa38bf16adbfccccd8430209bab781e21d9a1f61c9c03e226f9f67689893bbce25941368f3ddaf985fc3858b49dc languageName: node linkType: hard @@ -14396,6 +14726,27 @@ __metadata: languageName: node linkType: hard +"serve@npm:^14.2.0": + version: 14.2.0 + resolution: "serve@npm:14.2.0" + dependencies: + "@zeit/schemas": 2.29.0 + ajv: 8.11.0 + arg: 5.0.2 + boxen: 7.0.0 + chalk: 5.0.1 + chalk-template: 0.4.0 + clipboardy: 3.0.0 + compression: 1.7.4 + is-port-reachable: 4.0.0 + serve-handler: 6.1.5 + update-check: 1.5.4 + bin: + serve: build/main.js + checksum: a1c26e6c3dd0c482589b39a105e2f09bebf20ee8a0358accd5d64b313952ad3e1a56b8af2bdfef269f9fd37140b4f6c940d12395c851e4a46dfdace8f97616e4 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -14448,9 +14799,9 @@ __metadata: linkType: hard "shell-quote@npm:^1.7.3": - version: 1.7.4 - resolution: "shell-quote@npm:1.7.4" - checksum: 2874ea9c1a7c3ebfc9ec5734a897e16533d0d06f2e4cddc22ba3d1cab5cdc07d0f825364c1b1e39abe61236f44d8e60e933c7ad7349ce44de4f5dddc7b4354e9 + version: 1.8.0 + resolution: "shell-quote@npm:1.8.0" + checksum: 6ef7c5e308b9c77eedded882653a132214fa98b4a1512bb507588cf6cd2fc78bfee73e945d0c3211af028a1eabe09c6a19b96edd8977dc149810797e93809749 languageName: node linkType: hard @@ -14777,6 +15128,15 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.0.0": + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" + dependencies: + internal-slot: ^1.0.4 + checksum: d04173690b2efa40e24ab70e5e51a3ff31d56d699550cfad084104ab3381390daccb36652b25755e420245f3b0737de66c1879eaa2a8d4fc0a78f9bf892fcb42 + languageName: node + linkType: hard + "stream-parser@npm:~0.3.1": version: 0.3.1 resolution: "stream-parser@npm:0.3.1" @@ -14857,7 +15217,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.0": +"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" dependencies: @@ -14884,7 +15244,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.5": +"string.prototype.trimend@npm:^1.0.6": version: 1.0.6 resolution: "string.prototype.trimend@npm:1.0.6" dependencies: @@ -14895,7 +15255,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimstart@npm:^1.0.5": +"string.prototype.trimstart@npm:^1.0.6": version: 1.0.6 resolution: "string.prototype.trimstart@npm:1.0.6" dependencies: @@ -15004,6 +15364,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "strongly-connected-components@npm:^1.0.1": version: 1.0.1 resolution: "strongly-connected-components@npm:1.0.1" @@ -15076,9 +15443,9 @@ __metadata: linkType: hard "supports-color@npm:^9.2.1": - version: 9.2.3 - resolution: "supports-color@npm:9.2.3" - checksum: 47d598b70d2bac3cbce98950344134de17c2935f45e8ad6256cde78ebe5da440e240d9fa17c1146bd84b1c36f865239fef6c563d70068a8d901ca5a6c56960af + version: 9.3.1 + resolution: "supports-color@npm:9.3.1" + checksum: 00c4d1082a7ba0ee21cba1d4e4a466642635412e40476777b530aa5110d035e99a420cd048e1fb6811f2254c0946095fbb87a1eccf1af1d1ca45ab0a4535db93 languageName: node linkType: hard @@ -15236,16 +15603,16 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.12 - resolution: "tar@npm:6.1.12" + version: 6.1.13 + resolution: "tar@npm:6.1.13" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^3.0.0 + minipass: ^4.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: 49d72e4420944e7ede2782d6b0826a6ede6cdab23c7de63470917e7a78166bc4d5b1a96279d3d79a85f1ba5a17cd37c0acbb3cbff19a07447691445b8b051c55 + checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c languageName: node linkType: hard @@ -15301,8 +15668,8 @@ __metadata: linkType: hard "terser@npm:^5.14.2": - version: 5.16.0 - resolution: "terser@npm:5.16.0" + version: 5.16.3 + resolution: "terser@npm:5.16.3" dependencies: "@jridgewell/source-map": ^0.3.2 acorn: ^8.5.0 @@ -15310,7 +15677,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: d035672bd28bd40ff80d83bea6bc6c85bddf41c18060e49c8b36a3aa45a0a6b4a59c6a56bdf52f9d3350587684d664f8ca26656c6084abeb951b85edf34e47ae + checksum: d3c2ac1c2723c37b698b25b68d76fd315a1277fddde113983d5783d1f2a01dd7b8ed83ba3f54e5e65f0b59dd971ed7be2fdf8d4be94ec694b2d27832d2e7561f languageName: node linkType: hard @@ -15340,9 +15707,9 @@ __metadata: linkType: hard "throat@npm:^6.0.1": - version: 6.0.1 - resolution: "throat@npm:6.0.1" - checksum: 782d4171ee4e3cf947483ed2ff1af3e17cc4354c693b9d339284f61f99fbc401d171e0b0d2db3295bb7d447630333e9319c174ebd7ef315c6fb791db9675369c + version: 6.0.2 + resolution: "throat@npm:6.0.2" + checksum: 463093768d4884772020bb18b0f33d3fec8a2b4173f7da3958dfbe88ff0f1e686ffadf0f87333bf6f6db7306b1450efc7855df69c78bf0bfa61f6d84a3361fe8 languageName: node linkType: hard @@ -15411,9 +15778,9 @@ __metadata: linkType: hard "tinycolor2@npm:^1.4.2": - version: 1.4.2 - resolution: "tinycolor2@npm:1.4.2" - checksum: 57ed262e08815a4ab0ed933edafdbc6555a17081781766149813b44a080ecbe58b3ee281e81c0e75b42e4d41679f138cfa98eabf043f829e0683c04adb12c031 + version: 1.5.2 + resolution: "tinycolor2@npm:1.5.2" + checksum: 9df1ea9a986b03f1aebb1c1ac17fc561e358493f61b56d73ef2d7207fe7bd74eb71cf745b70487b2b5bb1ce33c9e8af7101088bb0b5fc532eaa1f9d1eda4ef31 languageName: node linkType: hard @@ -15625,9 +15992,9 @@ __metadata: linkType: hard "tslib@npm:^2.0.3, tslib@npm:^2.1.0": - version: 2.4.1 - resolution: "tslib@npm:2.4.1" - checksum: 19480d6e0313292bd6505d4efe096a6b31c70e21cf08b5febf4da62e95c265c8f571f7b36fcc3d1a17e068032f59c269fab3459d6cd3ed6949eafecf64315fca + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 languageName: node linkType: hard @@ -15709,6 +16076,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^2.13.0": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: a4ef07ece297c9fba78fc1bd6d85dff4472fe043ede98bd4710d2615d15776902b595abf62bd78339ed6278f021235fb28a96361f8be86ed754f778973a0d278 + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -15740,6 +16114,17 @@ __metadata: languageName: node linkType: hard +"typed-array-length@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-length@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + is-typed-array: ^1.1.9 + checksum: 2228febc93c7feff142b8c96a58d4a0d7623ecde6c7a24b2b98eb3170e99f7c7eff8c114f9b283085cd59dcd2bd43aadf20e25bba4b034a53c5bb292f71f8956 + languageName: node + linkType: hard + "typedarray-pool@npm:^1.1.0": version: 1.2.0 resolution: "typedarray-pool@npm:1.2.0" @@ -15767,22 +16152,22 @@ __metadata: linkType: hard "typescript@npm:^4.6.4": - version: 4.9.3 - resolution: "typescript@npm:4.9.3" + version: 4.9.5 + resolution: "typescript@npm:4.9.5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 17b8f816050b412403e38d48eef0e893deb6be522d6dc7caf105e54a72e34daf6835c447735fd2b28b66784e72bfbf87f627abb4818a8e43d1fa8106396128dc + checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db languageName: node linkType: hard "typescript@patch:typescript@^4.6.4#~builtin": - version: 4.9.3 - resolution: "typescript@patch:typescript@npm%3A4.9.3#~builtin::version=4.9.3&hash=493e53" + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=493e53" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: ef65c22622d864497d0a0c5db693523329b3284c15fe632e93ad9aa059e8dc38ef3bd767d6f26b1e5ecf9446f49bd0f6c4e5714a2eeaf352805dc002479843d1 + checksum: 2eee5c37cad4390385db5db5a8e81470e42e8f1401b0358d7390095d6f681b410f2c4a0c496c6ff9ebd775423c7785cdace7bcdad76c7bee283df3d9718c0f20 languageName: node linkType: hard @@ -15905,7 +16290,7 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.9": +"update-browserslist-db@npm:^1.0.10": version: 1.0.10 resolution: "update-browserslist-db@npm:1.0.10" dependencies: @@ -15919,6 +16304,16 @@ __metadata: languageName: node linkType: hard +"update-check@npm:1.5.4": + version: 1.5.4 + resolution: "update-check@npm:1.5.4" + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + checksum: 2c9f7de6f030364c5ea02a341e5ae2dfe76da6559b32d40dd3b047b3ac0927408cf92d322c51cd8e009688210a85ccbf1eba449762a65a0d1b14f3cdf1ea5c48 + languageName: node + linkType: hard + "update-diff@npm:^1.1.0": version: 1.1.0 resolution: "update-diff@npm:1.1.0" @@ -16525,7 +16920,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.8": +"which-typed-array@npm:^1.1.9": version: 1.1.9 resolution: "which-typed-array@npm:1.1.9" dependencies: @@ -16577,6 +16972,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^4.0.1": + version: 4.0.1 + resolution: "widest-line@npm:4.0.1" + dependencies: + string-width: ^5.0.1 + checksum: 64c48cf27171221be5f86fc54b94dd29879165bdff1a7aa92dde723d9a8c99fb108312768a5d62c8c2b80b701fa27bbd36a1ddc58367585cd45c0db7920a0cba + languageName: node + linkType: hard + "window-or-global@npm:^1.0.1": version: 1.0.1 resolution: "window-or-global@npm:1.0.1" @@ -16827,6 +17231,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^8.0.1": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -16862,17 +17277,17 @@ __metadata: linkType: hard "ws@npm:^8.4.2": - version: 8.11.0 - resolution: "ws@npm:8.11.0" + version: 8.12.0 + resolution: "ws@npm:8.12.0" peerDependencies: bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 + utf-8-validate: ">=5.0.2" peerDependenciesMeta: bufferutil: optional: true utf-8-validate: optional: true - checksum: 316b33aba32f317cd217df66dbfc5b281a2f09ff36815de222bc859e3424d83766d9eb2bd4d667de658b6ab7be151f258318fb1da812416b30be13103e5b5c67 + checksum: 818ff3f8749c172a95a114cceb8b89cedd27e43a82d65c7ad0f7882b1e96a2ee6709e3746a903c3fa88beec0c8bae9a9fcd75f20858b32a166dfb7519316a5d7 languageName: node linkType: hard @@ -16911,6 +17326,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 48f7bb00dc19fc635a13a39fe547f527b10c9290e7b3e836b9a8f1ca04d4d342e85714416b3c2ab74949c9c66f9cebb0473e6bc353b79035356103b47641285d + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" diff --git a/docs/src/plugins/install.rst b/docs/src/plugins/install.rst index 772c9b3b2..49ab94f17 100644 --- a/docs/src/plugins/install.rst +++ b/docs/src/plugins/install.rst @@ -36,7 +36,7 @@ Next we define the file ``bayes.yaml`` as this experiment: name: orion-with-bayes - algorithms: BayesianOptimizer + algorithm: BayesianOptimizer Then call ``orion hunt`` with the configuration file. diff --git a/docs/src/tutorials/huggingface.rst b/docs/src/tutorials/huggingface.rst new file mode 100644 index 000000000..55bb25fff --- /dev/null +++ b/docs/src/tutorials/huggingface.rst @@ -0,0 +1,210 @@ +***************************************************************************************** +Hyperparameters optimisation using a HuggingFace Model and the Hydra-Orion-Sweeper plugin +***************************************************************************************** + +In this tutorial, we will show an easy Orion integration of a HuggingFace translation model using +Hydra, with the `Hydra_Orion_Sweeper `_ plugin. +Hydra is essentially a framework for configuring applications. We will use it to define our +Hyperparameters and some Orion configuration. We will also be using +`Comet `_ for monitoring our experiments. +Installation +^^^^^^^^^^^^ +For this tutorial everything that we need to install can be can be found in the ``requirements.txt`` +file located in the ``examples/huggingface`` repository. You can then install the requirements +with ``pip``. + +.. code-block:: bash + + $ pip install -r examples/huggingface/requirements.txt + +Imports +^^^^^^^ +You will now need to import these modules. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 5-6,13-22 + + +Hydra configuration file +^^^^^^^^^^^^^^^^^^^^^^^^ + +Notice here how the arguments that are not defined will be set at ``None``, which will be +overridden by default values or not used at all. This serves as a replacement for parsing arguments +in the command line, but is integrated with Orion, which makes it more practical +to manage search spaces of hyperparameters. + +.. literalinclude:: /../../examples/huggingface/config.yaml + :language: yaml + :lines: 1- + +If you want to change your working or/and logging directory, you can also easily do that. +From the config file, you can specify + +.. literalinclude:: /../../examples/huggingface/config.yaml + :language: yaml + :lines: 29-31 + +This will change your working directory. You can see that with the hydra-orion-sweeper, we are able +to specify 4 variables from Orion. ``${hydra.sweeper.orion.name}`` for the ``experiment_name``, +``${hydra.sweeper.orion.id}`` for the ``experiment_id``, ``${hydra.sweeper.orion.uuid}`` for the +experiment ``uuid`` and ``${hydra.sweeper.orion.trial}`` for the ``trial_id``. + +In the code, you can now specify the output directory to the trainer with the +``output_dir`` parameter. ``os.getcwd()`` specifies the current working dir. + +Including these options will create different folders for each trial, under different ones for +each experiment and even different ones for each sweep. You do not have to add them all, but it +can be quite useful when you don't want 2 trials writing their cache in the same file, which +could result in an error. + +.. code-block:: python + + output_dir=str(os.getcwd())+"/test_trainer", + +You can find more about the Hydra-Orion-Sweeper plugin by looking directly at the +Github Repository : `Hydra_Orion_Sweeper `_ , +or find out more about Hydra in general here : `Hydra `_ + + +Adapting the code for Hydra +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The ``config_path`` and ``config_name`` here specifies the path of your Hydra file. +The ``cfg.args`` are reference to the ``args`` in the config file. +We also are going to be using this main function as the entry point of the program. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 141-146, 310-311 + +With the ``hydra-orion-sweeper``, this function needs to return the objective. You have 3 choices +for what you can return : + +.. code-block:: python + + if cfg.return_type == "float": + return out + + if cfg.return_type == "dict": + return dict(name="objective", type="objective", value=out) + + if cfg.return_type == "list": + return [dict(name="objective", type="objective", value=out)] + +For the purpose of this tutorial, we are going to keep it simple and simply return a float, +our objective that we want to minimize. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 304 + +Comet +^^^^^ +We are going to use Comet to track our experiments. It is quite simple to use. First, +install comet using + +.. code-block:: bash + + $ pip install comet-ml + +Now that it is installed, we simply have to set some environment variables, such as + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 8-13 + +You can also set them in your working environment. If you are to set them in python, however, +you need to make sure to set them before importing ``transformers``. + +For the ``COMET_API_KEY``, you will be given a token when creating your comet account. +This is the one you are going to use here. + +And that is it ! If the variables are set and comet-ml is downloaded, HuggingFace will +automatically upload your data to Comet, you simply have to go to your profile on their site +and see your experiments. + +It is important to note here that we can swap the Comet logger to many others, such as WandB, +MLFlow, Neptune and ClearML. You can see the complete list in the HuggingFace documentation +`HF callbacks `_ + +Example code +^^^^^^^^^^^^ +For this example, we are fine-tuning a pretrained translation model named ``Helsinki-NLP``. +We start by setting the training arguments. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 165-174 + +For our purposes, we will be using a ``Seq2SeqTrainer``, so for the training arguments are going +to be ``Seq2SeqTrainingArguments``. The ``set_training_args`` function adds the hydra arguments +into the training arguments. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 114-118 + +For the dataset, we are going to use the ``wmt16`` dataset. We can set a ``cache_dir`` to where +the dataset cache will be stored + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 179,182-184 + +We then prepare our training and evaluation datasets. In this example, we want to evaluate our +model with the validation dataset and the training dataset. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 196-229 + +For the metric, we are going to use ``sacrebleu``. We can also set a ``cache_dir`` here for the +metric cache files. The ``compute_metrics`` function goes as follows : + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 238-240, 247-268 + +Now we have to create the actual Trainer, a ``Seq2SeqTrainer`` as mentioned previously. +It is very much like a classic ``Trainer`` from HuggingFace. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 284-292 + +HuggingFace will log the evaluation from the ``eval_dataset`` to Comet. Since we also want the +evaluation from the training dataset, we will have to implement something called a +``CustomCallback``. The one I made for this tutorial takes the ``trainer`` and the dataset we want +to add (in our case, our train dataset) as parameters. +We can then rewrite some callback functions, such as ``on_epoch_end()``. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 270-282,294 + +All that is left to do now is to train the model, and once it's finish training, send the data to +Orion by returning it. + +.. literalinclude:: /../../examples/huggingface/main.py + :language: python + :lines: 295-297, 304 + +For more details, feel free to simply go look at the code, in ``examples/huggingface/main.py`` + +Execution +^^^^^^^^^ +We simply have to run the main.py file with the -m argument, which makes sure we use the +Hydra-Orion-Sweeper plugin. + +.. code-block:: bash + + $ python3 main.py -m + +Visualizing results +^^^^^^^^^^^^^^^^^^^ +With Orion, after your experiment has finished running, you can easily visualize your results +using `regret plots `_ +and `partial dependencies plots +`_ +These are very helpful to see what is happening during the optimization, and what can be adjusted +if necessary. diff --git a/docs/src/user/algorithms.rst b/docs/src/user/algorithms.rst index c895c8a80..6fd196c7d 100644 --- a/docs/src/user/algorithms.rst +++ b/docs/src/user/algorithms.rst @@ -1,4 +1,4 @@ -.. _Setup Algorithms: +.. _Setup algorithm: ********** Algorithms @@ -19,7 +19,7 @@ In a Oríon configuration YAML, define: .. code-block:: yaml experiment: - algorithms: + algorithm: gradient_descent: learning_rate: 0.1 @@ -51,7 +51,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: random: seed: null @@ -91,7 +91,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: gridsearch: n_values: 100 @@ -140,7 +140,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: hyperband: seed: null repetitions: 1 @@ -196,7 +196,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: asha: seed: null num_rungs: null @@ -235,7 +235,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: bohb: min_points_in_model: 20 top_n_percent: 15 @@ -285,7 +285,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: dehb: seed: null mutation_factor: 0.5 @@ -330,7 +330,7 @@ Configuration experiment: - algorithms: + algorithm: pbt: population_size: 50 generations: 10 @@ -400,7 +400,7 @@ Configuration experiment: - algorithms: + algorithm: pb2: population_size: 50 generations: 10 @@ -459,7 +459,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: tpe: seed: null n_initial_points: 20 @@ -503,7 +503,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: ax: seed: 1234 n_initial_trials: 5, @@ -573,7 +573,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: EvolutionES: seed: null repetitions: 1 @@ -626,7 +626,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: MOFA: seed: null index: 1 @@ -654,7 +654,7 @@ a library of algorithms for hyperparameter search. .. code-block:: yaml experiment: - algorithms: + algorithm: nevergrad: seed: null budget: 1000 @@ -692,7 +692,7 @@ Configuration .. code-block:: yaml experiment: - algorithms: + algorithm: hebo: seed: 1234 parameters: diff --git a/docs/src/user/config.rst b/docs/src/user/config.rst index 58d0a53a9..077e52d86 100644 --- a/docs/src/user/config.rst +++ b/docs/src/user/config.rst @@ -93,7 +93,7 @@ Full Example of Global Configuration type: pickleddb experiment: - algorithms: + algorithm: random: seed: None max_broken: 3 @@ -205,7 +205,7 @@ Experiment .. code-block:: yaml experiment: - algorithms: + algorithm: random: seed: None max_broken: 3 @@ -320,7 +320,7 @@ working_dir .. _config_experiment_algorithms: -algorithms +algorithm ~~~~~~~~~~ :Type: dict @@ -346,7 +346,7 @@ strategy :Env var: :Description: (DEPRECATED) This argument will be removed in v0.4. Parallel strategies are now handled by - algorithms directly and should be set in algorithm configuration when they support it. + algorithm directly and should be set in algorithm configuration when they support it. ---- diff --git a/examples/dask_example.py b/examples/dask_example.py index 7f8a10386..2a20542f2 100644 --- a/examples/dask_example.py +++ b/examples/dask_example.py @@ -56,7 +56,7 @@ def hpo(n_workers=16): "tol": "loguniform(1e-4, 1e-1, precision=None)", "class_weight": "choices([None, 'balanced'])", }, - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, ) with experiment.tmp_executor("dask", n_workers=n_workers): diff --git a/examples/huggingface/config.yaml b/examples/huggingface/config.yaml new file mode 100644 index 000000000..9f1a6bcd6 --- /dev/null +++ b/examples/huggingface/config.yaml @@ -0,0 +1,49 @@ +defaults: +- override hydra/sweeper: orion + +hydra: + sweeper: + params: + lr: "loguniform(1e-8, 1.0)" + wd: "loguniform(1e-10, 1)" + orion: + name: 'translationexp' + version: '1' + + algorithm: + type: random + config: + seed: 1 + + worker: + n_workers: 1 + max_broken: 3 + max_trials: 1 + + storage: + type: legacy + database: + type: pickleddb + host: 'orion_db.pkl' + + sweep: + dir: hydra_log/multirun/translation/${now:%Y-%m-%d}/${now:%H-%M-%S} + subdir: ${hydra.sweeper.orion.name}/${hydra.sweeper.orion.uuid}/${hydra.job.id} + +#Default value +lr: 0.01 +wd: 0.00 + +args: + size_train_dataset: 5000 + size_eval_dataset: + freeze_base_model: + per_device_train_batch_size: + optim: + weight_decay: ${wd} + adam_beta1: + adam_beta2: + adam_epsilon: + logfile: + learning_rate: ${lr} + num_train_epochs: 20 diff --git a/examples/huggingface/main.py b/examples/huggingface/main.py new file mode 100644 index 000000000..dbdee71ea --- /dev/null +++ b/examples/huggingface/main.py @@ -0,0 +1,311 @@ +# [markdown] +# # Fine-tune a pretrained model from Hugging Face +# +# source tutorial: https://huggingface.co/docs/transformers/training +import logging +import os + +os.environ["COMET_API_KEY"] = "comet_token" +os.environ["COMET_WORKSPACE"] = "workspace" +os.environ["COMET_PROJECT_NAME"] = "project" +os.environ["COMET_MODE"] = "ONLINE" +os.environ["COMET_LOG_ASSETS"] = "True" +os.environ["COMET_AUTO_LOG_METRICS"] = "True" +import argparse +from copy import deepcopy + +import hydra +import numpy as np +import torch +from datasets import load_dataset, load_metric +from omegaconf import DictConfig +from transformers import ( + AutoModelForSeq2SeqLM, + AutoTokenizer, + DataCollatorForSeq2Seq, + Seq2SeqTrainer, + Seq2SeqTrainingArguments, + TrainerCallback, +) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-st", + "--size-train-dataset", + help="Number of samples to use from training data set. If not specified, use complete dataset", + type=int, + required=False, + ) + parser.add_argument( + "-se", + "--size-eval-dataset", + help="Number of samples to use from evaluation data set. If not specified, use complete dataset", + type=int, + required=False, + ) + parser.add_argument( + "-f", + "--freeze-base-model", + help="Freeze parameters of base model during training", + action="store_true", + required=False, + ) + parser.add_argument( + "-lr", "--learning-rate", help="Learning rate", type=float, required=False + ) + parser.add_argument( + "-e", + "--num_train_epochs", + help="Number of training epochs", + type=int, + required=False, + ) + parser.add_argument( + "-b", + "--per_device_train_batch_size", + help="Per device batch size", + type=int, + required=False, + ) + parser.add_argument( + "-opt", + "--optim", + help="Optimizer (one of: adamw_hf, adamw_torch, adamw_apex_fused, or adafactor", + type=str, + required=False, + ) + parser.add_argument( + "-wd", + "--weight_decay", + help="Weight decay for AdamW optimizer", + type=float, + required=False, + ) + parser.add_argument( + "-b1", + "--adam_beta1", + help="beta1 hyperparameter for AdamW optimizer", + type=float, + required=False, + ) + parser.add_argument( + "-b2", + "--adam_beta2", + help="beta2 hyperparameter for AdamW optimizer", + type=float, + required=False, + ) + parser.add_argument( + "-eps", + "--adam_epsilon", + help="epsilon hyperparameter for AdamW optimizer", + type=float, + required=False, + ) + parser.add_argument( + "-log", "--logfile", help="Log file name and path", type=str, required=False + ) + args = parser.parse_args() + return vars(args) + + +def set_training_args(training_args, args): + for argname, argvalue in args.items(): + if argvalue is not None: + setattr(training_args, argname, argvalue) + return training_args + + +class GPUMemoryCallback(TrainerCallback): + def on_epoch_end(self, args, state, control, **kwargs): + print( + "GPU mem: Tot - ", + torch.cuda.get_device_properties(0).total_memory, + "res - ", + torch.cuda.memory_reserved(0), + "used - ", + torch.cuda.memory_allocated(0), + ) + + +def get_free_gpu(): + for i in range(torch.cuda.device_count()): + gpu_procs_str = torch.cuda.list_gpu_processes(i) + if "no processes are running" in gpu_procs_str: + return i + return None + + +@hydra.main(config_path=".", config_name="config") +def main(cfg: DictConfig) -> float: + print("args", cfg) + + # Get command line arguments and apply hyperparameters to training arguments + args = cfg.args + + # Logger setup + logfile = args["logfile"] or "translation_hf.log" + + logging.basicConfig(filename=logfile, level=logging.INFO) + logger = logging.getLogger() + + # Get a GPU if available + if torch.cuda.is_available(): + device = f"cuda:{get_free_gpu()}" + else: + device = "cpu" + + # We only use the device to print out what HF should be using by default + logger.info("Compute device: %s", device) + + batch_size = 16 + + # Get hyperparameters + training_args = Seq2SeqTrainingArguments( + output_dir=str(os.getcwd()) + "/test_trainer", + save_total_limit=2, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + evaluation_strategy="epoch", + predict_with_generate=True, + ) + + training_args = set_training_args(training_args, args) + print("Training arguments:", training_args) + + # Load a dataset + dataset_name = "wmt16" + logger.info("Dataset: %s", dataset_name) + + raw_dataset = load_dataset( + dataset_name, "ro-en", cache_dir="hydra_log/multirun/translation/dataset" + ) + + model_checkpoint = "Helsinki-NLP/opus-mt-en-ro" + + # Create tokenizer and tokenize the dataset + tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + max_input_length = 128 + max_target_length = 128 + source_lang = "en" + target_lang = "ro" + + def preprocess_function(examples): + inputs = [ex[source_lang] for ex in examples["translation"]] + targets = [ex[target_lang] for ex in examples["translation"]] + model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) + + # Setup the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs + + tokenized_datasets = raw_dataset.map(preprocess_function, batched=True) + + # Use only a subset of the available data, if desired + size_train_dataset = len(tokenized_datasets["train"]) + size_eval_dataset = len(tokenized_datasets["validation"]) + + if args["size_train_dataset"] is not None: + size_train_dataset = args["size_train_dataset"] + if args["size_eval_dataset"] is not None: + size_eval_dataset = args["size_eval_dataset"] + train_dataset = ( + tokenized_datasets["train"].shuffle(seed=42).select(range(size_train_dataset)) + ) + eval_train_dataset = ( + tokenized_datasets["train"].shuffle(seed=42).select(range(size_eval_dataset)) + ) + eval_dataset = ( + tokenized_datasets["validation"] + .shuffle(seed=42) + .select(range(size_eval_dataset)) + ) + + # Create model + model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) + + model_name = model_checkpoint.split("/")[-1] + + data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) + + # Train the model + metric = load_metric( + "sacrebleu", cache_dir="hydra_log/multirun/translation/dataset" + ) + + def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [[label.strip()] for label in labels] + return preds, labels + + def compute_metrics(eval_preds): + preds, labels = eval_preds + if isinstance(preds, tuple): + preds = preds[0] + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds, decoded_labels = postprocess_text(decoded_preds, decoded_labels) + + result = metric.compute(predictions=decoded_preds, references=decoded_labels) + result = {"sacrebleu": result["score"]} + + prediction_lens = [ + np.count_nonzero(pred != tokenizer.pad_token_id) for pred in preds + ] + result["gen_len"] = np.mean(prediction_lens) + result = {k: round(v, 4) for k, v in result.items()} + return result + + class CustomCallback(TrainerCallback): + def __init__(self, trainer, dataset) -> None: + super().__init__() + self._trainer = trainer + self.dataset = dataset + + def on_epoch_end(self, args, state, control, **kwargs): + if control.should_evaluate: + control_copy = deepcopy(control) + self._trainer.evaluate( + eval_dataset=self.dataset, metric_key_prefix="train" + ) + return control_copy + + trainer = Seq2SeqTrainer( + model, + training_args, + train_dataset=train_dataset, + eval_dataset=eval_dataset, + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, + ) + + trainer.add_callback(CustomCallback(trainer, eval_train_dataset)) + trainer.train() + # Evaluate model + eval_metrics = trainer.evaluate() + + # Print out memory stats + print("Total GPU memory:", torch.cuda.get_device_properties(0).total_memory) + print("GPU memory reserved:", torch.cuda.memory_reserved(0)) + print("GPU memory allocated:", torch.cuda.memory_allocated(0)) + + return -eval_metrics["eval_sacrebleu"] + + +# ======================================================================= +# Main +# ======================================================================= +if __name__ == "__main__": + main() diff --git a/examples/huggingface/requirements.txt b/examples/huggingface/requirements.txt new file mode 100644 index 000000000..2305ea009 --- /dev/null +++ b/examples/huggingface/requirements.txt @@ -0,0 +1,5 @@ +datasets +transformers +hydra +hydra-orion-sweeper +comet-ml \ No newline at end of file diff --git a/examples/tutorials/code_1_python_api.py b/examples/tutorials/code_1_python_api.py index fadc373bf..68601b691 100644 --- a/examples/tutorials/code_1_python_api.py +++ b/examples/tutorials/code_1_python_api.py @@ -104,7 +104,7 @@ def rosenbrock(x, noise=None): experiment = build_experiment( "tpe-rosenbrock", space=space, - algorithms={"tpe": {"n_initial_points": 5}}, + algorithm={"tpe": {"n_initial_points": 5}}, storage=storage, ) diff --git a/examples/tutorials/code_2_hyperband_checkpoint.py b/examples/tutorials/code_2_hyperband_checkpoint.py index df3f209ae..68f2ebbbe 100644 --- a/examples/tutorials/code_2_hyperband_checkpoint.py +++ b/examples/tutorials/code_2_hyperband_checkpoint.py @@ -304,7 +304,7 @@ def run_hpo(): "weight_decay": "loguniform(1e-10, 1e-2)", "gamma": "loguniform(0.97, 1)", }, - algorithms={ + algorithm={ "hyperband": { "seed": 1, "repetitions": 5, diff --git a/src/orion/algo/nevergradoptimizer/nevergradoptimizer.py b/src/orion/algo/nevergradoptimizer/nevergradoptimizer.py index 48cf0c56c..34bc7f0b3 100644 --- a/src/orion/algo/nevergradoptimizer/nevergradoptimizer.py +++ b/src/orion/algo/nevergradoptimizer/nevergradoptimizer.py @@ -144,6 +144,11 @@ def _(dim: Fidelity): "RPowell", "RSQP", "PymooNSGA2", + "NGOpt12", + "NGOpt13", + "NGOpt14", + "NGOpt21", + "NGOpt38", } diff --git a/src/orion/benchmark/__init__.py b/src/orion/benchmark/__init__.py index 9f3b01a1a..c8e2417f1 100644 --- a/src/orion/benchmark/__init__.py +++ b/src/orion/benchmark/__init__.py @@ -212,9 +212,7 @@ def get_experiments(self, silent=True): for _, exp in study.get_experiments(): exp_column = dict() stats = exp.stats - exp_column["Algorithm"] = list(exp.configuration["algorithms"].keys())[ - 0 - ] + exp_column["Algorithm"] = list(exp.configuration["algorithm"].keys())[0] exp_column["Experiment Name"] = exp.name exp_column["Number Trial"] = len(exp.fetch_trials()) exp_column["Best Evaluation"] = stats.best_evaluation @@ -405,7 +403,7 @@ def setup_experiments(self): experiment = create_experiment( experiment_name, space=space, - algorithms=algorithm.experiment_algorithm, + algorithm=algorithm.experiment_algorithm, max_trials=max_trials, storage=self.benchmark.storage, executor=executor, @@ -430,7 +428,7 @@ def status(self): for _, experiment in self.get_experiments(): trials = experiment.fetch_trials() - algorithm_name = list(experiment.configuration["algorithms"].keys())[0] + algorithm_name = list(experiment.configuration["algorithm"].keys())[0] if algorithm_tasks.get(algorithm_name, None) is None: task_state = { @@ -482,7 +480,7 @@ def get_experiments(self, algorithms=None): for repetition_index, experiment in self.experiments_info: if ( algorithms is None - or list(experiment.algorithms.configuration.keys())[0] in algorithms + or list(experiment.algorithm.configuration.keys())[0] in algorithms ): exps.append((repetition_index, experiment)) return exps diff --git a/src/orion/benchmark/assessment/averagerank.py b/src/orion/benchmark/assessment/averagerank.py index b2d6db412..e9ea2c736 100644 --- a/src/orion/benchmark/assessment/averagerank.py +++ b/src/orion/benchmark/assessment/averagerank.py @@ -34,7 +34,7 @@ def analysis(self, task, experiments): algorithm_groups = defaultdict(list) for _, exp in experiments: - algorithm_name = list(exp.configuration["algorithms"].keys())[0] + algorithm_name = list(exp.configuration["algorithm"].keys())[0] algorithm_groups[algorithm_name].append(exp) return {rankings.__name__: rankings(algorithm_groups)} diff --git a/src/orion/benchmark/assessment/averageresult.py b/src/orion/benchmark/assessment/averageresult.py index fd0b9c6c9..d55a819cc 100644 --- a/src/orion/benchmark/assessment/averageresult.py +++ b/src/orion/benchmark/assessment/averageresult.py @@ -32,7 +32,7 @@ def analysis(self, task, experiments): algorithm_groups = defaultdict(list) for _, exp in experiments: - algorithm_name = list(exp.configuration["algorithms"].keys())[0] + algorithm_name = list(exp.configuration["algorithm"].keys())[0] algorithm_groups[algorithm_name].append(exp) return {regrets.__name__: regrets(algorithm_groups)} diff --git a/src/orion/benchmark/assessment/parallelassessment.py b/src/orion/benchmark/assessment/parallelassessment.py index d2e6d1070..14648c60e 100644 --- a/src/orion/benchmark/assessment/parallelassessment.py +++ b/src/orion/benchmark/assessment/parallelassessment.py @@ -62,7 +62,7 @@ def analysis(self, task, experiments): algorithm_groups = defaultdict(list) algorithm_worker_groups = defaultdict(list) for repetition_index, exp in experiments: - algorithm_name = list(exp.configuration["algorithms"].keys())[0] + algorithm_name = list(exp.configuration["algorithm"].keys())[0] algorithm_groups[algorithm_name].append(exp) n_worker = self.workers[repetition_index] diff --git a/src/orion/client/__init__.py b/src/orion/client/__init__.py index 4bc5e59f6..f8fd81fa8 100644 --- a/src/orion/client/__init__.py +++ b/src/orion/client/__init__.py @@ -55,6 +55,7 @@ def build_experiment( name: str, version: int | None = None, space: dict[str, Any] | None = None, + algorithm: type[BaseAlgorithm] | dict | None = None, algorithms: type[BaseAlgorithm] | dict | None = None, strategy: str | dict | None = None, max_trials: int | None = None, @@ -81,7 +82,7 @@ def build_experiment( ``name`` and ``space`` arguments are required, otherwise ``NoConfigurationError`` will be raised. - All other arguments (``algorithms``, ``strategy``, ``max_trials``, ``storage``, ``branching`` + All other arguments (``algorithm``, ``strategy``, ``max_trials``, ``storage``, ``branching`` and ``working_dir``) will be replaced by system's defaults if omitted. The system's defaults can also be overridden in global configuration file as described for the database in :ref:`Database Configuration`. We do not recommend overriding the algorithm configuration using @@ -133,7 +134,7 @@ def build_experiment( or 1 for new experiment. space: dict, optional Optimization space of the algorithm. Should have the form ``dict(name='(args)')``. - algorithms: str or dict, optional + algorithm: str or dict, optional Algorithm used for optimization. strategy: str or dict, optional Deprecated and will be remove in v0.4. It should now be set in algorithm configuration @@ -258,6 +259,7 @@ def _build_experiment( name, version=version, space=space, + algorithm=algorithm, algorithms=algorithms, max_trials=max_trials, max_broken=max_broken, @@ -274,6 +276,7 @@ def _build_experiment( name, version=version, space=space, + algorithm=algorithm, algorithms=algorithms, strategy=strategy, max_trials=max_trials, @@ -332,6 +335,7 @@ def workon( function: Callable, space: dict, name: str = "loop", + algorithm: type[BaseAlgorithm] | str | dict | None = None, algorithms: type[BaseAlgorithm] | str | dict | None = None, max_trials: int | None = None, max_broken: int | None = None, @@ -341,7 +345,7 @@ def workon( This will create a new experiment with an in-memory storage and optimize the given function until `max_trials` is reached or the `algorithm` is done - (some algorithms like random search are never done). + (some algorithm like random search are never done). For information on how to fetch results, see :py:class:`orion.client.experiment.ExperimentClient`. @@ -359,7 +363,7 @@ def workon( or 1 for new experiment. space: dict, optional Optimization space of the algorithm. Should have the form `dict(name='(args)')`. - algorithms: str or dict, optional + algorithm: str or dict, optional Algorithm used for optimization. max_trials: int, optional Maximum number or trials before the experiment is considered done. @@ -376,6 +380,7 @@ def workon( name, version=1, space=space, + algorithm=algorithm, algorithms=algorithms, max_trials=max_trials, max_broken=max_broken, diff --git a/src/orion/client/experiment.py b/src/orion/client/experiment.py index 059f53523..2b1e8c473 100644 --- a/src/orion/client/experiment.py +++ b/src/orion/client/experiment.py @@ -171,9 +171,9 @@ def space(self) -> Space: return space @property - def algorithms(self): - """Algorithms of the experiment.""" - return self._experiment.algorithms + def algorithm(self): + """Algorithm of the experiment.""" + return self._experiment.algorithm @property def refers(self): @@ -814,8 +814,8 @@ def workon( if self.max_trials > max_trials: self._experiment.max_trials = max_trials - assert self._experiment.algorithms is not None - self._experiment.algorithms.max_trials = max_trials + assert self._experiment.algorithm is not None + self._experiment.algorithm.max_trials = max_trials with SetupWorkingDir(self): diff --git a/src/orion/core/__init__.py b/src/orion/core/__init__.py index 3521a6124..f98530e26 100644 --- a/src/orion/core/__init__.py +++ b/src/orion/core/__init__.py @@ -209,12 +209,24 @@ def define_experiment_config(config): ) experiment_config.add_option( - "algorithms", + "algorithm", option_type=dict, default={"random": {"seed": None}}, help="Algorithm configuration for the experiment.", ) + experiment_config.add_option( + "algorithms", + option_type=dict, + default={"random": {"seed": None}}, + deprecate=dict( + version="v0.4", + alternative="worker.algorithm", + name="experiment.algorithm", + ), + help="This argument will be removed in v0.4. Use algorithm instead", + ) + experiment_config.add_option( "strategy", option_type=dict, diff --git a/src/orion/core/cli/db/upgrade.py b/src/orion/core/cli/db/upgrade.py index a829e8c43..2c2991c96 100644 --- a/src/orion/core/cli/db/upgrade.py +++ b/src/orion/core/cli/db/upgrade.py @@ -11,6 +11,7 @@ import sys from orion.core.io import experiment_builder +from orion.core.io.config import ConfigurationError from orion.core.io.database.ephemeraldb import EphemeralCollection from orion.core.io.database.mongodb import MongoDB from orion.core.io.database.pickleddb import PickledDB @@ -122,8 +123,19 @@ def upgrade_documents(storage): for experiment in storage.fetch_experiments({}): add_version(experiment) uid = experiment.pop("_id") + algorithm = None + if "algorithms" in experiment: + algorithm = experiment.pop("algorithms") + if "algorithm" in experiment: + algorithm = experiment.pop("algorithm") + + if algorithm is None: + raise ConfigurationError( + "The data was corrupted, as there was no algorithm in the experiment" + ) + storage.update_experiment(uid=experiment, **experiment) - storage.initialize_algorithm_lock(uid, experiment["algorithms"]) + storage.initialize_algorithm_lock(uid, algorithm) for trial in storage.fetch_trials(uid=uid): # trial_config = trial.to_dict() diff --git a/src/orion/core/cli/frontend.py b/src/orion/core/cli/frontend.py index 67bf3bb68..1fedf5415 100644 --- a/src/orion/core/cli/frontend.py +++ b/src/orion/core/cli/frontend.py @@ -29,12 +29,25 @@ def get_dashboard_build_path(): https://docs.python.org/3/distutils/setupscript.html#installing-additional-files Otherwise, dashboard build should be in dashboard folder near src in orion repository. + + NB (2023/02/20): It seems that, depending on installation, additional files may + be installed in `/local` instead of just ``. + More info: + https://stackoverflow.com/questions/14211575/any-python-function-to-get-data-files-root-directory#comment99087548_14211600 """ current_file_path = __file__ if current_file_path.startswith(sys.prefix): dashboard_build_path = os.path.join(sys.prefix, "orion-dashboard", "build") + if not os.path.isdir(dashboard_build_path): + dashboard_build_path = os.path.join( + sys.prefix, "local", "orion-dashboard", "build" + ) elif current_file_path.startswith(site.USER_BASE): dashboard_build_path = os.path.join(site.USER_BASE, "orion-dashboard", "build") + if not os.path.isdir(dashboard_build_path): + dashboard_build_path = os.path.join( + site.USER_BASE, "local", "orion-dashboard", "build" + ) else: dashboard_build_path = os.path.abspath( os.path.join( @@ -114,7 +127,7 @@ def add_subparser(parser): def main(args): """Starts an application server to serve http requests""" - app = falcon.API() + app = falcon.App() resource = StaticResource(args) app.add_sink(resource.on_get) diff --git a/src/orion/core/evc/conflicts.py b/src/orion/core/evc/conflicts.py index 33ff34ef7..84f0fcf1a 100644 --- a/src/orion/core/evc/conflicts.py +++ b/src/orion/core/evc/conflicts.py @@ -1083,7 +1083,7 @@ def detect(cls, old_config, new_config, branching_config=None): """Detect if algorithm definition in `new_config` differs from `old_config` :param branching_config: """ - if old_config["algorithms"] != new_config["algorithms"]: + if old_config["algorithm"] != new_config["algorithm"]: yield cls(old_config, new_config) def try_resolve(self, *args, **kwargs): @@ -1097,14 +1097,14 @@ def try_resolve(self, *args, **kwargs): def diff(self): """Produce human-readable differences""" return colored_diff( - pprint.pformat(self.old_config["algorithms"]), - pprint.pformat(self.new_config["algorithms"]), + pprint.pformat(self.old_config["algorithm"]), + pprint.pformat(self.new_config["algorithm"]), ) def __repr__(self): # TODO: select different subset rather than printing the old dict - formatted_old_config = pprint.pformat(self.old_config["algorithms"]) - formatted_new_config = pprint.pformat(self.new_config["algorithms"]) + formatted_old_config = pprint.pformat(self.old_config["algorithm"]) + formatted_new_config = pprint.pformat(self.new_config["algorithm"]) return f"{formatted_old_config}\n !=\n{formatted_new_config}" class AlgorithmResolution(Resolution): diff --git a/src/orion/core/evc/experiment.py b/src/orion/core/evc/experiment.py index a1e54255f..fa56016af 100644 --- a/src/orion/core/evc/experiment.py +++ b/src/orion/core/evc/experiment.py @@ -235,10 +235,7 @@ def _adapt_parent_trials(node, parent_trials_node, ids): parent.item["trials"] = [ trial for trial in parent.item["trials"] - if trial.compute_trial_hash( - trial, ignore_lie=True, ignore_experiment=True - ) - not in node_ids + if trial.compute_trial_hash(trial, ignore_lie=True) not in node_ids ] return node.item, parent_trials_node @@ -266,10 +263,7 @@ def _adapt_children_trials(node, children_trials_nodes): subchild.item["trials"] = [ trial for trial in subchild.item["trials"] - if trial.compute_trial_hash( - trial, ignore_lie=True, ignore_experiment=True - ) - not in ids + if trial.compute_trial_hash(trial, ignore_lie=True) not in ids ] return node.item, children_trials_nodes diff --git a/src/orion/core/io/experiment_builder.py b/src/orion/core/io/experiment_builder.py index d810c64d7..9a65c8c5f 100644 --- a/src/orion/core/io/experiment_builder.py +++ b/src/orion/core/io/experiment_builder.py @@ -150,11 +150,11 @@ def clean_config(name: str, config: dict, branching: dict | None): def merge_algorithm_config(config: dict, new_config: dict) -> None: """Merge given algorithm configuration with db config""" # TODO: Find a better solution - if isinstance(config.get("algorithms"), dict) and len(config["algorithms"]) > 1: + if isinstance(config.get("algorithm"), dict) and len(config["algorithm"]) > 1: log.debug("Overriding algo config with new one.") - log.debug(" Old config:\n%s", pprint.pformat(config["algorithms"])) - log.debug(" New config:\n%s", pprint.pformat(new_config["algorithms"])) - config["algorithms"] = new_config["algorithms"] + log.debug(" Old config:\n%s", pprint.pformat(config["algorithm"])) + log.debug(" New config:\n%s", pprint.pformat(new_config["algorithm"])) + config["algorithm"] = new_config["algorithm"] # TODO: Remove for v0.4 @@ -251,13 +251,13 @@ def _instantiate_algo( ---------- config: Configuration of the algorithm. If None or empty, system's defaults are used - (orion.core.config.experiment.algorithms). + (orion.core.config.experiment.algorithm). ignore_unavailable: bool, optional If True and algorithm is not available (plugin not installed), return the configuration. Otherwise, raise Factory error. """ - config = config or orion.core.config.experiment.algorithms + config = config or orion.core.config.experiment.algorithm assert config is not None try: algo_type: type[BaseAlgorithm] @@ -533,7 +533,7 @@ def build( space: dict, optional Optimization space of the algorithm. Should have the form ``dict(name='(args)')``. - algorithms: str or dict, optional + algorithm: str or dict, optional Algorithm used for optimization. strategy: str or dict, optional Deprecated and will be remove in v0.4. It should now be set in algorithm configuration @@ -868,13 +868,16 @@ def _branch_experiment(self, experiment, conflicts, version, branching_arguments return self.create_experiment(mode="x", **config) - # pylint: disable=too-many-arguments + # too-many-locals disabled for the deprecation of algorithms. + # We will be able to able once algorithms is removed + # pylint: disable=too-many-arguments,too-many-locals def create_experiment( self, name: str, version: int, mode: Mode, space: Space | dict[str, str], + algorithm: str | dict | None = None, algorithms: str | dict | None = None, max_trials: int | None = None, max_broken: int | None = None, @@ -905,7 +908,7 @@ def create_experiment( space: dict or Space object Optimization space of the algorithm. If dict, should have the form `dict(name='(args)')`. - algorithms: str or dict, optional + algorithm: str or dict, optional Algorithm used for optimization. strategy: str or dict, optional Parallel strategy to use to parallelize the algorithm. @@ -930,13 +933,25 @@ def _default(v: T | None, default: V) -> T | V: max_trials = _default(max_trials, orion.core.config.experiment.max_trials) if isinstance(knowledge_base, dict): knowledge_base = _instantiate_knowledge_base(knowledge_base) + instantiated_algorithm = _instantiate_algo( space=space, max_trials=max_trials, - config=algorithms, + config=algorithm, ignore_unavailable=mode != "x", knowledge_base=knowledge_base, ) + if algorithms is not None and algorithm is None: + log.warning( + "algorithms is deprecated and will be removed in v0.4.0. Use algorithm instead." + ) + instantiated_algorithm = _instantiate_algo( + space=space, + max_trials=max_trials, + config=algorithms, + ignore_unavailable=mode != "x", + knowledge_base=knowledge_base, + ) max_broken = _default(max_broken, orion.core.config.experiment.max_broken) working_dir = _default(working_dir, orion.core.config.experiment.working_dir) @@ -954,14 +969,13 @@ def _default(v: T | None, default: V) -> T | V: space=space, _id=_id, max_trials=max_trials, - algorithms=instantiated_algorithm, + algorithm=instantiated_algorithm, max_broken=max_broken, working_dir=working_dir, metadata=metadata, refers=refers, knowledge_base=knowledge_base, ) - if kwargs: # TODO: https://github.com/Epistimio/orion/issues/972 log.debug( diff --git a/src/orion/core/utils/format_terminal.py b/src/orion/core/utils/format_terminal.py index 522dea5f5..64abfe8d8 100644 --- a/src/orion/core/utils/format_terminal.py +++ b/src/orion/core/utils/format_terminal.py @@ -281,7 +281,7 @@ def format_algorithm(experiment): """Render a string for algorithm section""" algorithm_string = ALGORITHM_TEMPLATE.format( title=format_title("Algorithm"), - configuration=format_dict(experiment.configuration["algorithms"]), + configuration=format_dict(experiment.configuration["algorithm"]), ) return algorithm_string diff --git a/src/orion/core/worker/experiment.py b/src/orion/core/worker/experiment.py index a5052fbb1..3e54d8c91 100644 --- a/src/orion/core/worker/experiment.py +++ b/src/orion/core/worker/experiment.py @@ -134,7 +134,7 @@ class Experiment(Generic[AlgoT]): it will overwrite the previous one. space: Space Object representing the optimization space. - algorithms: `BaseAlgorithm` object or a wrapper. + algorithm: `BaseAlgorithm` object or a wrapper. Complete specification of the optimization and dynamical procedures taking place in this `Experiment`. @@ -172,7 +172,7 @@ class Experiment(Generic[AlgoT]): "max_broken", "version", "space", - "algorithms", + "algorithm", "working_dir", "knowledge_base", "_id", @@ -192,7 +192,7 @@ def __init__( _id: str | int | None = None, max_trials: int | None = None, max_broken: int | None = None, - algorithms: AlgoT | None = None, + algorithm: AlgoT | None = None, working_dir: str | None = None, metadata: dict | None = None, refers: dict | None = None, @@ -209,9 +209,8 @@ def __init__( self.max_trials = max_trials self.max_broken = max_broken self.knowledge_base = knowledge_base - self.algorithms = algorithms self.working_dir = working_dir - + self.algorithm = algorithm self._storage = storage self._node = ExperimentNode( @@ -491,14 +490,14 @@ def acquire_algorithm_lock( with self._storage.acquire_algorithm_lock( experiment=self, timeout=timeout, retry_interval=retry_interval ) as locked_algorithm_state: - assert self.algorithms is not None - if locked_algorithm_state.configuration != self.algorithms.configuration: + assert self.algorithm is not None + if locked_algorithm_state.configuration != self.algorithm.configuration: log.warning( "Saved configuration: %s", locked_algorithm_state.configuration ) log.warning( "Current configuration: %s %s", - self.algorithms.configuration, + self.algorithm.configuration, self._storage._db, ) raise RuntimeError( @@ -507,11 +506,11 @@ def acquire_algorithm_lock( ) if locked_algorithm_state.state: - self.algorithms.set_state(locked_algorithm_state.state) + self.algorithm.set_state(locked_algorithm_state.state) - yield self.algorithms + yield self.algorithm - locked_algorithm_state.set_state(self.algorithms.state_dict) + locked_algorithm_state.set_state(self.algorithm.state_dict) def _select_evc_call(self, with_evc_tree, function, *args, **kwargs): if self._node is not None and with_evc_tree: @@ -591,7 +590,7 @@ def is_done(self): """Return True, if this experiment is considered to be finished. 1. Count how many trials have been completed and compare with ``max_trials``. - 2. Ask ``algorithms`` if they consider there is a chance for further improvement, and + 2. Ask ``algorithm`` if they consider there is a chance for further improvement, and verify is there is any pending trial. .. note:: @@ -610,7 +609,7 @@ def is_done(self): num_pending_trials += 1 return (num_completed_trials >= self.max_trials) or ( - self.algorithms.is_done and num_pending_trials == 0 + self.algorithm.is_done and num_pending_trials == 0 ) @property diff --git a/src/orion/core/worker/experiment_config.py b/src/orion/core/worker/experiment_config.py index bc278f726..dd669d093 100644 --- a/src/orion/core/worker/experiment_config.py +++ b/src/orion/core/worker/experiment_config.py @@ -111,7 +111,7 @@ class ExperimentConfig(TypedDict): space: dict[str, Any] """ Object representing the optimization space. """ - algorithms: dict[str, Any] | None + algorithm: dict[str, Any] | None """ Complete specification of the optimization and dynamical procedures taking place in the Experiment. """ @@ -146,6 +146,6 @@ class PartialExperimentConfig(ExperimentConfig, total=False): max_trials: int | None max_broken: int | None space: dict[str, Any] - algorithms: dict[str, Any] | None + algorithm: dict[str, Any] | None working_dir: str | None knowledge_base: dict[str, Any] | None diff --git a/src/orion/serving/benchmarks_resource.py b/src/orion/serving/benchmarks_resource.py index 02a907173..c03f6b826 100644 --- a/src/orion/serving/benchmarks_resource.py +++ b/src/orion/serving/benchmarks_resource.py @@ -24,7 +24,7 @@ def on_get(self, req: Request, resp: Response): benchmarks = self.storage.fetch_benchmark({}) response = build_benchmarks_response(benchmarks) - resp.body = json.dumps(response) + resp.text = json.dumps(response) def on_get_benchmark(self, req: Request, resp: Response, name: str): """ @@ -44,4 +44,4 @@ def on_get_benchmark(self, req: Request, resp: Response, name: str): ) response = build_benchmark_response(benchmark, assessment, task, algorithms) - resp.body = json.dumps(response) + resp.text = json.dumps(response) diff --git a/src/orion/serving/experiments_resource.py b/src/orion/serving/experiments_resource.py index 92b8165ec..55164e81b 100644 --- a/src/orion/serving/experiments_resource.py +++ b/src/orion/serving/experiments_resource.py @@ -31,7 +31,7 @@ def on_get(self, req: Request, resp: Response): leaf_experiments = _find_latest_versions(experiments) response = build_experiments_response(leaf_experiments) - resp.body = json.dumps(response) + resp.text = json.dumps(response) def on_get_experiment(self, req: Request, resp: Response, name: str): """ @@ -47,7 +47,7 @@ def on_get_experiment(self, req: Request, resp: Response, name: str): best_trial = _retrieve_best_trial(experiment) response = build_experiment_response(experiment, status, algorithm, best_trial) - resp.body = json.dumps(response) + resp.text = json.dumps(response) def on_get_experiment_status(self, req: Request, resp: Response, name: str): """ @@ -57,7 +57,7 @@ def on_get_experiment_status(self, req: Request, resp: Response, name: str): verify_query_parameters(req.params, ["version"]) version = req.get_param_as_int("version") experiment = retrieve_experiment(self.storage, name, version) - resp.body = json.dumps(experiment.stats.to_json()) + resp.text = json.dumps(experiment.stats.to_json()) def _find_latest_versions(experiments): @@ -87,10 +87,10 @@ def _retrieve_status(experiment: Experiment) -> str: def _retrieve_algorithm(experiment: Experiment) -> dict: """Populates the `algorithm` key with the configuration of the experiment's algorithm.""" - algorithm_name = list(experiment.algorithms.configuration.keys())[0] + algorithm_name = list(experiment.algorithm.configuration.keys())[0] result = {"name": algorithm_name} - result.update(experiment.algorithms.configuration[algorithm_name]) + result.update(experiment.algorithm.configuration[algorithm_name]) return result diff --git a/src/orion/serving/parameters.py b/src/orion/serving/parameters.py index 6fd695c52..f9434c61b 100644 --- a/src/orion/serving/parameters.py +++ b/src/orion/serving/parameters.py @@ -40,7 +40,9 @@ def verify_query_parameters(parameters: dict, supported_parameters: list): for parameter in parameters: if parameter not in supported_parameters: description = _compose_error_message(parameter, supported_parameters) - raise falcon.HTTPBadRequest(ERROR_INVALID_PARAMETER, description) + raise falcon.HTTPBadRequest( + title=ERROR_INVALID_PARAMETER, description=description + ) def verify_status(status): @@ -51,7 +53,9 @@ def verify_status(status): list(Trial.allowed_stati) ) - raise falcon.HTTPBadRequest(ERROR_INVALID_PARAMETER, description) + raise falcon.HTTPBadRequest( + title=ERROR_INVALID_PARAMETER, description=description + ) def _compose_error_message(key: str, supported_parameters: list): diff --git a/src/orion/serving/plots_resources.py b/src/orion/serving/plots_resources.py index 3d7d46753..fe89b8eb8 100644 --- a/src/orion/serving/plots_resources.py +++ b/src/orion/serving/plots_resources.py @@ -26,7 +26,7 @@ def on_get_lpi(self, req: Request, resp: Response, experiment_name: str): experiment = ExperimentClient( retrieve_experiment(self.storage, experiment_name), None ) - resp.body = experiment.plot.lpi().to_json() + resp.text = experiment.plot.lpi().to_json() def on_get_parallel_coordinates( self, req: Request, resp: Response, experiment_name: str @@ -39,7 +39,7 @@ def on_get_parallel_coordinates( experiment = ExperimentClient( retrieve_experiment(self.storage, experiment_name), None ) - resp.body = experiment.plot.parallel_coordinates().to_json() + resp.text = experiment.plot.parallel_coordinates().to_json() def on_get_partial_dependencies( self, req: Request, resp: Response, experiment_name: str @@ -52,7 +52,7 @@ def on_get_partial_dependencies( experiment = ExperimentClient( retrieve_experiment(self.storage, experiment_name), None ) - resp.body = experiment.plot.partial_dependencies().to_json() + resp.text = experiment.plot.partial_dependencies().to_json() def on_get_regret(self, req: Request, resp: Response, experiment_name: str): """ @@ -62,4 +62,4 @@ def on_get_regret(self, req: Request, resp: Response, experiment_name: str): experiment = ExperimentClient( retrieve_experiment(self.storage, experiment_name), None ) - resp.body = experiment.plot.regret().to_json() + resp.text = experiment.plot.regret().to_json() diff --git a/src/orion/serving/runtime.py b/src/orion/serving/runtime.py index 6dfc9d011..73f05bdf3 100644 --- a/src/orion/serving/runtime.py +++ b/src/orion/serving/runtime.py @@ -30,4 +30,4 @@ def on_get(self, req, resp): "database": database, } - resp.body = json.dumps(response, indent=4) + resp.text = json.dumps(response, indent=4) diff --git a/src/orion/serving/trials_resource.py b/src/orion/serving/trials_resource.py index e093b0a82..1289d1690 100644 --- a/src/orion/serving/trials_resource.py +++ b/src/orion/serving/trials_resource.py @@ -44,7 +44,7 @@ def on_get_trials_in_experiment( trials = experiment.fetch_trials(with_ancestors) response = build_trials_response(trials) - resp.body = json.dumps(response) + resp.text = json.dumps(response) def on_get_trial_in_experiment( self, req: Request, resp: Response, experiment_name: str, trial_id: str @@ -57,4 +57,4 @@ def on_get_trial_in_experiment( trial = retrieve_trial(experiment, trial_id) response = build_trial_response(trial) - resp.body = json.dumps(response) + resp.text = json.dumps(response) diff --git a/src/orion/serving/webapi.py b/src/orion/serving/webapi.py index 3ca339c14..d72159559 100644 --- a/src/orion/serving/webapi.py +++ b/src/orion/serving/webapi.py @@ -76,7 +76,7 @@ def middleware(self): return MyCORSMiddleware(self) -class WebApi(falcon.API): +class WebApi(falcon.App): """ Main entry point into a Falcon-based app. An instance provides a callable WSGI interface and a routing engine. diff --git a/src/orion/storage/legacy.py b/src/orion/storage/legacy.py index b35b02fca..d626d0a46 100644 --- a/src/orion/storage/legacy.py +++ b/src/orion/storage/legacy.py @@ -122,7 +122,7 @@ def create_experiment(self, config): """See :func:`orion.storage.base.BaseStorageProtocol.create_experiment`""" exp_rval = self._db.write("experiments", data=config, query=None) self.initialize_algorithm_lock( - experiment_id=config["_id"], algorithm_config=config.get("algorithms", {}) + experiment_id=config["_id"], algorithm_config=config.get("algorithm", {}) ) return exp_rval diff --git a/src/orion/testing/__init__.py b/src/orion/testing/__init__.py index 85464aafa..2153e2fd6 100644 --- a/src/orion/testing/__init__.py +++ b/src/orion/testing/__init__.py @@ -33,7 +33,7 @@ "datetime": "2017-11-23T02:00:00", "orion_version": "XYZ", }, - "algorithms": {"random": {"seed": 1}}, + "algorithm": {"random": {"seed": 1}}, } base_trial = { @@ -133,7 +133,7 @@ def generate_benchmark_experiments_trials( exp = copy.deepcopy(experiment_config) exp["_id"] = i exp["name"] = f"experiment-name-{i}" - exp["algorithms"] = benchmark_algorithms[i % algo_num] + exp["algorithm"] = benchmark_algorithms[i % algo_num] exp["max_trials"] = max_trial exp["metadata"]["datetime"] = datetime.datetime.utcnow() gen_exps.append(exp) diff --git a/src/orion/testing/state.py b/src/orion/testing/state.py index 7a81786d3..25faf7ca5 100644 --- a/src/orion/testing/state.py +++ b/src/orion/testing/state.py @@ -267,11 +267,11 @@ def _set_tables(self): self.database.write("experiments", self._experiments) for experiment in self._experiments: self.storage.initialize_algorithm_lock( - experiment["_id"], experiment.get("algorithms") + experiment["_id"], experiment.get("algorithm") ) # For tests that need a deterministic experiment id. self.storage.initialize_algorithm_lock( - experiment["name"], experiment.get("algorithms") + experiment["name"], experiment.get("algorithm") ) if self._trials: self.database.write("trials", self._trials) diff --git a/tests/functional/algos/test_algos.py b/tests/functional/algos/test_algos.py index 08aa161db..25281b407 100644 --- a/tests/functional/algos/test_algos.py +++ b/tests/functional/algos/test_algos.py @@ -234,7 +234,7 @@ def test_missing_fidelity(algorithm: dict): """Test a simple usage scenario.""" task = CustomRosenbrock(max_trials=30, with_fidelity=False) with pytest.raises(RuntimeError) as exc: - workon(task, task.get_search_space(), algorithms=algorithm, max_trials=100) + workon(task, task.get_search_space(), algorithm=algorithm, max_trials=100) assert "https://orion.readthedocs.io/en/develop/user/algorithms.html" in str( exc.value @@ -256,10 +256,10 @@ def test_missing_fidelity(algorithm: dict): def test_simple(algorithm: dict): """Test a simple usage scenario.""" max_trials = 30 - exp = workon(rosenbrock, space, algorithms=algorithm, max_trials=max_trials) + exp = workon(rosenbrock, space, algorithm=algorithm, max_trials=max_trials) assert exp.max_trials == max_trials - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials() assert len(trials) == max_trials @@ -284,7 +284,7 @@ def test_cardinality_stop_uniform(algorithm: dict): """Test when algo needs to stop because all space is explored (discrete space).""" discrete_space = copy.deepcopy(space) discrete_space["x"] = "uniform(-10, 5, discrete=True)" - exp = workon(rosenbrock, discrete_space, algorithms=algorithm, max_trials=30) + exp = workon(rosenbrock, discrete_space, algorithm=algorithm, max_trials=30) trials = exp.fetch_trials() assert len(trials) == 16 @@ -302,9 +302,9 @@ def test_cardinality_stop_loguniform(algorithm: dict): max_trials = 30 exp = workon( - rosenbrock, space=discrete_space, algorithms=algorithm, max_trials=max_trials + rosenbrock, space=discrete_space, algorithm=algorithm, max_trials=max_trials ) - algo_wrapper: SpaceTransform = exp.algorithms + algo_wrapper: SpaceTransform = exp.algorithm assert algo_wrapper.space == discrete_space assert algo_wrapper.algorithm.is_done assert algo_wrapper.is_done @@ -331,14 +331,14 @@ def test_with_nested_spaces(algorithm: dict): exp = workon( nested_rosenbrock, nested_space, - algorithms=algorithm, + algorithm=algorithm, max_trials=30, ) - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials() - assert len(trials) >= 30 or exp.algorithms.is_done + assert len(trials) >= 30 or exp.algorithm.is_done assert trials[-1].status == "completed" assert set(trials[-1].params.keys()) == {"x"} assert set(trials[-1].params["x"].keys()) == {"value", "noise"} @@ -357,14 +357,14 @@ def test_with_fidelity(algorithm: dict): exp = workon( rosenbrock_with_fidelity, space_with_fidelity, - algorithms=algorithm, + algorithm=algorithm, max_trials=30, ) - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials() - assert len(trials) >= 30 or exp.algorithms.is_done + assert len(trials) >= 30 or exp.algorithm.is_done assert trials[-1].status == "completed" trials = [trial for trial in trials if trial.status == "completed"] @@ -392,14 +392,12 @@ def test_with_multidim(algorithm): space["x"] = "uniform(-50, 50, shape=(2, 1))" MAX_TRIALS = 30 - exp = workon( - multidim_rosenbrock, space, algorithms=algorithm, max_trials=MAX_TRIALS - ) + exp = workon(multidim_rosenbrock, space, algorithm=algorithm, max_trials=MAX_TRIALS) - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials() - assert len(trials) >= 25 or exp.algorithms.is_done + assert len(trials) >= 25 or exp.algorithm.is_done completed_trials = exp.fetch_trials_by_status("completed") assert len(completed_trials) >= MAX_TRIALS or len(completed_trials) == len(trials) @@ -430,7 +428,7 @@ def test_with_evc(algorithm, storage): base_exp = build_experiment( name="exp", space=space_with_fidelity, - algorithms=algorithm_configs["random"], + algorithm=algorithm_configs["random"], max_trials=10, storage=storage, ) @@ -439,7 +437,7 @@ def test_with_evc(algorithm, storage): exp = build_experiment( name="exp", space=space_with_fidelity, - algorithms=algorithm, + algorithm=algorithm, max_trials=30, storage=storage, branching={"branch_from": "exp", "enable": True}, @@ -449,7 +447,7 @@ def test_with_evc(algorithm, storage): exp.workon(rosenbrock, max_trials=30) - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials(with_evc_tree=False) @@ -457,7 +455,7 @@ def test_with_evc(algorithm, storage): assert len(trials) >= 20 trials_with_evc = exp.fetch_trials(with_evc_tree=True) - assert len(trials_with_evc) >= 30 or exp.algorithms.is_done + assert len(trials_with_evc) >= 30 or exp.algorithm.is_done assert len(trials_with_evc) - len(trials) == 10 completed_trials = [ @@ -492,15 +490,15 @@ def test_parallel_workers(algorithm, storage): name = f"{list(algorithm.keys())[0]}_exp" exp = create_experiment( - name=name, space=space_with_fidelity, algorithms=algorithm, storage=storage + name=name, space=space_with_fidelity, algorithm=algorithm, storage=storage ) exp.workon(rosenbrock, max_trials=MAX_TRIALS, n_workers=2) - assert exp.configuration["algorithms"] == algorithm + assert exp.configuration["algorithm"] == algorithm trials = exp.fetch_trials() - assert len(trials) >= MAX_TRIALS or exp.algorithms.is_done + assert len(trials) >= MAX_TRIALS or exp.algorithm.is_done completed_trials = [trial for trial in trials if trial.status == "completed"] assert MAX_TRIALS <= len(completed_trials) <= MAX_TRIALS + 2 @@ -533,7 +531,7 @@ def test_branching_algos( exp = build_experiment( name="exp", space=space_with_fidelity, - algorithms=algorithm, + algorithm=algorithm, working_dir=tmp_path, storage=storage, ) @@ -543,15 +541,15 @@ def test_branching_algos( def build_params_hist(trial: Trial) -> list[str]: params = [trial.params_repr()] while trial.parent: - assert isinstance(exp.algorithms, AlgoWrapper) - trial = exp.algorithms.registry[trial.parent] + assert isinstance(exp.algorithm, AlgoWrapper) + trial = exp.algorithm.registry[trial.parent] params.append(trial.params_repr()) return params[::-1] for trial in exp.fetch_trials(): params_history = build_params_hist(trial) - assert isinstance(exp.algorithms, AlgoWrapper) - algo = exp.algorithms.unwrapped + assert isinstance(exp.algorithm, AlgoWrapper) + algo = exp.algorithm.unwrapped # TODO: This assumes algo.fidelities which may be specific to PBT... assert hasattr(algo, "fidelities") fidelities: list = algo.fidelities # type: ignore diff --git a/tests/functional/backward_compatibility/random.yaml b/tests/functional/backward_compatibility/random.yaml index ff90f1ffd..4645bb2f9 100644 --- a/tests/functional/backward_compatibility/random.yaml +++ b/tests/functional/backward_compatibility/random.yaml @@ -1,6 +1,6 @@ pool_size: 1 max_trials: 10 -algorithms: +algorithm: random: seed: 1 diff --git a/tests/functional/backward_compatibility/test_versions.py b/tests/functional/backward_compatibility/test_versions.py index 4fd20d0ec..df6d86663 100644 --- a/tests/functional/backward_compatibility/test_versions.py +++ b/tests/functional/backward_compatibility/test_versions.py @@ -7,6 +7,7 @@ import pytest from pymongo import MongoClient +import orion.core.cli import orion.core.io.experiment_builder as experiment_builder from orion.client import create_experiment from orion.core.io.database import database_factory @@ -221,7 +222,7 @@ def fill_db(request): orion_version = get_version("orion") assert orion_version != f"orion {version}" - print(execute("orion -vv db upgrade -f")) + orion.core.cli.main(["db", "upgrade", "-f"]) return version diff --git a/tests/functional/backward_compatibility/versions.txt b/tests/functional/backward_compatibility/versions.txt index 484484568..b848d5684 100644 --- a/tests/functional/backward_compatibility/versions.txt +++ b/tests/functional/backward_compatibility/versions.txt @@ -1,10 +1,3 @@ -0.1.6 -0.1.7 -0.1.8 -0.1.9 -0.1.10 -0.1.11 -0.1.12 0.1.13 0.1.14 0.1.15 @@ -13,3 +6,4 @@ 0.2.1 0.2.3 0.2.4.post1 +0.2.7 diff --git a/tests/functional/branching/new_algo_config.yaml b/tests/functional/branching/new_algo_config.yaml index d481569a2..2fa65550a 100644 --- a/tests/functional/branching/new_algo_config.yaml +++ b/tests/functional/branching/new_algo_config.yaml @@ -1,3 +1,3 @@ -algorithms: +algorithm: gradient_descent: learning_rate: 0.0001 diff --git a/tests/functional/branching/orion_config.yaml b/tests/functional/branching/orion_config.yaml index 631a5ba6d..ec25b1f3c 100644 --- a/tests/functional/branching/orion_config.yaml +++ b/tests/functional/branching/orion_config.yaml @@ -3,4 +3,4 @@ name: demo_random_search pool_size: 2 max_trials: 400 -algorithms: random +algorithm: random diff --git a/tests/functional/client/orion_config.yaml b/tests/functional/client/orion_config.yaml index 6547e14e1..9ebc9da9e 100644 --- a/tests/functional/client/orion_config.yaml +++ b/tests/functional/client/orion_config.yaml @@ -3,4 +3,4 @@ name: voila_voici pool_size: 1 max_trials: 100 -algorithms: random +algorithm: random diff --git a/tests/functional/commands/experiment.yaml b/tests/functional/commands/experiment.yaml index 7a84d9f98..65b5ab6f8 100644 --- a/tests/functional/commands/experiment.yaml +++ b/tests/functional/commands/experiment.yaml @@ -25,7 +25,7 @@ parent_id: null pool_size: 2 max_trials: 1000 - algorithms: + algorithm: random: seed: null @@ -49,7 +49,7 @@ parent_id: null pool_size: 2 max_trials: 1000 - algorithms: + algorithm: random: seed: null @@ -73,6 +73,6 @@ parent_id: null pool_size: 2 max_trials: 1000 - algorithms: + algorithm: random: seed: null diff --git a/tests/functional/commands/orion_config_random.yaml b/tests/functional/commands/orion_config_random.yaml index 631a5ba6d..ec25b1f3c 100644 --- a/tests/functional/commands/orion_config_random.yaml +++ b/tests/functional/commands/orion_config_random.yaml @@ -3,4 +3,4 @@ name: demo_random_search pool_size: 2 max_trials: 400 -algorithms: random +algorithm: random diff --git a/tests/functional/configuration/test_all_options.py b/tests/functional/configuration/test_all_options.py index c97b23b82..a5c71a38b 100644 --- a/tests/functional/configuration/test_all_options.py +++ b/tests/functional/configuration/test_all_options.py @@ -434,7 +434,7 @@ class TestExperimentConfig(ConfigurationTestSuite): "max_broken": 5, "working_dir": "here", "worker_trials": 5, - "algorithms": {"aa": {"b": "c", "d": {"e": "f"}}}, + "algorithm": {"aa": {"b": "c", "d": {"e": "f"}}}, } } @@ -450,7 +450,7 @@ class TestExperimentConfig(ConfigurationTestSuite): "max_trials": 75, "max_broken": 16, "working_dir": "in_db?", - "algorithms": {"ab": {"d": "i", "f": "g"}}, + "algorithm": {"ab": {"d": "i", "f": "g"}}, "space": {"/x": "uniform(0, 1)"}, "metadata": { "VCS": { @@ -494,7 +494,7 @@ class TestExperimentConfig(ConfigurationTestSuite): "max_trials": 50, "max_broken": 15, "working_dir": "here_again", - "algorithms": {"ac": {"d": "e", "f": "g"}}, + "algorithm": {"ac": {"d": "e", "f": "g"}}, } } diff --git a/tests/functional/core/worker/warm_start/test_warm_starting.py b/tests/functional/core/worker/warm_start/test_warm_starting.py index 4c95f0002..c7a36836d 100644 --- a/tests/functional/core/worker/warm_start/test_warm_starting.py +++ b/tests/functional/core/worker/warm_start/test_warm_starting.py @@ -68,7 +68,7 @@ def test_warm_starting_helps( source_experiment = build_experiment( name="source_exp", space=source_space, - algorithms={"of_type": "random", "seed": 42}, + algorithm={"of_type": "random", "seed": 42}, storage=storage, max_trials=n_source_trials, ) @@ -82,7 +82,7 @@ def test_warm_starting_helps( name="witout_warm_start", space=target_space, max_trials=max_trials, - algorithms=algo_config, + algorithm=algo_config, knowledge_base=None, ) assert len(without_warm_starting.fetch_trials()) == max_trials @@ -92,7 +92,7 @@ def test_warm_starting_helps( name="with_warm_start", space=target_space, max_trials=max_trials, - algorithms=algo_config, + algorithm=algo_config, knowledge_base=knowledge_base, ) assert len(with_warm_starting.fetch_trials()) == max_trials @@ -132,7 +132,7 @@ def test_warm_start_benchmarking(algo: type[BaseAlgorithm], tmp_path: Path): space=source_space, storage=warm_start_storage, max_trials=n_source_trials, - algorithms={"of_type": "random", "seed": 42}, + algorithm={"of_type": "random", "seed": 42}, ) source_experiment.workon(_wrap(source_task)) warm_start_kb = KnowledgeBase(warm_start_storage) @@ -145,7 +145,7 @@ def test_warm_start_benchmarking(algo: type[BaseAlgorithm], tmp_path: Path): space=target_space, storage=hot_start_storage, max_trials=n_source_trials, - algorithms={"of_type": "random", "seed": 42}, + algorithm={"of_type": "random", "seed": 42}, ) target_prior_experiment.workon(_wrap(target_task)) hot_start_kb = KnowledgeBase(hot_start_storage) @@ -157,7 +157,7 @@ def test_warm_start_benchmarking(algo: type[BaseAlgorithm], tmp_path: Path): name="cold_start", space=target_space, max_trials=max_trials, - algorithms=algo_config, + algorithm=algo_config, knowledge_base=None, ) # Warm-start: Prior information from the source task. @@ -166,7 +166,7 @@ def test_warm_start_benchmarking(algo: type[BaseAlgorithm], tmp_path: Path): name="warm_start", space=target_space, max_trials=max_trials, - algorithms=algo_config, + algorithm=algo_config, knowledge_base=warm_start_kb, ) # Hot-start: Prior information from the target task. @@ -175,7 +175,7 @@ def test_warm_start_benchmarking(algo: type[BaseAlgorithm], tmp_path: Path): name="hot_start", space=target_space, max_trials=max_trials, - algorithms=algo_config, + algorithm=algo_config, knowledge_base=hot_start_kb, ) diff --git a/tests/functional/demo/orion_config.yaml b/tests/functional/demo/orion_config.yaml index b1a53472c..b436e2b04 100644 --- a/tests/functional/demo/orion_config.yaml +++ b/tests/functional/demo/orion_config.yaml @@ -5,7 +5,7 @@ experiment: max_trials: 20 max_broken: 5 - algorithms: + algorithm: gradient_descent: learning_rate: 0.1 # dx_tolerance: 1e-7 diff --git a/tests/functional/demo/orion_config_other.yaml b/tests/functional/demo/orion_config_other.yaml index 4f446bd90..347fd4793 100644 --- a/tests/functional/demo/orion_config_other.yaml +++ b/tests/functional/demo/orion_config_other.yaml @@ -5,7 +5,7 @@ experiment: max_trials: 20 max_broken: 5 - algorithms: + algorithm: gradient_descent: learning_rate: 0.1 # dx_tolerance: 1e-7 diff --git a/tests/functional/demo/orion_config_random.yaml b/tests/functional/demo/orion_config_random.yaml index 524db9a29..1e6cd9055 100644 --- a/tests/functional/demo/orion_config_random.yaml +++ b/tests/functional/demo/orion_config_random.yaml @@ -4,6 +4,6 @@ pool_size: 2 max_trials: 20 max_broken: 5 -algorithms: +algorithm: random: seed: 2 diff --git a/tests/functional/demo/stress_gradient.yaml b/tests/functional/demo/stress_gradient.yaml index 1fab39981..bc3f0484a 100644 --- a/tests/functional/demo/stress_gradient.yaml +++ b/tests/functional/demo/stress_gradient.yaml @@ -1,4 +1,4 @@ -algorithms: +algorithm: gradient_descent: learning_rate: 0.23 dx_tolerance: 1.0e-7 diff --git a/tests/functional/demo/test_demo.py b/tests/functional/demo/test_demo.py index 385e23e9b..fbc670fc8 100644 --- a/tests/functional/demo/test_demo.py +++ b/tests/functional/demo/test_demo.py @@ -41,7 +41,7 @@ def test_demo_with_default_algo_cli_config_only(storage, monkeypatch): assert exp["name"] == "default_algo" assert exp["max_trials"] == 5 assert exp["max_broken"] == 3 - assert exp["algorithms"] == {"random": {"seed": None}} + assert exp["algorithm"] == {"random": {"seed": None}} assert "user" in exp["metadata"] assert "datetime" in exp["metadata"] assert "orion_version" in exp["metadata"] @@ -105,7 +105,7 @@ def test_demo(storage, monkeypatch): assert exp["name"] == "voila_voici" assert exp["max_trials"] == 20 assert exp["max_broken"] == 5 - assert exp["algorithms"] == { + assert exp["algorithm"] == { "gradient_descent": {"learning_rate": 0.1, "dx_tolerance": 1e-5} } assert "user" in exp["metadata"] @@ -155,7 +155,7 @@ def test_demo_with_script_config(storage, monkeypatch): assert exp["name"] == "voila_voici" assert exp["max_trials"] == 20 assert exp["max_broken"] == 5 - assert exp["algorithms"] == { + assert exp["algorithm"] == { "gradient_descent": {"learning_rate": 0.1, "dx_tolerance": 1e-5} } assert "user" in exp["metadata"] @@ -211,7 +211,7 @@ def test_demo_with_python_and_script(storage, monkeypatch): assert exp["name"] == "voila_voici" assert exp["max_trials"] == 20 assert exp["max_broken"] == 5 - assert exp["algorithms"] == { + assert exp["algorithm"] == { "gradient_descent": {"learning_rate": 0.1, "dx_tolerance": 1e-5} } assert "user" in exp["metadata"] @@ -348,7 +348,7 @@ def test_demo_four_workers(tmp_path, storage, monkeypatch): assert exp["name"] == "four_workers_demo" assert exp["max_trials"] == 20 assert exp["max_broken"] == 5 - assert exp["algorithms"] == {"random": {"seed": 2}} + assert exp["algorithm"] == {"random": {"seed": 2}} assert "user" in exp["metadata"] assert "datetime" in exp["metadata"] assert "orion_version" in exp["metadata"] @@ -371,7 +371,7 @@ def test_workon(): """Test scenario having a configured experiment already setup.""" name = "voici_voila" config = {"name": name} - config["algorithms"] = {"random": {"seed": 1}} + config["algorithm"] = {"random": {"seed": 1}} config["max_trials"] = 50 config["exp_max_broken"] = 5 config["user_args"] = [ @@ -411,7 +411,7 @@ def test_workon(): assert exp["name"] == name assert exp["max_trials"] == 50 assert exp["max_broken"] == 5 - assert exp["algorithms"] == {"random": {"seed": 1}} + assert exp["algorithm"] == {"random": {"seed": 1}} assert "user" in exp["metadata"] assert "datetime" in exp["metadata"] assert "user_script" in exp["metadata"] @@ -789,7 +789,7 @@ def test_demo_with_nondefault_config_keyword(storage, monkeypatch): exp_id = exp["_id"] assert exp["name"] == "voila_voici" assert exp["max_trials"] == 20 - assert exp["algorithms"] == { + assert exp["algorithm"] == { "gradient_descent": {"learning_rate": 0.1, "dx_tolerance": 1e-5} } assert "user" in exp["metadata"] diff --git a/tests/functional/example/orion_config.yaml b/tests/functional/example/orion_config.yaml index 19e95ab7b..6df2ad80f 100644 --- a/tests/functional/example/orion_config.yaml +++ b/tests/functional/example/orion_config.yaml @@ -2,6 +2,6 @@ name: scikit-iris-tutorial max_trials: 1 -algorithms: +algorithm: random: seed: 1 diff --git a/tests/functional/parsing/orion_config_random.yaml b/tests/functional/parsing/orion_config_random.yaml index 631a5ba6d..ec25b1f3c 100644 --- a/tests/functional/parsing/orion_config_random.yaml +++ b/tests/functional/parsing/orion_config_random.yaml @@ -3,4 +3,4 @@ name: demo_random_search pool_size: 2 max_trials: 400 -algorithms: random +algorithm: random diff --git a/tests/functional/serving/test_experiments_resource.py b/tests/functional/serving/test_experiments_resource.py index 490dc6318..8ca2fcd73 100644 --- a/tests/functional/serving/test_experiments_resource.py +++ b/tests/functional/serving/test_experiments_resource.py @@ -25,7 +25,7 @@ max_trials=10, max_broken=7, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, producer={"strategy": "NoParallelStrategy"}, ) diff --git a/tests/functional/serving/test_plots_resource.py b/tests/functional/serving/test_plots_resource.py index 7ff1fc282..fb2cd4be9 100644 --- a/tests/functional/serving/test_plots_resource.py +++ b/tests/functional/serving/test_plots_resource.py @@ -21,7 +21,7 @@ pool_size=1, max_trials=10, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, producer={"strategy": "NoParallelStrategy"}, ) diff --git a/tests/functional/serving/test_trials_resource.py b/tests/functional/serving/test_trials_resource.py index 41ed5df4f..23c29be31 100644 --- a/tests/functional/serving/test_trials_resource.py +++ b/tests/functional/serving/test_trials_resource.py @@ -28,7 +28,7 @@ pool_size=1, max_trials=10, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, ) base_trial = { diff --git a/tests/stress/client/stress_experiment.py b/tests/stress/client/stress_experiment.py index 3433e3c40..c9321ed8b 100644 --- a/tests/stress/client/stress_experiment.py +++ b/tests/stress/client/stress_experiment.py @@ -57,7 +57,7 @@ def get_experiment(storage, space_type, size): space={"x": f"uniform(0, {high}, discrete={discrete})"}, max_trials=size, max_idle_time=60 * 5, - algorithms={"random": {"seed": None if space_type == "real" else 1}}, + algorithm={"random": {"seed": None if space_type == "real" else 1}}, storage={"type": "legacy", "database": storage_config}, ) diff --git a/tests/unittests/algo/long/nevergrad/integration_test.py b/tests/unittests/algo/long/nevergrad/integration_test.py index 52182febc..bde227a51 100644 --- a/tests/unittests/algo/long/nevergrad/integration_test.py +++ b/tests/unittests/algo/long/nevergrad/integration_test.py @@ -213,6 +213,11 @@ def merge_dicts(*dicts: dict) -> dict: "RPowell": merge_dicts(_deterministic_first_point, _sequential, _not_serializable), "RSQP": merge_dicts(_deterministic_first_point, _sequential, _not_serializable), "PymooNSGA2": merge_dicts(_no_tell_without_ask, _sequential, _not_parallel), + "NGOpt12": {}, + "NGOpt13": {}, + "NGOpt14": {}, + "NGOpt21": {}, + "NGOpt38": {}, } @@ -231,11 +236,12 @@ def _config(request: FixtureRequest): test_name = request.function.__name__ model_name: str = request.param # type: ignore - model_type = ng.optimizers.registry[model_name] if model_name in NOT_WORKING: pytest.skip(reason=f"Model {model_name} is not supported.") + model_type = ng.optimizers.registry[model_name] + tweaks = MODEL_NAMES[model_name] if model_type.no_parallelization: diff --git a/tests/unittests/algo/test_evolution_es.py b/tests/unittests/algo/test_evolution_es.py index 9ab0eca0c..80805c575 100644 --- a/tests/unittests/algo/test_evolution_es.py +++ b/tests/unittests/algo/test_evolution_es.py @@ -149,7 +149,6 @@ def rung_4(space1: Space): trial_hash = trial.compute_trial_hash( trial, ignore_fidelity=True, - ignore_experiment=True, ) assert trial.objective is not None results[trial_hash] = (trial.objective.value, trial) diff --git a/tests/unittests/benchmark/task/profet/test_profet_task.py b/tests/unittests/benchmark/task/profet/test_profet_task.py index 96276f3ff..13b26ce8c 100644 --- a/tests/unittests/benchmark/task/profet/test_profet_task.py +++ b/tests/unittests/benchmark/task/profet/test_profet_task.py @@ -154,6 +154,7 @@ def test_mock_load_data_fixture_when_real_data_available( assert min_c <= real_c.min() and min_c <= fake_c.min() assert real_c.max() <= max_c and fake_c.max() <= max_c + @pytest.mark.filterwarnings("ignore:Checkpoint file") @pytest.mark.timeout(30) @pytest.mark.parametrize("device_str", _devices) def test_attributes( diff --git a/tests/unittests/client/test_client.py b/tests/unittests/client/test_client.py index 407a9d387..4d2b7bf24 100644 --- a/tests/unittests/client/test_client.py +++ b/tests/unittests/client/test_client.py @@ -47,7 +47,7 @@ max_trials=10, max_broken=5, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, refers=dict(root_id="supernaekei", parent_id=None, adapter=[]), ) @@ -208,8 +208,8 @@ def test_create_experiment_new_default(self): assert experiment.max_trials == orion.core.config.experiment.max_trials assert experiment.max_broken == orion.core.config.experiment.max_broken assert experiment.working_dir == orion.core.config.experiment.working_dir - assert experiment.algorithms - assert experiment.algorithms.configuration == {"random": {"seed": None}} + assert experiment.algorithm + assert experiment.algorithm.configuration == {"random": {"seed": None}} def test_create_experiment_new_full_config(self, user_config): """Test creating a new experiment by specifying all attributes.""" @@ -222,7 +222,7 @@ def test_create_experiment_new_full_config(self, user_config): assert exp_config["max_trials"] == config["max_trials"] assert exp_config["max_broken"] == config["max_broken"] assert exp_config["working_dir"] == config["working_dir"] - assert exp_config["algorithms"] == config["algorithms"] + assert exp_config["algorithm"] == config["algorithm"] def test_create_experiment_hit_no_branch(self, user_config): """Test creating an existing experiment by specifying all identical attributes.""" @@ -237,7 +237,7 @@ def test_create_experiment_hit_no_branch(self, user_config): assert exp_config["max_trials"] == config["max_trials"] assert exp_config["max_broken"] == config["max_broken"] assert exp_config["working_dir"] == config["working_dir"] - assert exp_config["algorithms"] == config["algorithms"] + assert exp_config["algorithm"] == config["algorithm"] def test_create_experiment_hit_no_config(self): """Test creating an existing experiment by specifying the name only.""" @@ -247,8 +247,8 @@ def test_create_experiment_hit_no_config(self): assert experiment.name == config["name"] assert experiment.version == 1 assert experiment.space.configuration == config["space"] - assert experiment.algorithms - assert experiment.algorithms.configuration == config["algorithms"] + assert experiment.algorithm + assert experiment.algorithm.configuration == config["algorithm"] assert experiment.max_trials == config["max_trials"] assert experiment.max_broken == config["max_broken"] assert experiment.working_dir == config["working_dir"] @@ -265,8 +265,8 @@ def test_create_experiment_hit_branch(self): assert experiment.name == config["name"] assert experiment.version == 2 - assert experiment.algorithms - assert experiment.algorithms.configuration == config["algorithms"] + assert experiment.algorithm + assert experiment.algorithm.configuration == config["algorithm"] assert experiment.max_trials == config["max_trials"] assert experiment.max_broken == config["max_broken"] assert experiment.working_dir == config["working_dir"] @@ -439,10 +439,10 @@ def foo(x): foo, space={"x": "uniform(0, 10)"}, max_trials=5, - algorithms={"random": {"seed": 5}}, + algorithm={"random": {"seed": 5}}, ) - assert experiment.algorithms - algo = experiment.algorithms.unwrapped + assert experiment.algorithm + algo = experiment.algorithm.unwrapped assert isinstance(algo, Random) assert algo.seed == 5 diff --git a/tests/unittests/client/test_experiment_client.py b/tests/unittests/client/test_experiment_client.py index 83c55cf22..8ffac6d1a 100644 --- a/tests/unittests/client/test_experiment_client.py +++ b/tests/unittests/client/test_experiment_client.py @@ -41,7 +41,7 @@ max_trials=10, max_broken=5, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, producer={"strategy": "NoParallelStrategy"}, refers=dict(root_id="supernaekei", parent_id=None, adapter=[]), ) @@ -817,7 +817,7 @@ def amnesia(num=1): """Suggest a new value and then always suggest the same""" return [format_trials.tuple_to_trial([0], experiment.space)] - monkeypatch.setattr(experiment.algorithms, "suggest", amnesia) + monkeypatch.setattr(experiment.algorithm, "suggest", amnesia) assert len(experiment.fetch_trials()) == 1 @@ -845,7 +845,7 @@ def opt_out(num=1): client, ): - monkeypatch.setattr(experiment.algorithms, "suggest", opt_out) + monkeypatch.setattr(experiment.algorithm, "suggest", opt_out) assert len(experiment.fetch_trials()) == 1 @@ -1234,6 +1234,6 @@ def test_run_experiment_twice(): client.workon(main, max_trials=10) client._experiment.max_trials = 20 - client._experiment.algorithms.algorithm.max_trials = 20 + client._experiment.algorithm.algorithm.max_trials = 20 client.workon(main, max_trials=20) diff --git a/tests/unittests/client/test_runner.py b/tests/unittests/client/test_runner.py index bff136249..1d3d6d609 100644 --- a/tests/unittests/client/test_runner.py +++ b/tests/unittests/client/test_runner.py @@ -567,7 +567,7 @@ def make_runner(n_workers, max_trials_per_worker, pool_size=None): runner.client.close() -def run_runner(reraise=False, executor=None): +def run_runner(reraise=False, executor=None, close_executor=True): try: count = 10 max_trials = 10 @@ -591,7 +591,10 @@ def set_is_done(): thread = Thread(target=set_is_done) thread.start() - with executor: + if close_executor: + with executor: + runner.run() + else: runner.run() print("done") @@ -675,11 +678,13 @@ def run(self): def test_runner_inside_dask(): """Runner can not execute inside a dask worker""" - executor = Dask() + with Dask() as executor: - future = executor.submit(run_runner, executor=executor, reraise=True) + future = executor.submit( + run_runner, executor=executor, reraise=True, close_executor=False + ) - assert future.get() == 0 + assert future.get() == 0 def test_custom_prepare_trial(): diff --git a/tests/unittests/core/cli/test_info.py b/tests/unittests/core/cli/test_info.py index f29c15905..6cb88eef4 100755 --- a/tests/unittests/core/cli/test_info.py +++ b/tests/unittests/core/cli/test_info.py @@ -415,7 +415,7 @@ def test_format_config(monkeypatch): def test_format_algorithm(algorithm_dict): """Test algorithm section formatting""" experiment = DummyExperiment() - experiment.configuration = {"algorithms": algorithm_dict} + experiment.configuration = {"algorithm": algorithm_dict} assert ( format_algorithm(experiment) == """\ @@ -606,7 +606,7 @@ def test_format_info(algorithm_dict, dummy_trial): experiment.max_trials = 100 experiment.max_broken = 5 experiment.working_dir = "working_dir" - experiment.configuration = {"algorithms": algorithm_dict} + experiment.configuration = {"algorithm": algorithm_dict} space = SpaceBuilder().build( {"some": 'choices(["random", "or", "not"])', "command": "uniform(0, 1)"} diff --git a/tests/unittests/core/conftest.py b/tests/unittests/core/conftest.py index 947b8921d..e1a72ef9b 100644 --- a/tests/unittests/core/conftest.py +++ b/tests/unittests/core/conftest.py @@ -177,7 +177,7 @@ def with_user_dendi(monkeypatch): pool_size=1, max_trials=1000, working_dir="", - algorithms={"dumbalgo": {}}, + algorithm={"dumbalgo": {}}, producer={"strategy": "NoParallelStrategy"}, ) @@ -321,7 +321,7 @@ def new_config(): user_script = "tests/functional/demo/black_box.py" config = dict( name="test", - algorithms="fancy", + algorithm="fancy", version=1, metadata={ "VCS": "to be changed", @@ -376,7 +376,7 @@ def old_config(storage): user_script = "tests/functional/demo/black_box.py" config = dict( name="test", - algorithms="random", + algorithm="random", version=1, metadata={ "VCS": { @@ -540,7 +540,7 @@ def bad_exp_parent_config(): "orion_version": "XYZ", }, version=1, - algorithms="random", + algorithm="random", ) backward.populate_space(config) diff --git a/tests/unittests/core/database/test_database.py b/tests/unittests/core/database/test_database.py index 53bd1d66a..9a365a091 100644 --- a/tests/unittests/core/database/test_database.py +++ b/tests/unittests/core/database/test_database.py @@ -163,7 +163,7 @@ def test_read_nothing(self, orion_db): value = orion_db.read( "experiments", {"name": "not_found", "metadata.user": "tsirif"}, - selection={"algorithms": 1}, + selection={"algorithm": 1}, ) assert value == [] diff --git a/tests/unittests/core/evc/test_conflicts.py b/tests/unittests/core/evc/test_conflicts.py index 8054cba94..6444c50c1 100644 --- a/tests/unittests/core/evc/test_conflicts.py +++ b/tests/unittests/core/evc/test_conflicts.py @@ -252,8 +252,8 @@ def test_try_resolve(self, algorithm_conflict): def test_repr(self, old_config, new_config, algorithm_conflict): """Verify the representation of conflict for user interface""" repr_baseline = "{}\n !=\n{}".format( - pprint.pformat(old_config["algorithms"]), - pprint.pformat(new_config["algorithms"]), + pprint.pformat(old_config["algorithm"]), + pprint.pformat(new_config["algorithm"]), ) assert repr(algorithm_conflict) == repr_baseline diff --git a/tests/unittests/core/experiment.yaml b/tests/unittests/core/experiment.yaml index df7073944..333cafd5f 100644 --- a/tests/unittests/core/experiment.yaml +++ b/tests/unittests/core/experiment.yaml @@ -26,7 +26,7 @@ pool_size: 2 max_trials: 1000 working_dir: - algorithms: + algorithm: dumbalgo: # this must be logged as `Experiment` would log it, complete specification seed: null value: 5 @@ -71,9 +71,9 @@ max_trials: 1000 working_dir: - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: dumbalgo: # this must be logged as `Experiment` would log it, complete specification seed: null value: 5 @@ -105,7 +105,7 @@ pool_size: 2 max_trials: 1000 working_dir: - algorithms: + algorithm: dumbalgo: # this must be logged as `Experiment` would log it, complete specification seed: null value: 5 @@ -138,7 +138,7 @@ max_trials: 1 working_dir: status: done - algorithms: + algorithm: dumbalgo: # this must be logged as `Experiment` would log it, complete specification seed: null value: 5 @@ -188,9 +188,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -232,9 +232,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -274,9 +274,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -322,9 +322,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -363,9 +363,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -404,9 +404,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -445,9 +445,9 @@ # functional info status: pending # pending, done, broken - # **complete** specification of algorithms used + # **complete** specification of algorithm used # user's configuration, but defaults are inferred - algorithms: + algorithm: random: seed: null @@ -485,7 +485,7 @@ status: pending # pending, done, broken # TODO hack in fixtures since we can't define another valid algorithm as we only have random. - algorithms: + algorithm: random: seed: null diff --git a/tests/unittests/core/io/orion_config.yaml b/tests/unittests/core/io/orion_config.yaml index a8c0dc1be..d12e04ccb 100644 --- a/tests/unittests/core/io/orion_config.yaml +++ b/tests/unittests/core/io/orion_config.yaml @@ -4,7 +4,7 @@ experiment: max_trials: 100 max_broken: 5 - algorithms: 'random' + algorithm: 'random' database: type: 'pickleddb' diff --git a/tests/unittests/core/io/orion_old_config.yaml b/tests/unittests/core/io/orion_old_config.yaml index 6b761970e..a9b3b64c8 100644 --- a/tests/unittests/core/io/orion_old_config.yaml +++ b/tests/unittests/core/io/orion_old_config.yaml @@ -3,7 +3,7 @@ name: voila_voici pool_size: 2 max_trials: 1000 -algorithms: +algorithm: dumbalgo: done: False judgement: null diff --git a/tests/unittests/core/io/test_experiment_builder.py b/tests/unittests/core/io/test_experiment_builder.py index 5611eecec..9849c4aeb 100644 --- a/tests/unittests/core/io/test_experiment_builder.py +++ b/tests/unittests/core/io/test_experiment_builder.py @@ -66,7 +66,7 @@ def python_api_config(): max_trials=1000, max_broken=5, working_dir="", - algorithms={ + algorithm={ "dumbalgo": { "done": False, "judgement": None, @@ -86,7 +86,7 @@ def python_api_config(): @pytest.fixture() def algo_unavailable_config(python_api_config): - python_api_config["algorithms"] = {"idontreallyexist": {"but": "iwishiwould"}} + python_api_config["algorithm"] = {"idontreallyexist": {"but": "iwishiwould"}} return python_api_config @@ -114,7 +114,7 @@ def new_config(random_dt, script_path): max_trials=1000, max_broken=5, working_dir="", - algorithms={ + algorithm={ "dumbalgo": { "done": False, "judgement": None, @@ -143,7 +143,7 @@ def parent_version_config(script_path): _id="parent_config", name="old_experiment", version=1, - algorithms="random", + algorithm="random", metadata={ "user": "corneauf", "datetime": datetime.datetime.utcnow(), @@ -184,7 +184,7 @@ def test_get_cmd_config(raw_config): cmdargs = {"config": raw_config} local_config = experiment_builder.get_cmd_config(cmdargs) - assert local_config["algorithms"] == "random" + assert local_config["algorithm"] == "random" assert local_config["max_trials"] == 100 assert local_config["max_broken"] == 5 assert local_config["name"] == "voila_voici" @@ -208,7 +208,7 @@ def test_get_cmd_config_from_incomplete_config(incomplete_config_file): cmdargs = {"config": incomplete_config_file} local_config = experiment_builder.get_cmd_config(cmdargs) - assert "algorithms" not in local_config + assert "algorithm" not in local_config assert "max_trials" not in local_config assert "max_broken" not in local_config assert "name" not in local_config["storage"]["database"] @@ -243,7 +243,7 @@ def test_fetch_config_from_db_hit(new_config): assert db_config["metadata"] == new_config["metadata"] assert db_config["max_trials"] == new_config["max_trials"] assert db_config["max_broken"] == new_config["max_broken"] - assert db_config["algorithms"] == new_config["algorithms"] + assert db_config["algorithm"] == new_config["algorithm"] assert db_config["metadata"] == new_config["metadata"] @@ -281,8 +281,8 @@ def get_storage(*args, **kwargs): assert exp_view.metadata == new_config["metadata"] assert exp_view.max_trials == new_config["max_trials"] assert exp_view.max_broken == new_config["max_broken"] - assert exp_view.algorithms - assert exp_view.algorithms.configuration == new_config["algorithms"] + assert exp_view.algorithm + assert exp_view.algorithm.configuration == new_config["algorithm"] @pytest.mark.usefixtures("with_user_tsirif") @@ -315,8 +315,8 @@ def get_storage(*args, **kwargs): assert exp_view.metadata == new_config["metadata"] assert exp_view.max_trials == new_config["max_trials"] assert exp_view.max_broken == new_config["max_broken"] - assert exp_view.algorithms - assert exp_view.algorithms.configuration == new_config["algorithms"] + assert exp_view.algorithm + assert exp_view.algorithm.configuration == new_config["algorithm"] @pytest.mark.usefixtures("with_user_dendi") @@ -358,8 +358,8 @@ def get_storage(*args, **kwargs): assert exp.metadata["user_args"] == cmdargs["user_args"] assert exp.max_trials == 100 assert exp.max_broken == 5 - assert exp.algorithms - assert exp.algorithms.configuration == {"random": {"seed": None}} + assert exp.algorithm + assert exp.algorithm.configuration == {"random": {"seed": None}} @pytest.mark.usefixtures( @@ -394,8 +394,8 @@ def get_storage(*args, **kwargs): assert exp.metadata == new_config["metadata"] assert exp.max_trials == new_config["max_trials"] assert exp.max_broken == new_config["max_broken"] - assert exp.algorithms - assert exp.algorithms.configuration == new_config["algorithms"] + assert exp.algorithm + assert exp.algorithm.configuration == new_config["algorithm"] @pytest.mark.usefixtures("with_user_bouthilx") @@ -500,8 +500,8 @@ def test_build_no_hit(config_file, random_dt, script_path): assert exp.max_trials == max_trials assert exp.max_broken == max_broken assert not exp.is_done - assert exp.algorithms - assert exp.algorithms.configuration == {"random": {"seed": None}} + assert exp.algorithm + assert exp.algorithm.configuration == {"random": {"seed": None}} def test_build_no_commandline_config(): @@ -531,8 +531,8 @@ def test_build_hit(python_api_config): assert exp.metadata == python_api_config["metadata"] assert exp.max_trials == python_api_config["max_trials"] assert exp.max_broken == python_api_config["max_broken"] - assert exp.algorithms - assert exp.algorithms.configuration == python_api_config["algorithms"] + assert exp.algorithm + assert exp.algorithm.configuration == python_api_config["algorithm"] @pytest.mark.usefixtures("with_user_tsirif", "version_XYZ") @@ -553,8 +553,8 @@ def test_build_without_config_hit(python_api_config): assert exp.metadata == python_api_config["metadata"] assert exp.max_trials == python_api_config["max_trials"] assert exp.max_broken == python_api_config["max_broken"] - assert exp.algorithms - assert exp.algorithms.configuration == python_api_config["algorithms"] + assert exp.algorithm + assert exp.algorithm.configuration == python_api_config["algorithm"] @pytest.mark.usefixtures( @@ -588,8 +588,8 @@ def get_storage(*args, **kwargs): assert exp.metadata == new_config["metadata"] assert exp.max_trials == new_config["max_trials"] assert exp.max_broken == new_config["max_broken"] - assert exp.algorithms - assert exp.algorithms.configuration == new_config["algorithms"] + assert exp.algorithm + assert exp.algorithm.configuration == new_config["algorithm"] # TODO: Remove for v0.4 @@ -664,18 +664,18 @@ def test_experiment_overwritten_evc_disabled(self, parent_version_config, caplog assert "Running experiment in a different state" not in caplog.text assert exp.version == 1 - assert exp.configuration["algorithms"] == {"random": {"seed": None}} + assert exp.configuration["algorithm"] == {"random": {"seed": None}} new_algo = "gridsearch" with caplog.at_level(logging.WARNING): exp = experiment_builder.build( - name=parent_version_config["name"], algorithms=new_algo + name=parent_version_config["name"], algorithm=new_algo ) assert "Running experiment in a different state" in caplog.text assert exp.version == 1 - assert list(exp.configuration["algorithms"].keys())[0] == new_algo + assert list(exp.configuration["algorithm"].keys())[0] == new_algo caplog.clear() with caplog.at_level(logging.WARNING): @@ -684,7 +684,7 @@ def test_experiment_overwritten_evc_disabled(self, parent_version_config, caplog assert "Running experiment in a different state" not in caplog.text assert exp.version == 1 - assert list(exp.configuration["algorithms"].keys())[0] == new_algo + assert list(exp.configuration["algorithm"].keys())[0] == new_algo def test_backward_compatibility_no_version(self, parent_version_config): """Branch from parent that has no version field.""" @@ -759,12 +759,12 @@ def test_good_set_before_init_hit_no_diffs_exc_max_trials(self, new_config): exp = experiment_builder.build(**new_config) # Deliver an external configuration to finalize init - new_config["algorithms"]["dumbalgo"]["done"] = False - new_config["algorithms"]["dumbalgo"]["judgement"] = None - new_config["algorithms"]["dumbalgo"]["scoring"] = 0 - new_config["algorithms"]["dumbalgo"]["suspend"] = False - new_config["algorithms"]["dumbalgo"]["value"] = 5 - new_config["algorithms"]["dumbalgo"]["seed"] = None + new_config["algorithm"]["dumbalgo"]["done"] = False + new_config["algorithm"]["dumbalgo"]["judgement"] = None + new_config["algorithm"]["dumbalgo"]["scoring"] = 0 + new_config["algorithm"]["dumbalgo"]["suspend"] = False + new_config["algorithm"]["dumbalgo"]["value"] = 5 + new_config["algorithm"]["dumbalgo"]["seed"] = None new_config.pop("something_to_be_ignored") new_config["knowledge_base"] = None assert exp.configuration == new_config @@ -789,12 +789,12 @@ def test_good_set_before_init_no_hit(self, random_dt, new_config): new_config["refers"] = {} new_config.pop("_id") new_config.pop("something_to_be_ignored") - new_config["algorithms"]["dumbalgo"]["done"] = False - new_config["algorithms"]["dumbalgo"]["judgement"] = None - new_config["algorithms"]["dumbalgo"]["scoring"] = 0 - new_config["algorithms"]["dumbalgo"]["suspend"] = False - new_config["algorithms"]["dumbalgo"]["value"] = 5 - new_config["algorithms"]["dumbalgo"]["seed"] = None + new_config["algorithm"]["dumbalgo"]["done"] = False + new_config["algorithm"]["dumbalgo"]["judgement"] = None + new_config["algorithm"]["dumbalgo"]["scoring"] = 0 + new_config["algorithm"]["dumbalgo"]["suspend"] = False + new_config["algorithm"]["dumbalgo"]["value"] = 5 + new_config["algorithm"]["dumbalgo"]["seed"] = None new_config["refers"] = {"adapter": [], "parent_id": None, "root_id": _id} new_config["knowledge_base"] = None assert found_config[0] == new_config @@ -805,8 +805,8 @@ def test_good_set_before_init_no_hit(self, random_dt, new_config): assert exp.max_broken == new_config["max_broken"] assert exp.working_dir == new_config["working_dir"] assert exp.version == new_config["version"] - assert exp.algorithms - assert exp.algorithms.configuration == new_config["algorithms"] + assert exp.algorithm + assert exp.algorithm.configuration == new_config["algorithm"] def test_working_dir_is_correctly_set(self, new_config): """Check if working_dir is correctly changed.""" @@ -852,12 +852,12 @@ def test_configuration_hit_no_diffs(self, new_config): exp = experiment_builder.build(**new_config) assert experiment_count_before == count_experiments() - new_config["algorithms"]["dumbalgo"]["done"] = False - new_config["algorithms"]["dumbalgo"]["judgement"] = None - new_config["algorithms"]["dumbalgo"]["scoring"] = 0 - new_config["algorithms"]["dumbalgo"]["suspend"] = False - new_config["algorithms"]["dumbalgo"]["value"] = 5 - new_config["algorithms"]["dumbalgo"]["seed"] = None + new_config["algorithm"]["dumbalgo"]["done"] = False + new_config["algorithm"]["dumbalgo"]["judgement"] = None + new_config["algorithm"]["dumbalgo"]["scoring"] = 0 + new_config["algorithm"]["dumbalgo"]["suspend"] = False + new_config["algorithm"]["dumbalgo"]["value"] = 5 + new_config["algorithm"]["dumbalgo"]["seed"] = None new_config.pop("something_to_be_ignored") new_config["knowledge_base"] = None assert exp.configuration == new_config @@ -867,7 +867,7 @@ def test_instantiation_after_init(self, new_config): with OrionState(experiments=[new_config], trials=[]): exp = experiment_builder.build(**new_config) - assert isinstance(exp.algorithms, AlgoWrapper) + assert isinstance(exp.algorithm, AlgoWrapper) assert isinstance(exp.space, Space) assert isinstance(exp.refers["adapter"], BaseAdapter) @@ -875,7 +875,7 @@ def test_instantiation_after_init(self, new_config): def test_algo_case_insensitive(self, new_config): """Verify that algo with uppercase or lowercase leads to same experiment""" with OrionState(experiments=[new_config], trials=[]): - new_config["algorithms"]["DUMBALGO"] = new_config["algorithms"].pop( + new_config["algorithm"]["DUMBALGO"] = new_config["algorithm"].pop( "dumbalgo" ) exp = experiment_builder.build(**new_config) @@ -944,14 +944,12 @@ def test_algorithm_config_with_just_a_string(self): """Test that configuring an algorithm with just a string is OK.""" name = "supernaedo3" space = {"x": "uniform(0,10)"} - algorithms = "dumbalgo" + algorithm = "dumbalgo" with OrionState(experiments=[], trials=[]): - exp = experiment_builder.build( - name=name, space=space, algorithms=algorithms - ) + exp = experiment_builder.build(name=name, space=space, algorithm=algorithm) - assert exp.configuration["algorithms"] == { + assert exp.configuration["algorithm"] == { "dumbalgo": { "done": False, "judgement": None, @@ -1355,17 +1353,17 @@ def test_with_dict( def test_load_unavailable_algo(algo_unavailable_config, capsys): with OrionState(experiments=[algo_unavailable_config]): experiment = experiment_builder.load("supernaekei", mode="r") - assert experiment.algorithms == algo_unavailable_config["algorithms"] + assert experiment.algorithm == algo_unavailable_config["algorithm"] assert ( - experiment.configuration["algorithms"] - == algo_unavailable_config["algorithms"] + experiment.configuration["algorithm"] + == algo_unavailable_config["algorithm"] ) experiment = experiment_builder.load("supernaekei", mode="w") - assert experiment.algorithms == algo_unavailable_config["algorithms"] + assert experiment.algorithm == algo_unavailable_config["algorithm"] assert ( - experiment.configuration["algorithms"] - == algo_unavailable_config["algorithms"] + experiment.configuration["algorithm"] + == algo_unavailable_config["algorithm"] ) with pytest.raises( diff --git a/tests/unittests/core/io/test_resolve_config.py b/tests/unittests/core/io/test_resolve_config.py index d451b885a..453fd86a0 100644 --- a/tests/unittests/core/io/test_resolve_config.py +++ b/tests/unittests/core/io/test_resolve_config.py @@ -220,7 +220,7 @@ def test_fetch_config(raw_config): "max_trials": 100, "max_broken": 5, "name": "voila_voici", - "algorithms": "random", + "algorithm": "random", } assert config == {} @@ -256,6 +256,8 @@ def mocked_config(file_object): assert exp_config.pop("max_trials") == orion.core.config.experiment.max_trials assert exp_config.pop("max_broken") == orion.core.config.experiment.max_broken assert exp_config.pop("working_dir") == orion.core.config.experiment.working_dir + assert exp_config.pop("algorithm") == orion.core.config.experiment.algorithm + # TODO: Remove for v0.4 assert exp_config.pop("algorithms") == orion.core.config.experiment.algorithms # TODO: Remove for v0.4 assert exp_config.pop("strategy") == orion.core.config.experiment.strategy @@ -327,14 +329,14 @@ def test_fetch_config_dash(monkeypatch, config_file): """Verify fetch_config supports dash.""" def mocked_config(file_object): - return {"experiment": {"max-broken": 10, "algorithms": {"dont-change": "me"}}} + return {"experiment": {"max-broken": 10, "algorithm": {"dont-change": "me"}}} monkeypatch.setattr("yaml.safe_load", mocked_config) config = resolve_config.fetch_config({"config": config_file}) assert config == { - "experiment": {"max_broken": 10, "algorithms": {"dont-change": "me"}} + "experiment": {"max_broken": 10, "algorithm": {"dont-change": "me"}} } @@ -342,14 +344,14 @@ def test_fetch_config_underscore(monkeypatch, config_file): """Verify fetch_config supports underscore as well.""" def mocked_config(file_object): - return {"experiment": {"max_broken": 10, "algorithms": {"dont-change": "me"}}} + return {"experiment": {"max_broken": 10, "algorithm": {"dont-change": "me"}}} monkeypatch.setattr("yaml.safe_load", mocked_config) config = resolve_config.fetch_config({"config": config_file}) assert config == { - "experiment": {"max_broken": 10, "algorithms": {"dont-change": "me"}} + "experiment": {"max_broken": 10, "algorithm": {"dont-change": "me"}} } diff --git a/tests/unittests/core/test_branch_config.py b/tests/unittests/core/test_branch_config.py index 626e8ba1c..b4da1d909 100644 --- a/tests/unittests/core/test_branch_config.py +++ b/tests/unittests/core/test_branch_config.py @@ -54,7 +54,7 @@ def parent_config(user_config): config = dict( _id="test", name="test", - algorithms="random", + algorithm="random", version=1, metadata={ "VCS": { @@ -132,7 +132,7 @@ def changed_config(child_config): @pytest.fixture def changed_algo_config(child_config): """Create a child config with a new algo""" - child_config["algorithms"] = "stupid-grid" + child_config["algorithm"] = "stupid-grid" return child_config @@ -201,7 +201,7 @@ def cl_config(): config = dict( name="test", branch="test2", - algorithms="random", + algorithm="random", metadata={ "hash_commit": "old", "user_script": user_script, @@ -312,8 +312,8 @@ def test_algo_conflict(self, parent_config, changed_algo_config): conflict = conflicts.get()[0] assert conflict.is_resolved is False - assert conflict.old_config["algorithms"] == "random" - assert conflict.new_config["algorithms"] == "stupid-grid" + assert conflict.old_config["algorithm"] == "random" + assert conflict.new_config["algorithm"] == "stupid-grid" assert isinstance(conflict, AlgorithmConflict) def test_orion_version_conflict(self, parent_config, changed_orion_version_config): diff --git a/tests/unittests/core/worker/test_experiment.py b/tests/unittests/core/worker/test_experiment.py index a03661896..4b62a9c28 100644 --- a/tests/unittests/core/worker/test_experiment.py +++ b/tests/unittests/core/worker/test_experiment.py @@ -56,7 +56,7 @@ def new_config(random_dt, algorithm): max_trials=1000, max_broken=5, working_dir=None, - algorithms=algorithm.configuration, + algorithm=algorithm.configuration, # attrs starting with '_' also # _id='fasdfasfa', # and in general anything which is not in Experiment's slots @@ -75,7 +75,7 @@ def parent_version_config(): _id="parent_config", name="old_experiment", version=1, - algorithms="random", + algorithm="random", metadata={ "user": "corneauf", "datetime": datetime.datetime.utcnow(), @@ -323,7 +323,7 @@ def test_acquire_algorithm_lock_successful( with OrionState(experiments=[new_config]) as cfg: exp = Experiment("supernaekei", mode="x", storage=cfg.storage, space=space) exp._id = 0 - exp.algorithms = algorithm + exp.algorithm = algorithm state_dict = algorithm.state_dict # No state_dict in DB @@ -351,7 +351,7 @@ def test_acquire_algorithm_lock_with_different_config( exp = Experiment("supernaekei", mode="x", storage=cfg.storage, space=space) exp._id = 0 algorithm_original_config = algorithm.configuration - exp.algorithms = algorithm + exp.algorithm = algorithm # Setting attribute to algorithm inside the wrapper algorithm.unwrapped.seed = 10 @@ -369,7 +369,7 @@ def test_acquire_algorithm_lock_timeout( with OrionState(experiments=[new_config]) as cfg: exp = Experiment("supernaekei", mode="x", storage=cfg.storage, space=space) exp._id = 0 - exp.algorithms = algorithm + exp.algorithm = algorithm storage_acquisition_mock = mocker.spy(cfg.storage, "acquire_algorithm_lock") @@ -531,7 +531,7 @@ def test_is_done_property_with_pending(algorithm, space: Space): exp = Experiment("supernaekei", mode="x", storage=cfg.storage, space=space) exp._id = cfg.trials[0]["experiment"] - exp.algorithms = algorithm + exp.algorithm = algorithm exp.max_trials = 10 assert exp.is_done @@ -541,7 +541,7 @@ def test_is_done_property_with_pending(algorithm, space: Space): # There is only 10 completed trials assert not exp.is_done - exp.algorithms.algorithm.done = True + exp.algorithm.algorithm.done = True # Algorithm is done but 5 trials are pending assert not exp.is_done @@ -555,14 +555,14 @@ def test_is_done_property_no_pending(algorithm, space: Space): exp = Experiment("supernaekei", mode="x", storage=cfg.storage, space=space) exp._id = cfg.trials[0]["experiment"] - exp.algorithms = algorithm + exp.algorithm = algorithm exp.max_trials = 15 # There is only 10 completed trials and algo not done. assert not exp.is_done - exp.algorithms.unwrapped.done = True + exp.algorithm.unwrapped.done = True # Algorithm is done and no pending trials assert exp.is_done @@ -853,7 +853,7 @@ def test_experiment_pickleable(space: Space): read_only_methods = [ - "algorithms", + "algorithm", "configuration", "fetch_lost_trials", "fetch_pending_trials", @@ -959,7 +959,7 @@ def create_experiment(mode: Mode, space: Space, algorithm, storage): "supernaekei", mode=mode, space=space, - algorithms=algorithm, + algorithm=algorithm, max_broken=5, max_trials=5, storage=storage, @@ -972,8 +972,8 @@ def disable_algo_lock(monkeypatch, storage): @contextlib.contextmanager def no_lock(experiment, timeout, retry_interval): yield LockedAlgorithmState( - state=experiment.algorithms.state_dict, - configuration=experiment.algorithms.configuration, + state=experiment.algorithm.state_dict, + configuration=experiment.algorithm.configuration, ) monkeypatch.setattr(storage, "acquire_algorithm_lock", no_lock) diff --git a/tests/unittests/core/worker/test_producer.py b/tests/unittests/core/worker/test_producer.py index 454c422cb..1f8f95e6f 100644 --- a/tests/unittests/core/worker/test_producer.py +++ b/tests/unittests/core/worker/test_producer.py @@ -34,7 +34,7 @@ def update_algorithm(producer): "datetime": "2017-11-23T02:00:00", "orion_version": "XYZ", }, - "algorithms": { + "algorithm": { "dumbalgo": { "value": (5,), "scoring": 0, @@ -60,10 +60,10 @@ def create_producer(): ) as cfg: experiment = cfg.get_experiment(name="default_name") - experiment.algorithms.algorithm.possible_values = [(v,) for v in range(0, 11)] - experiment.algorithms.seed_rng(0) + experiment.algorithm.algorithm.possible_values = [(v,) for v in range(0, 11)] + experiment.algorithm.seed_rng(0) experiment.max_trials = 20 - experiment.algorithms.algorithm.max_trials = 20 + experiment.algorithm.algorithm.max_trials = 20 producer = Producer(experiment) yield producer, cfg.storage @@ -72,7 +72,7 @@ def create_producer(): def test_produce(): """Test new trials are properly produced""" with create_producer() as (producer, _): - algorithm = producer.experiment.algorithms + algorithm = producer.experiment.algorithm possible_values = [(1,)] algorithm.unwrapped.possible_values = possible_values @@ -91,7 +91,7 @@ def test_register_new_trials(): trials_in_db_before = len(storage._fetch_trials({})) new_trials_in_db_before = len(storage._fetch_trials({"status": "new"})) - algorithm = producer.experiment.algorithms + algorithm = producer.experiment.algorithm possible_values = [(1,)] algorithm.unwrapped.possible_values = possible_values @@ -125,9 +125,9 @@ def test_concurent_producers(monkeypatch): trials_in_db_before = len(storage._fetch_trials({})) new_trials_in_db_before = len(storage._fetch_trials({"status": "new"})) - producer.experiment.algorithms.algorithm.possible_values = [(1,)] + producer.experiment.algorithm.algorithm.possible_values = [(1,)] # Make sure it starts from index 0 - producer.experiment.algorithms.seed_rng(0) + producer.experiment.algorithm.seed_rng(0) second_producer = Producer(producer.experiment) second_producer.experiment = copy.deepcopy(producer.experiment) @@ -136,11 +136,9 @@ def test_concurent_producers(monkeypatch): def suggest(self, num): time.sleep(sleep) - return producer.experiment.algorithms.algorithm.possible_values[0] + return producer.experiment.algorithm.algorithm.possible_values[0] - monkeypatch.setattr( - producer.experiment.algorithms.algorithm, "suggest", suggest - ) + monkeypatch.setattr(producer.experiment.algorithm.algorithm, "suggest", suggest) pool = threading.Pool(2) first_result = pool.apply_async(producer.produce) @@ -185,16 +183,16 @@ def test_duplicate_within_pool(): new_trials_in_db_before = len(storage._fetch_trials({"status": "new"})) # Avoid limiting number of samples from the within the algorithm. - producer.experiment.algorithms.unwrapped.pool_size = 1000 + producer.experiment.algorithm.unwrapped.pool_size = 1000 - producer.experiment.algorithms.unwrapped.possible_values = [ + producer.experiment.algorithm.unwrapped.possible_values = [ (v,) for v in [1, 1, 3] ] assert producer.produce(3) == 2 # Algorithm was required to suggest some trials - num_new_trials = producer.experiment.algorithms.unwrapped._num + num_new_trials = producer.experiment.algorithm.unwrapped._num assert num_new_trials == 3 # pool size # `num_new_trials` new trials were registered at database @@ -220,16 +218,16 @@ def test_duplicate_within_pool_and_db(): new_trials_in_db_before = len(storage._fetch_trials({"status": "new"})) # Avoid limiting number of samples from the within the algorithm. - producer.experiment.algorithms.unwrapped.pool_size = 1000 + producer.experiment.algorithm.unwrapped.pool_size = 1000 - producer.experiment.algorithms.unwrapped.possible_values = [ + producer.experiment.algorithm.unwrapped.possible_values = [ (v,) for v in [0, 1, 2] ] assert producer.produce(3) == 1 # Algorithm was required to suggest some trials - num_new_trials = producer.experiment.algorithms.unwrapped._num + num_new_trials = producer.experiment.algorithm.unwrapped._num assert num_new_trials == 3 # pool size # `num_new_trials` new trials were registered at database @@ -250,7 +248,7 @@ def test_evc(monkeypatch, producer): """Verify that producer is using available trials from EVC""" experiment = producer.experiment new_experiment = build( - experiment.name, algorithms="random", branching={"enable": True} + experiment.name, algorithm="random", branching={"enable": True} ) # Replace parent with hacked exp, otherwise parent ID does not match trials in DB @@ -276,7 +274,7 @@ def test_evc_duplicates(monkeypatch, producer): """Verify that producer won't register samples that are available in parent experiment""" experiment = producer.experiment new_experiment = build( - experiment.name, algorithms="random", branching={"enable": True} + experiment.name, algorithm="random", branching={"enable": True} ) # Replace parent with hacked exp, otherwise parent ID does not match trials in DB @@ -296,9 +294,9 @@ def suggest(pool_size=None): return suggest_trials producer.experiment = new_experiment - producer.algorithm = new_experiment.algorithms + producer.algorithm = new_experiment.algorithm - monkeypatch.setattr(new_experiment.algorithms, "suggest", suggest) + monkeypatch.setattr(new_experiment.algorithm, "suggest", suggest) producer.update() producer.produce(len(trials) + 2) diff --git a/tests/unittests/core/worker/warm_start/test_multi_task.py b/tests/unittests/core/worker/warm_start/test_multi_task.py index 4b5aa26e5..6f2b43683 100644 --- a/tests/unittests/core/worker/warm_start/test_multi_task.py +++ b/tests/unittests/core/worker/warm_start/test_multi_task.py @@ -213,10 +213,10 @@ def test_passing_algo(self, algo: type[BaseAlgorithm], how_to_pass_algo: type): source_experiment = build_experiment( name="foo", space={"x": "uniform(0, 1)"}, - algorithms=algo_config, + algorithm=algo_config, debug=True, ) - assert isinstance(source_experiment.algorithms.unwrapped, algo) + assert isinstance(source_experiment.algorithm.unwrapped, algo) class TestMultiTaskWrapper: @@ -329,7 +329,7 @@ def test_adds_task_id(self, knowledge_base: KnowledgeBase): name="target_1", space=target_space, knowledge_base=knowledge_base, - algorithms=algo_type, + algorithm=algo_type, max_trials=max_trials, debug=True, ) @@ -340,7 +340,7 @@ def test_adds_task_id(self, knowledge_base: KnowledgeBase): # IDEA: Add type hints to the `Experiment` class, and make it generic in terms of the # algorithms, so that we could get the `algorithms` to be algo_type here. - algo = experiment.algorithms + algo = experiment.algorithm assert isinstance(algo, MultiTaskWrapper) assert isinstance(algo.unwrapped, algo_type) assert experiment.fetch_trials() == [] @@ -365,12 +365,12 @@ def test_with_warmstarteable_algo(self, knowledge_base: DummyKnowledgeBase): name="target_2", space=target_space, knowledge_base=knowledge_base, - algorithms=DummyWarmStarteableAlgo, + algorithm=DummyWarmStarteableAlgo, max_trials=100, debug=True, ) assert isinstance(experiment._experiment.knowledge_base, KnowledgeBase) - algo = experiment.algorithms + algo = experiment.algorithm assert algo is not None assert isinstance(algo.unwrapped, DummyWarmStarteableAlgo) assert not algo.unwrapped.warm_start_trials @@ -381,7 +381,7 @@ def test_with_warmstarteable_algo(self, knowledge_base: DummyKnowledgeBase): trial_with_result = _add_result(trial, objective) experiment.observe(trial_with_result, []) - algo = experiment.algorithms + algo = experiment.algorithm # IDEA: Add type hints to the `Experiment` class, and make it generic in terms of the # algorithms, so that we could get the `algorithms` to be `Random` here. diff --git a/tests/unittests/plotting/test_plot_accessor.py b/tests/unittests/plotting/test_plot_accessor.py index f89d41ba6..3591afb6a 100644 --- a/tests/unittests/plotting/test_plot_accessor.py +++ b/tests/unittests/plotting/test_plot_accessor.py @@ -30,7 +30,7 @@ pool_size=1, max_trials=10, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, ) diff --git a/tests/unittests/plotting/test_plotly_backend.py b/tests/unittests/plotting/test_plotly_backend.py index d8128b2aa..75792bbeb 100644 --- a/tests/unittests/plotting/test_plotly_backend.py +++ b/tests/unittests/plotting/test_plotly_backend.py @@ -51,7 +51,7 @@ pool_size=1, max_trials=10, working_dir="", - algorithms={"random": {"seed": 1}}, + algorithm={"random": {"seed": 1}}, ) trial_config = { diff --git a/tests/unittests/storage/test_storage.py b/tests/unittests/storage/test_storage.py index fb70cc0a8..b0756cdad 100644 --- a/tests/unittests/storage/test_storage.py +++ b/tests/unittests/storage/test_storage.py @@ -772,7 +772,7 @@ def test_get_algorithm_lock_info(self, storage): algo_state_lock = storage.get_algorithm_lock_info(uid=experiments[0]["_id"]) assert isinstance(algo_state_lock, LockedAlgorithmState) assert algo_state_lock.state is None - assert algo_state_lock.configuration == experiments[0]["algorithms"] + assert algo_state_lock.configuration == experiments[0]["algorithm"] def test_delete_algorithm_lock(self, storage): if storage and storage["type"] == "track": diff --git a/tox.ini b/tox.ini index a2b5c0db7..adf50580c 100644 --- a/tox.ini +++ b/tox.ini @@ -279,8 +279,6 @@ commands = addopts = -ra -q --color=yes norecursedirs = .* *.egg* config docs dist build xfail_strict = True -rsyncdirs = src tests -looponfailroots = src tests examples # Coverage configuration [coverage:run]