diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a73debe69d23..c7ebc2a8a261 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,6 @@ /superset/migrations/ @apache/superset-committers # Notify Preset team when ephemeral env settings are changed -.github/workflows/ecs-task-definition.json @robdiciuccio @craig-rueda @benjreinhart -.github/workflows/docker-ephemeral-env.yml @robdiciuccio @craig-rueda @benjreinhart -.github/workflows/ephemeral*.yml @robdiciuccio @craig-rueda @benjreinhart +.github/workflows/ecs-task-definition.json @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch +.github/workflows/docker-ephemeral-env.yml @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch +.github/workflows/ephemeral*.yml @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f57658a6e071..12fee9c96759 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -33,6 +33,7 @@ If applicable, add screenshots to help explain your problem. - superset version: `superset version` - python version: `python --version` - node.js version: `node -v` +- any feature flags active: ### Checklist diff --git a/.github/ISSUE_TEMPLATE/sip.md b/.github/ISSUE_TEMPLATE/sip.md index d6a668f7a34b..6c526d6d1fa0 100644 --- a/.github/ISSUE_TEMPLATE/sip.md +++ b/.github/ISSUE_TEMPLATE/sip.md @@ -6,9 +6,9 @@ labels: "#SIP" --- *Please make sure you are familiar with the SIP process documented* -(here)[https://github.com/apache/superset/issues/5602] +(here)[https://github.com/apache/superset/issues/5602]. The SIP number should be the next number after the latest SIP listed [here](https://github.com/apache/superset/issues?q=is%3Aissue+label%3Asip). -## [SIP] Proposal for XXX +## [SIP-\] Proposal for ### Motivation diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c5e50829ce50..2558dcd3e190 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,6 +11,7 @@ <!--- Check any relevant boxes with "x" --> <!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue --> - [ ] Has associated issue: +- [ ] Required feature flags: - [ ] Changes UI - [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351)) - [ ] Migration is atomic, supports rollback & is backwards-compatible diff --git a/.github/workflows/bashlib.sh b/.github/workflows/bashlib.sh index ff162de88950..bcb3b78e73b2 100644 --- a/.github/workflows/bashlib.sh +++ b/.github/workflows/bashlib.sh @@ -192,7 +192,7 @@ cypress-run-all() { say "::endgroup::" # Rerun SQL Lab tests with backend persist enabled - export SUPERSET_CONFIG=tests.superset_test_config_sqllab_backend_persist + export SUPERSET_CONFIG=tests.integration_tests.superset_test_config_sqllab_backend_persist # Restart Flask with new configs kill $flaskProcessId diff --git a/.github/workflows/ephemeral-env.yml b/.github/workflows/ephemeral-env.yml index 9c9aa230c4c9..c1945b3c2091 100644 --- a/.github/workflows/ephemeral-env.yml +++ b/.github/workflows/ephemeral-env.yml @@ -17,7 +17,6 @@ jobs: - name: Debug run: | echo "Comment on PR #${{ github.event.issue.number }} by ${{ github.event.issue.user.login }}, ${{ github.event.comment.author_association }}" - echo "Comment body: ${{ github.event.comment.body }}" - name: Eval comment body for /testenv slash command uses: actions/github-script@v3 diff --git a/.github/workflows/superset-e2e.yml b/.github/workflows/superset-e2e.yml index 13564d51c2f1..3b3898b52091 100644 --- a/.github/workflows/superset-e2e.yml +++ b/.github/workflows/superset-e2e.yml @@ -25,7 +25,7 @@ jobs: env: FLASK_ENV: development ENABLE_REACT_CRUD_VIEWS: true - SUPERSET_CONFIG: tests.superset_test_config + SUPERSET_CONFIG: tests.integration_tests.superset_test_config SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset PYTHONPATH: ${{ github.workspace }} REDIS_PORT: 16379 diff --git a/.github/workflows/superset-python-integrationtest.yml b/.github/workflows/superset-python-integrationtest.yml new file mode 100644 index 000000000000..c006d22424ce --- /dev/null +++ b/.github/workflows/superset-python-integrationtest.yml @@ -0,0 +1,193 @@ +# Python integration tests +name: Python-Integration + +on: + push: + branches-ignore: + - "dependabot/npm_and_yarn/**" + pull_request: + types: [synchronize, opened, reopened, ready_for_review] + +jobs: + test-mysql: + if: github.event.pull_request.draft == false + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.7] + env: + PYTHONPATH: ${{ github.workspace }} + SUPERSET_CONFIG: tests.integration_tests.superset_test_config + REDIS_PORT: 16379 + SUPERSET__SQLALCHEMY_DATABASE_URI: | + mysql+mysqldb://superset:superset@127.0.0.1:13306/superset?charset=utf8mb4&binary_prefix=true + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - 13306:3306 + redis: + image: redis:5-alpine + options: --entrypoint redis-server + ports: + - 16379:6379 + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v2 + with: + persist-credentials: false + submodules: recursive + - name: Check if python changes are present + id: check + env: + GITHUB_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + continue-on-error: true + run: ./scripts/ci_check_no_file_changes.sh python + - name: Setup Python + if: steps.check.outcome == 'failure' + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + if: steps.check.outcome == 'failure' + uses: ./.github/actions/cached-dependencies + with: + run: | + apt-get-install + pip-upgrade + pip install -r requirements/testing.txt + setup-mysql + - name: Run celery + if: steps.check.outcome == 'failure' + run: celery --app=superset.tasks.celery_app:app worker -Ofair -c 2 & + - name: Python integration tests (MySQL) + if: steps.check.outcome == 'failure' + run: | + ./scripts/python_tests.sh + - name: Upload code coverage + if: steps.check.outcome == 'failure' + run: | + bash .github/workflows/codecov.sh -c -F python -F mysql + + test-postgres: + if: github.event.pull_request.draft == false + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.7, 3.8] + env: + PYTHONPATH: ${{ github.workspace }} + SUPERSET_CONFIG: tests.integration_tests.superset_test_config + REDIS_PORT: 16379 + SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset + services: + postgres: + image: postgres:10-alpine + env: + POSTGRES_USER: superset + POSTGRES_PASSWORD: superset + ports: + # Use custom ports for services to avoid accidentally connecting to + # GitHub action runner's default installations + - 15432:5432 + redis: + image: redis:5-alpine + ports: + - 16379:6379 + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v2 + with: + persist-credentials: false + submodules: recursive + - name: Check if python changes are present + id: check + env: + GITHUB_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + continue-on-error: true + run: ./scripts/ci_check_no_file_changes.sh python + - name: Setup Python + if: steps.check.outcome == 'failure' + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + if: steps.check.outcome == 'failure' + uses: ./.github/actions/cached-dependencies + with: + run: | + apt-get-install + pip-upgrade + pip install -r requirements/testing.txt + setup-postgres + - name: Run celery + if: steps.check.outcome == 'failure' + run: celery --app=superset.tasks.celery_app:app worker -Ofair -c 2 & + - name: Python integration tests (PostgreSQL) + if: steps.check.outcome == 'failure' + run: | + ./scripts/python_tests.sh + - name: Upload code coverage + if: steps.check.outcome == 'failure' + run: | + bash .github/workflows/codecov.sh -c -F python -F postgres + + test-sqlite: + if: github.event.pull_request.draft == false + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: [3.7] + env: + PYTHONPATH: ${{ github.workspace }} + SUPERSET_CONFIG: tests.integration_tests.superset_test_config + REDIS_PORT: 16379 + SUPERSET__SQLALCHEMY_DATABASE_URI: | + sqlite:///${{ github.workspace }}/.temp/unittest.db + services: + redis: + image: redis:5-alpine + ports: + - 16379:6379 + steps: + - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" + uses: actions/checkout@v2 + with: + persist-credentials: false + submodules: recursive + - name: Check if python changes are present + id: check + env: + GITHUB_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + continue-on-error: true + run: ./scripts/ci_check_no_file_changes.sh python + - name: Setup Python + if: steps.check.outcome == 'failure' + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + if: steps.check.outcome == 'failure' + uses: ./.github/actions/cached-dependencies + with: + run: | + apt-get-install + pip-upgrade + pip install -r requirements/testing.txt + mkdir ${{ github.workspace }}/.temp + - name: Run celery + if: steps.check.outcome == 'failure' + run: celery --app=superset.tasks.celery_app:app worker -Ofair -c 2 & + - name: Python integration tests (SQLite) + if: steps.check.outcome == 'failure' + run: | + ./scripts/python_tests.sh + - name: Upload code coverage + if: steps.check.outcome == 'failure' + run: | + bash .github/workflows/codecov.sh -c -F python -F sqlite diff --git a/.github/workflows/superset-python-presto-hive.yml b/.github/workflows/superset-python-presto-hive.yml index 34d23e8833cc..917703782202 100644 --- a/.github/workflows/superset-python-presto-hive.yml +++ b/.github/workflows/superset-python-presto-hive.yml @@ -17,7 +17,7 @@ jobs: python-version: [3.8] env: PYTHONPATH: ${{ github.workspace }} - SUPERSET_CONFIG: tests.superset_test_config + SUPERSET_CONFIG: tests.integration_tests.superset_test_config REDIS_PORT: 16379 SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset SUPERSET__SQLALCHEMY_EXAMPLES_URI: presto://localhost:15433/memory/default @@ -73,7 +73,7 @@ jobs: setup-postgres - name: Run celery if: steps.check.outcome == 'failure' - run: celery worker --app=superset.tasks.celery_app:app -Ofair -c 2 & + run: celery --app=superset.tasks.celery_app:app worker -Ofair -c 2 & - name: Python unit tests (PostgreSQL) if: steps.check.outcome == 'failure' run: | @@ -91,7 +91,7 @@ jobs: python-version: [3.8] env: PYTHONPATH: ${{ github.workspace }} - SUPERSET_CONFIG: tests.superset_test_config + SUPERSET_CONFIG: tests.integration_tests.superset_test_config REDIS_PORT: 16379 SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset SUPERSET__SQLALCHEMY_EXAMPLES_URI: hive://localhost:10000/default @@ -148,7 +148,7 @@ jobs: setup-postgres - name: Run celery if: steps.check.outcome == 'failure' - run: celery worker --app=superset.tasks.celery_app:app -Ofair -c 2 & + run: celery --app=superset.tasks.celery_app:app worker -Ofair -c 2 & - name: Python unit tests (PostgreSQL) if: steps.check.outcome == 'failure' run: | diff --git a/.github/workflows/superset-python-unittest.yml b/.github/workflows/superset-python-unittest.yml index 8d03fa8b443f..69f96bcb9b77 100644 --- a/.github/workflows/superset-python-unittest.yml +++ b/.github/workflows/superset-python-unittest.yml @@ -1,5 +1,5 @@ # Python unit tests -name: Python +name: Python-Unit on: push: @@ -9,150 +9,14 @@ on: types: [synchronize, opened, reopened, ready_for_review] jobs: - test-mysql: + unit-tests: if: github.event.pull_request.draft == false runs-on: ubuntu-20.04 strategy: matrix: - python-version: [3.7] + python-version: [3.7,3.8] env: PYTHONPATH: ${{ github.workspace }} - SUPERSET_CONFIG: tests.superset_test_config - REDIS_PORT: 16379 - SUPERSET__SQLALCHEMY_DATABASE_URI: | - mysql+mysqldb://superset:superset@127.0.0.1:13306/superset?charset=utf8mb4&binary_prefix=true - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 13306:3306 - redis: - image: redis:5-alpine - options: --entrypoint redis-server - ports: - - 16379:6379 - steps: - - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v2 - with: - persist-credentials: false - submodules: recursive - - name: Check if python changes are present - id: check - env: - GITHUB_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - continue-on-error: true - run: ./scripts/ci_check_no_file_changes.sh python - - name: Setup Python - if: steps.check.outcome == 'failure' - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - if: steps.check.outcome == 'failure' - uses: ./.github/actions/cached-dependencies - with: - run: | - apt-get-install - pip-upgrade - pip install -r requirements/testing.txt - setup-mysql - - name: Run celery - if: steps.check.outcome == 'failure' - run: celery worker --app=superset.tasks.celery_app:app -Ofair -c 2 & - - name: Python unit tests (MySQL) - if: steps.check.outcome == 'failure' - run: | - ./scripts/python_tests.sh - - name: Upload code coverage - if: steps.check.outcome == 'failure' - run: | - bash .github/workflows/codecov.sh -c -F python -F mysql - - test-postgres: - if: github.event.pull_request.draft == false - runs-on: ubuntu-20.04 - strategy: - matrix: - python-version: [3.7, 3.8] - env: - PYTHONPATH: ${{ github.workspace }} - SUPERSET_CONFIG: tests.superset_test_config - REDIS_PORT: 16379 - SUPERSET__SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://superset:superset@127.0.0.1:15432/superset - services: - postgres: - image: postgres:10-alpine - env: - POSTGRES_USER: superset - POSTGRES_PASSWORD: superset - ports: - # Use custom ports for services to avoid accidentally connecting to - # GitHub action runner's default installations - - 15432:5432 - redis: - image: redis:5-alpine - ports: - - 16379:6379 - steps: - - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v2 - with: - persist-credentials: false - submodules: recursive - - name: Check if python changes are present - id: check - env: - GITHUB_REPO: ${{ github.repository }} - PR_NUMBER: ${{ github.event.pull_request.number }} - continue-on-error: true - run: ./scripts/ci_check_no_file_changes.sh python - - name: Setup Python - if: steps.check.outcome == 'failure' - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - if: steps.check.outcome == 'failure' - uses: ./.github/actions/cached-dependencies - with: - run: | - apt-get-install - pip-upgrade - pip install -r requirements/testing.txt - setup-postgres - - name: Run celery - if: steps.check.outcome == 'failure' - run: celery worker --app=superset.tasks.celery_app:app -Ofair -c 2 & - - name: Python unit tests (PostgreSQL) - if: steps.check.outcome == 'failure' - run: | - ./scripts/python_tests.sh - - name: Upload code coverage - if: steps.check.outcome == 'failure' - run: | - bash .github/workflows/codecov.sh -c -F python -F postgres - - test-sqlite: - if: github.event.pull_request.draft == false - runs-on: ubuntu-20.04 - strategy: - matrix: - python-version: [3.7] - env: - PYTHONPATH: ${{ github.workspace }} - SUPERSET_CONFIG: tests.superset_test_config - REDIS_PORT: 16379 - SUPERSET__SQLALCHEMY_DATABASE_URI: | - sqlite:///${{ github.workspace }}/.temp/unittest.db - services: - redis: - image: redis:5-alpine - ports: - - 16379:6379 steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v2 @@ -171,6 +35,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} +# TODO: separated requiermentes.txt file just for unit tests - name: Install dependencies if: steps.check.outcome == 'failure' uses: ./.github/actions/cached-dependencies @@ -180,14 +45,11 @@ jobs: pip-upgrade pip install -r requirements/testing.txt mkdir ${{ github.workspace }}/.temp - - name: Run celery - if: steps.check.outcome == 'failure' - run: celery worker --app=superset.tasks.celery_app:app -Ofair -c 2 & - - name: Python unit tests (SQLite) + - name: Python unit tests if: steps.check.outcome == 'failure' run: | - ./scripts/python_tests.sh + pytest ./tests/unit_tests --cache-clear - name: Upload code coverage if: steps.check.outcome == 'failure' run: | - bash .github/workflows/codecov.sh -c -F python -F sqlite + bash .github/workflows/codecov.sh -c -F python -F unit diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bca0923e8068..80202bebfab9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,9 +24,10 @@ repos: hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.790 + rev: v0.910 hooks: - id: mypy + additional_dependencies: [types-all] - repo: https://github.com/peterdemin/pip-compile-multi rev: v2.4.1 hooks: diff --git a/.pylintrc b/.pylintrc index 49b3984d1943..e3715334d145 100644 --- a/.pylintrc +++ b/.pylintrc @@ -70,7 +70,8 @@ confidence= # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -#enable= +enable= + useless-suppression, # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this @@ -81,8 +82,10 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,missing-docstring,too-many-lines,ungrouped-imports,import-outside-toplevel,raise-missing-from,super-with-arguments,bad-option-value,too-few-public-methods,too-many-locals - +disable= + missing-docstring, + too-many-lines, + duplicate-code, [REPORTS] @@ -209,7 +212,7 @@ max-nested-blocks=5 [FORMAT] # Maximum number of characters on a single line. -max-line-length=88 +max-line-length=90 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )?<?https?://\S+>?$ @@ -308,7 +311,7 @@ generated-members= # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. -contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager +contextmanager-decorators=contextlib.contextmanager [VARIABLES] @@ -365,7 +368,7 @@ max-locals=15 max-returns=10 # Maximum number of branch for function / method body -max-branches=12 +max-branches=15 # Maximum number of statements in function / method body max-statements=50 @@ -374,7 +377,7 @@ max-statements=50 max-parents=7 # Maximum number of attributes for a class (see R0902). -max-attributes=7 +max-attributes=8 # Minimum number of public methods for a class (see R0903). min-public-methods=2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 66dc4ab841cd..9f16f500de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,511 @@ specific language governing permissions and limitations under the License. --> ## Change Log +### 1.2 (Date TBD) +**Features** +- [11498](https://github.com/apache/superset/pull/11498) feat(SIP-39): Websocket sidecar app (#11498) (@Rob DiCiuccio) +- [13894](https://github.com/apache/superset/pull/13894) feat(alert/report): add ALERTS_ATTACH_REPORTS feature flags + feature (#13894) (@Lily Kuang) +- [13828](https://github.com/apache/superset/pull/13828) feat(alert/report): chart as csv format attachment for email and slack (#13828) (@Lily Kuang) +- [14235](https://github.com/apache/superset/pull/14235) feat(alert/report): update content format radio buttons (#14235) (@Lily Kuang) +- [13752](https://github.com/apache/superset/pull/13752) feat(alerts & reports): Easier to read execution logs (#13752) (@Jack Fragassi) +- [14076](https://github.com/apache/superset/pull/14076) feat(can_share): can share chart and dashboard (#14076) (@Amit Miran) +- [13687](https://github.com/apache/superset/pull/13687) feat(cross-filter): Cross filter badge (#13687) (@simcha90) +- [13625](https://github.com/apache/superset/pull/13625) feat(cross-filters): Add scoping for cross filters (#13625) (@simcha90) +- [13523](https://github.com/apache/superset/pull/13523) feat(dashboard): dashboard/id/datasets endpoint (#13523) (@David Aaron Suddjian) +- [13145](https://github.com/apache/superset/pull/13145) feat(dashboard_rbac): manage roles for dashboard (#13145) (@simcha90) +- [13992](https://github.com/apache/superset/pull/13992) feat(dashboard_rbac): provide data access based on dashboard access (#13992) (@Amit Miran) +- [14519](https://github.com/apache/superset/pull/14519) feat(dremio): implement convert_dttm method (#14519) (@Ville Brofeldt) +- [13210](https://github.com/apache/superset/pull/13210) feat(explore): ColumnSelectControl with drag-and-drop (#13210) (@Yongjie Zhao) +- [13598](https://github.com/apache/superset/pull/13598) feat(explore): Drag and drop UX improvements (#13598) (@Kamil Gabryjelski) +- [13294](https://github.com/apache/superset/pull/13294) feat(explore): Postgres datatype conversion (#13294) (@Nikola Gigić) +- [13758](https://github.com/apache/superset/pull/13758) feat(explore): adhoc column formatting for Table chart (#13758) (@Jesse Yang) +- [14340](https://github.com/apache/superset/pull/14340) feat(filter-box): sort by metric on backend (#14340) (@Ville Brofeldt) +- [13576](https://github.com/apache/superset/pull/13576) feat(filter-set): Filter set edge cases (#13576) (@simcha90) +- [13529](https://github.com/apache/superset/pull/13529) feat(filter-set): Filter set history (#13529) (@simcha90) +- [13545](https://github.com/apache/superset/pull/13545) feat(filter-set): Update existing filter set (#13545) (@simcha90) +- [13515](https://github.com/apache/superset/pull/13515) feat(filter-set): adding new filters (#13515) (@simcha90) +- [13678](https://github.com/apache/superset/pull/13678) feat(helm): Add optional apt-get install to superset_bootstrap.sh (#13678) (@Daniel Wood) +- [14030](https://github.com/apache/superset/pull/14030) feat(helm): add pod and deployment annotations to template (#14030) (@Bruno FERNANDO) +- [13683](https://github.com/apache/superset/pull/13683) feat(homescreen and cards): Toggle thumbnails off or on and feature flag (#13683) (@Phillip Kelley-Dotson) +- [13726](https://github.com/apache/superset/pull/13726) feat(native-filters): Add default first value to select filter (#13726) (@simcha90) +- [14461](https://github.com/apache/superset/pull/14461) feat(native-filters): Auto apply changes in FiltersConfigModal (#14461) (@simcha90) +- [13507](https://github.com/apache/superset/pull/13507) feat(native-filters): Filter set tabs (#13507) (@simcha90) +- [14313](https://github.com/apache/superset/pull/14313) feat(native-filters): Implement adhoc filters and time picker in Range and Select native filters (#14313) (@Kamil Gabryjelski) +- [14261](https://github.com/apache/superset/pull/14261) feat(native-filters): Show/Hide filter bar by metdata ff (#14261) (@simcha90) +- [13506](https://github.com/apache/superset/pull/13506) feat(native-filters): Update filter bar buttons (#13506) (@simcha90) +- [14374](https://github.com/apache/superset/pull/14374) feat(native-filters): Use datasets in dashboard as default options for native filters (#14374) (@Kamil Gabryjelski) +- [14314](https://github.com/apache/superset/pull/14314) feat(native-filters): add option to create value in select filter (#14314) (@Ville Brofeldt) +- [14346](https://github.com/apache/superset/pull/14346) feat(native-filters): add optional sort metric to select filter (#14346) (@Ville Brofeldt) +- [14375](https://github.com/apache/superset/pull/14375) feat(native-filters): add refresh button to default value picker (#14375) (@Ville Brofeldt) +- [13569](https://github.com/apache/superset/pull/13569) feat(native-filters): add sort option to select filter (#13569) (@Ville Brofeldt) +- [13622](https://github.com/apache/superset/pull/13622) feat(native-filters): add temporal support to select filter (#13622) (@Ville Brofeldt) +- [13484](https://github.com/apache/superset/pull/13484) feat(native-filters): add timegrain and column filter (#13484) (@Ville Brofeldt) +- [14312](https://github.com/apache/superset/pull/14312) feat(native-filters): add tooltip to control values (#14312) (@Ville Brofeldt) +- [14217](https://github.com/apache/superset/pull/14217) feat(native-filters): select group by support (#14217) (@Amit Miran) +- [13634](https://github.com/apache/superset/pull/13634) feat(reports): SLACK_API_TOKEN as callable or str (#13634) (@ʈᵃᵢ) +- [13135](https://github.com/apache/superset/pull/13135) feat(reports): send notification on error with grace (#13135) (@Daniel Vaz Gaspar) +- [13772](https://github.com/apache/superset/pull/13772) feat(rls): enable row level security by default (#13772) (@Ville Brofeldt) +- [14197](https://github.com/apache/superset/pull/14197) feat(viz): add mixed and radar chart (#14197) (@Ville Brofeldt) +- [13377](https://github.com/apache/superset/pull/13377) feat(viz): implement time picker on filter box (#13377) (@Yongjie Zhao) +- [13144](https://github.com/apache/superset/pull/13144) feat: Add error pages (#13144) (@Sam Faber-Manning) +- [14357](https://github.com/apache/superset/pull/14357) feat: Add etag caching to dashboard APIs (#14357) (@Erik Ritter) +- [14110](https://github.com/apache/superset/pull/14110) feat: Add health endpoint to WebSocket server (#14110) (@Ben Reinhart) +- [13190](https://github.com/apache/superset/pull/13190) feat: Added Rows Returned (#13190) (@AAfghahi) +- [14109](https://github.com/apache/superset/pull/14109) feat: Adding encrypted field factory (#14109) (@Craig Rueda) +- [14234](https://github.com/apache/superset/pull/14234) feat: Adding limiting_factor column to Query model (#14234) (@AAfghahi) +- [14318](https://github.com/apache/superset/pull/14318) feat: Dynamic imports for the Icons component (#14318) (@Geido) +- [13981](https://github.com/apache/superset/pull/13981) feat: FE: Export for Queries II (#13981) (@Lyndsi Kay Williams) +- [14091](https://github.com/apache/superset/pull/14091) feat: FE: Import for Queries II (#14091) (@Lyndsi Kay Williams) +- [13740](https://github.com/apache/superset/pull/13740) feat: Implement Celery SoftTimeLimit handling (#13740) (@Lily Kuang) +- [13340](https://github.com/apache/superset/pull/13340) feat: Implement drag and drop columns for filters (#13340) (@Kamil Gabryjelski) +- [13575](https://github.com/apache/superset/pull/13575) feat: Implement drag and drop for metrics (#13575) (@Kamil Gabryjelski) +- [13521](https://github.com/apache/superset/pull/13521) feat: Logic added to limiting factor column in Query model (#13521) (@AAfghahi) +- [14007](https://github.com/apache/superset/pull/14007) feat: Make async query JWT cookie domain configurable (#14007) (@Ben Reinhart) +- [12862](https://github.com/apache/superset/pull/12862) feat: Make initial user configurable in helm chart (#12862) (@Andreas Eberle) +- [13696](https://github.com/apache/superset/pull/13696) feat: Refactor asyncEvent middleware and add websocket support (#13696) (@Rob DiCiuccio) +- [14256](https://github.com/apache/superset/pull/14256) feat: Setup `supersetText` configuration to overwrite errors (#14256) (@Hugh A. Miles II) +- [14398](https://github.com/apache/superset/pull/14398) feat: Support env vars configuration for WebSocket server (#14398) (@Ben Reinhart) +- [14008](https://github.com/apache/superset/pull/14008) feat: Support feature flag overrides in ephemeral test envs (#14008) (@Rob DiCiuccio) +- [14122](https://github.com/apache/superset/pull/14122) feat: TrinoEngineSpec.adjust_database_uri (#14122) (@Đặng Minh Dũng) +- [13662](https://github.com/apache/superset/pull/13662) feat: add 'extra' column to saved_queries GET (#13662) (@Maxime Beauchemin) +- [13682](https://github.com/apache/superset/pull/13682) feat: add Databricks DB engine spec (#13682) (@Beto Dealmeida) +- [14072](https://github.com/apache/superset/pull/14072) feat: add Echarts gauge chart (#14072) (@krsnik93) +- [13584](https://github.com/apache/superset/pull/13584) feat: add ErrorBoundary to chart controls (#13584) (@Jesse Yang) +- [14267](https://github.com/apache/superset/pull/14267) feat: add alerts & reports to docker compose (#14267) (@Daniel Vaz Gaspar) +- [13152](https://github.com/apache/superset/pull/13152) feat: add connector for CrateDB (#13152) (@Srini Kadamati) +- [13827](https://github.com/apache/superset/pull/13827) feat: add descriptions to report emails (#13827) (@Sam Faber-Manning) +- [14208](https://github.com/apache/superset/pull/14208) feat: add endpoint to fetch available DBs (#14208) (@Beto Dealmeida) +- [13468](https://github.com/apache/superset/pull/13468) feat: add event_logger to test_connection and create_database commands (#13468) (@Hugh A. Miles II) +- [13997](https://github.com/apache/superset/pull/13997) feat: add extract_errors to Postgres (#13997) (@Beto Dealmeida) +- [13586](https://github.com/apache/superset/pull/13586) feat: add more big data for testing (#13586) (@Beto Dealmeida) +- [13448](https://github.com/apache/superset/pull/13448) feat: add option to load big/wide tables (#13448) (@Beto Dealmeida) +- [13364](https://github.com/apache/superset/pull/13364) feat: add unit test for LimitMethod.FETCH_MANY (#13364) (@Beto Dealmeida) +- [13606](https://github.com/apache/superset/pull/13606) feat: add warning metadata to tables and metrics (#13606) (@Erik Ritter) +- [13610](https://github.com/apache/superset/pull/13610) feat: added support to configure the default explorer viz (#13610) (@cccs-jc) +- [13601](https://github.com/apache/superset/pull/13601) feat: better error message when adding DBs (#13601) (@Beto Dealmeida) +- [14250](https://github.com/apache/superset/pull/14250) feat: catch errors on do_ping (#14250) (@Beto Dealmeida) +- [13893](https://github.com/apache/superset/pull/13893) feat: create backend routes and API for importing saved queries (#13893) (@AAfghahi) +- [13487](https://github.com/apache/superset/pull/13487) feat: create hooks to redux component (#13487) (@AAfghahi) +- [13871](https://github.com/apache/superset/pull/13871) feat: create table with long name (#13871) (@Beto Dealmeida) +- [14172](https://github.com/apache/superset/pull/14172) feat: error messages for Presto connections (#14172) (@Beto Dealmeida) +- [14093](https://github.com/apache/superset/pull/14093) feat: error messages when connecting to MSSQL (#14093) (@Beto Dealmeida) +- [14171](https://github.com/apache/superset/pull/14171) feat: error messages when connecting to mssql (#14171) (@AAfghahi) +- [14040](https://github.com/apache/superset/pull/14040) feat: handle chart/data API errors (#14040) (@Erik Ritter) +- [14204](https://github.com/apache/superset/pull/14204) feat: improve engine spec discoverability (#14204) (@Beto Dealmeida) +- [13960](https://github.com/apache/superset/pull/13960) feat: initial work to make v1 API compatible with SIP-40 and SIP-41 (#13960) (@Beto Dealmeida) +- [14146](https://github.com/apache/superset/pull/14146) feat: invalid DB name error messages (MySQL/Postgres/Redshift) (#14146) (@AAfghahi) +- [14089](https://github.com/apache/superset/pull/14089) feat: invalid hostname and password error messages (MySQL) (#14089) (@AAfghahi) +- [14111](https://github.com/apache/superset/pull/14111) feat: invalid hostname and password error messages (Redshift) (#14111) (@AAfghahi) +- [14038](https://github.com/apache/superset/pull/14038) feat: invalid password error message (Postgres) (#14038) (@Beto Dealmeida) +- [13862](https://github.com/apache/superset/pull/13862) feat: only send alert error emails to owners of the alert (#13862) (@Sam Faber-Manning) +- [14163](https://github.com/apache/superset/pull/14163) feat: publish superset helm chart (#14163) (@jawabuu) +- [13441](https://github.com/apache/superset/pull/13441) feat: refactor on DBEventLogger to allow for context management (#13441) (@Hugh A. Miles II) +- [14092](https://github.com/apache/superset/pull/14092) feat: restyle database modal (#14092) (@Elizabeth Thompson) +- [13561](https://github.com/apache/superset/pull/13561) feat: script to benchmark DB migrations (#13561) (@Beto Dealmeida) +- [13720](https://github.com/apache/superset/pull/13720) feat: sort time grain configs (#13720) (@Elizabeth Thompson) +- [13743](https://github.com/apache/superset/pull/13743) feat: use cross-env when running dev-server (#13743) (@Cemre Mengu) + +**Fixes** +- [13585](https://github.com/apache/superset/pull/13585) fix antd select dropdown issue (#13585) (@Lily Kuang) +- [14369](https://github.com/apache/superset/pull/14369) fix error getting partitionQuery from table.partition (#14369) (@eriendeng) +- [13878](https://github.com/apache/superset/pull/13878) fix(#13378): Ensure g.user is set for impersonation (#13878) (@Ben Reinhart) +- [13756](https://github.com/apache/superset/pull/13756) fix(CI): use presto docker image from starburst (#13756) (@Jesse Yang) +- [13718](https://github.com/apache/superset/pull/13718) fix(alerts and reports): Unify timestamp format on execution log view (#13718) (@Jack Fragassi) +- [13436](https://github.com/apache/superset/pull/13436) fix(alerts&reports): add celery soft timeout support (#13436) (@Daniel Vaz Gaspar) +- [13911](https://github.com/apache/superset/pull/13911) fix(alerts/reports): working timeout with celery kill and logic fix (#13911) (@Daniel Vaz Gaspar) +- [14483](https://github.com/apache/superset/pull/14483) fix(annotations): pass force param to annotation request (#14483) (@Ville Brofeldt) +- [14526](https://github.com/apache/superset/pull/14526) fix(chart-data): handle url_params in csv export and native filters (#14526) (@Ville Brofeldt) +- [13945](https://github.com/apache/superset/pull/13945) fix(colors): fix color schemes (#13945) (@simcha90) +- [14063](https://github.com/apache/superset/pull/14063) fix(country-map): update geojson and control layout (#14063) (@Jesse Yang) +- [13665](https://github.com/apache/superset/pull/13665) fix(cross-filter): fix scheme py (#13665) (@simcha90) +- [14220](https://github.com/apache/superset/pull/14220) fix(cross-filters): Fix missed metadata (#14220) (@simcha90) +- [13692](https://github.com/apache/superset/pull/13692) fix(cross-filters): add a control panel to range filter (#13692) (@Ville Brofeldt) +- [13703](https://github.com/apache/superset/pull/13703) fix(cross-filters): expand relevant indicator sections (#13703) (@Ville Brofeldt) +- [13704](https://github.com/apache/superset/pull/13704) fix(cross-filters): only apply filters if ff is set (#13704) (@Ville Brofeldt) +- [13825](https://github.com/apache/superset/pull/13825) fix(cypress): avoid referencing detached dom nodes (#13825) (@David Aaron Suddjian) +- [14306](https://github.com/apache/superset/pull/14306) fix(dashboard): Add caching for dashboard datasets (#14306) (@David Aaron Suddjian) +- [13352](https://github.com/apache/superset/pull/13352) fix(dashboard): Get dashboard by slug (#13352) (@David Aaron Suddjian) +- [14207](https://github.com/apache/superset/pull/14207) fix(dashboard): draft dashboards should be viewable (#14207) (@Jesse Yang) +- [14129](https://github.com/apache/superset/pull/14129) fix(dashboard): filter set hydration not working (#14129) (@Ville Brofeldt) +- [14148](https://github.com/apache/superset/pull/14148) fix(dashboard): incorrect perm for users with multiple roles (#14148) (@Jesse Yang) +- [13691](https://github.com/apache/superset/pull/13691) fix(dashboard): missing parents in directPathToFilter (#13691) (@Ville Brofeldt) +- [13769](https://github.com/apache/superset/pull/13769) fix(dashboard): rename native filter configuration property (#13769) (@Ville Brofeldt) +- [13652](https://github.com/apache/superset/pull/13652) fix(datasets): log create exceptions as warning (#13652) (@ʈᵃᵢ) +- [14295](https://github.com/apache/superset/pull/14295) fix(db2): change name and add legacy alias (#14295) (@Ville Brofeldt) +- [14362](https://github.com/apache/superset/pull/14362) fix(emotion): add separate namespace for menu app (#14362) (@ʈᵃᵢ) +- [13826](https://github.com/apache/superset/pull/13826) fix(errors): Downgrade error on stop query to a warning (#13826) (@Will Barrett) +- [13621](https://github.com/apache/superset/pull/13621) fix(explore): Add int and bool regex pattern (#13621) (@Nikola Gigić) +- [14194](https://github.com/apache/superset/pull/14194) fix(explore): CUSTOM SQL tab should automatically update (#14194) (@Yaozong Liu) +- [13566](https://github.com/apache/superset/pull/13566) fix(explore): make horizontal scroll appears in data panel (#13566) (@Yaozong Liu) +- [13473](https://github.com/apache/superset/pull/13473) fix(explore): make sure sort by metric is not duplicated (#13473) (@Jesse Yang) +- [13801](https://github.com/apache/superset/pull/13801) fix(explore): strip semicolons in virtual table SQL (#13801) (@Phillip Kelley-Dotson) +- [13613](https://github.com/apache/superset/pull/13613) fix(helm): Add missing extraConfigs template volume mapping and fix type error on template. (#13613) (@Daniel Wood) +- [14255](https://github.com/apache/superset/pull/14255) fix(hive): Update CSV to Hive upload prefix (#14255) (@John Bodley) +- [14240](https://github.com/apache/superset/pull/14240) fix(hive): Use parquet rather than textfile when uploading CSV files to Hive (#14240) (@John Bodley) +- [13906](https://github.com/apache/superset/pull/13906) fix(listview): update listview feature flag (#13906) (@Phillip Kelley-Dotson) +- [14332](https://github.com/apache/superset/pull/14332) fix(native-filters): Fix first loading of charts (#14332) (@simcha90) +- [14334](https://github.com/apache/superset/pull/14334) fix(native-filters): Fix indicators (#14334) (@simcha90) +- [14370](https://github.com/apache/superset/pull/14370) fix(native-filters): Update filter saving (#14370) (@simcha90) +- [13837](https://github.com/apache/superset/pull/13837) fix(native-filters): add global async query support to native filters (#13837) (@Ville Brofeldt) +- [14353](https://github.com/apache/superset/pull/14353) fix(native-filters): allowClear only when required not checked (#14353) (@Ville Brofeldt) +- [14409](https://github.com/apache/superset/pull/14409) fix(native-filters): always show filters without dataset (#14409) (@Ville Brofeldt) +- [14401](https://github.com/apache/superset/pull/14401) fix(native-filters): default value not populated on second opening (#14401) (@Ville Brofeldt) +- [14426](https://github.com/apache/superset/pull/14426) fix(native-filters): fix filter scope error (#14426) (@Ville Brofeldt) +- [14387](https://github.com/apache/superset/pull/14387) fix(native-filters): fix lint (#14387) (@simcha90) +- [14218](https://github.com/apache/superset/pull/14218) fix(native-filters): fix remove native filters (#14218) (@simcha90) +- [13688](https://github.com/apache/superset/pull/13688) fix(native-filters): fix removing native filter (#13688) (@simcha90) +- [14385](https://github.com/apache/superset/pull/14385) fix(native-filters): force update control value on change (#14385) (@Ville Brofeldt) +- [13794](https://github.com/apache/superset/pull/13794) fix(native-filters): improve loading styles for filter component (#13794) (@Ville Brofeldt) +- [14244](https://github.com/apache/superset/pull/14244) fix(native-filters): merge_extra_form_data extras processing (#14244) (@Ville Brofeldt) +- [13434](https://github.com/apache/superset/pull/13434) fix(query): order by adhoc metrics should trigger group by (#13434) (@Jesse Yang) +- [13331](https://github.com/apache/superset/pull/13331) fix(query-object): extra time-range-endpoints (#13331) (@John Bodley) +- [14014](https://github.com/apache/superset/pull/14014) fix(sqla): apply sqla type on calculated columns (#14014) (@Ville Brofeldt) +- [14095](https://github.com/apache/superset/pull/14095) fix(sqla): labels_expected contains mutated label (#14095) (@Ville Brofeldt) +- [14297](https://github.com/apache/superset/pull/14297) fix(sqla): timeseries limit not applied when using columns (#14297) (@Ville Brofeldt) +- [13739](https://github.com/apache/superset/pull/13739) fix(sqla-query): order by aggregations in Presto and Hive (#13739) (@Jesse Yang) +- [13636](https://github.com/apache/superset/pull/13636) fix(sqllab): Table name and schema are encoded twice during fetching table metadata on SQL Lab page. (#13636) (@Dmytro Mudrov) +- [14390](https://github.com/apache/superset/pull/14390) fix(sqllab): fix error due to anonymous user (#14390) (@Ville Brofeldt) +- [13288](https://github.com/apache/superset/pull/13288) fix(sqllab): per-tab hide left bar (#13288) (@Beto Dealmeida) +- [13841](https://github.com/apache/superset/pull/13841) fix(sqllab): warning message when rows limited (#13841) (@zuzana-vej) +- [14045](https://github.com/apache/superset/pull/14045) fix(viz): bump superset-ui 0.17.30 (#14045) (@Ville Brofeldt) +- [14438](https://github.com/apache/superset/pull/14438) fix(viz): bump superset-ui to fix bugs in table and graph chart (#14438) (@Jesse Yang) +- [14191](https://github.com/apache/superset/pull/14191) fix(viz): time shift read-only error (#14191) (@Ville Brofeldt) +- [13657](https://github.com/apache/superset/pull/13657) fix: 'Run in SQL Lab' redirect bug (#13657) (@Maxime Beauchemin) +- [13444](https://github.com/apache/superset/pull/13444) fix: API to allow importing old exports (JSON/YAML) (#13444) (@Beto Dealmeida) +- [14180](https://github.com/apache/superset/pull/14180) fix: Add extra requires for shillelagh (#14180) (@Erik Ritter) +- [14192](https://github.com/apache/superset/pull/14192) fix: Center each import icon and add a tooltip II (#14192) (@Lyndsi Kay Williams) +- [13859](https://github.com/apache/superset/pull/13859) fix: Check for permissions in FilterBox (#13859) (@Erik Ritter) +- [14317](https://github.com/apache/superset/pull/14317) fix: Cleanup serialization and hashing code (#14317) (@Ben Reinhart) +- [13972](https://github.com/apache/superset/pull/13972) fix: Data table z index in sql Editor (#13972) (@AAfghahi) +- [13749](https://github.com/apache/superset/pull/13749) fix: Disallows negative options remaining (#13749) (@Michael S. Molina) +- [14291](https://github.com/apache/superset/pull/14291) fix: Fix unintended cache misses with async queries (#14291) (@Ben Reinhart) +- [14086](https://github.com/apache/superset/pull/14086) fix: Fixed saved query export (#14086) (@Lyndsi Kay Williams) +- [13858](https://github.com/apache/superset/pull/13858) fix: Floating Menu in SQL Left Bar (#13858) (@AAfghahi) +- [14147](https://github.com/apache/superset/pull/14147) fix: Handle bad permission errors for bigquery test connections (#14147) (@Hugh A. Miles II) +- [13980](https://github.com/apache/superset/pull/13980) fix: Issue 13956 (#13980) (@John Bodley) +- [13602](https://github.com/apache/superset/pull/13602) fix: ParsedQuery subselect edge case (#13602) (@Erik Ritter) +- [13987](https://github.com/apache/superset/pull/13987) fix: Pie chart not displayed in viz (#13987) (@Yongjie Zhao) +- [13852](https://github.com/apache/superset/pull/13852) fix: Pin Prophet dependency after breaking changes (#13852) (@Étienne Boisseau-Sierra) +- [13469](https://github.com/apache/superset/pull/13469) fix: Remove view in sql lab from druid datasources (#13469) (@Erik Ritter) +- [13464](https://github.com/apache/superset/pull/13464) fix: SHOW is not DML (#13464) (@Beto Dealmeida) +- [13946](https://github.com/apache/superset/pull/13946) fix: SQL -> Explore Overwrite flow (#13946) (@Hugh A. Miles II) +- [14358](https://github.com/apache/superset/pull/14358) fix: SQL Statement on QUERY_LOGGER prints none to log (#14358) (@cccs-rc) +- [14372](https://github.com/apache/superset/pull/14372) fix: SQLLab role permissions (#14372) (@Daniel Vaz Gaspar) +- [13655](https://github.com/apache/superset/pull/13655) fix: Slack dashboard has stale charts (#13655) (@Beto Dealmeida) +- [14170](https://github.com/apache/superset/pull/14170) fix: Switching to local codecov script (#14170) (@Craig Rueda) +- [13974](https://github.com/apache/superset/pull/13974) fix: Use superset generic db to catch external_metadata queries (#13974) (@Hugh A. Miles II) +- [13830](https://github.com/apache/superset/pull/13830) fix: Use utils.json_iso_dttm_ser to dump jsons when async query execution (#13830) (@Carlos Fidel Selva Ochoa) +- [13496](https://github.com/apache/superset/pull/13496) fix: `IS NULL` filter operator for numeric columns (#13496) (@Jesse Yang) +- [14036](https://github.com/apache/superset/pull/14036) fix: add exception to catch session not having JWT (#14036) (@Hugh A. Miles II) +- [13822](https://github.com/apache/superset/pull/13822) fix: adjusted tab height (#13822) (@AAfghahi) +- [13488](https://github.com/apache/superset/pull/13488) fix: allow option to generate new query (#13488) (@Elizabeth Thompson) +- [13800](https://github.com/apache/superset/pull/13800) fix: allow spaces in DB names (#13800) (@Beto Dealmeida) +- [13563](https://github.com/apache/superset/pull/13563) fix: always pass a string as a value to ace editor (#13563) (@Elizabeth Thompson) +- [12341](https://github.com/apache/superset/pull/12341) fix: annotation layer modal err handling (#12341) (@Moriah Kreeger) +- [13969](https://github.com/apache/superset/pull/13969) fix: annotation modal dateRangePicker saved invalid value (#13969) (@Yongjie Zhao) +- [13790](https://github.com/apache/superset/pull/13790) fix: bad copy-and-paste in API spec (#13790) (@Beto Dealmeida) +- [13578](https://github.com/apache/superset/pull/13578) fix: better handle datasource exceptions (#13578) (@Daniel Vaz Gaspar) +- [13642](https://github.com/apache/superset/pull/13642) fix: bump FAB to 3.2.1, SQLAlchemy fix (#13642) (@Daniel Vaz Gaspar) +- [13868](https://github.com/apache/superset/pull/13868) fix: change status code for generic errors to 400 (#13868) (@Hugh A. Miles II) +- [13681](https://github.com/apache/superset/pull/13681) fix: clean up incorrect usage of TypeScript type in PropTypes (#13681) (@Jesse Yang) +- [14084](https://github.com/apache/superset/pull/14084) fix: custom TimeRange can not input value (#14084) (@Yongjie Zhao) +- [13695](https://github.com/apache/superset/pull/13695) fix: dashboard filter scope bug (#13695) (@Grace Guo) +- [13624](https://github.com/apache/superset/pull/13624) fix: data column in SQL lab left panel open by default (#13624) (@AAfghahi) +- [13330](https://github.com/apache/superset/pull/13330) fix: date picker support date unit with singular and plural (#13330) (@Yongjie Zhao) +- [13603](https://github.com/apache/superset/pull/13603) fix: default when adding tab_state.hide_left_bar (#13603) (@Beto Dealmeida) +- [13389](https://github.com/apache/superset/pull/13389) fix: delete dataset columns and metrics on the REST API (#13389) (@Daniel Vaz Gaspar) +- [13917](https://github.com/apache/superset/pull/13917) fix: do not run containers as root by default in Helm chart (#13917) (@Stanislav Simovski) +- [13926](https://github.com/apache/superset/pull/13926) fix: enable installing docker/requirements-local.txt in docker-compose-non-dev.yml (#13926) (@ʈᵃᵢ) +- [13998](https://github.com/apache/superset/pull/13998) fix: execution log crashes for logs with no uuid (#13998) (@Jack Fragassi) +- [14081](https://github.com/apache/superset/pull/14081) fix: fix bug when remove chart not removing it's related cross filter data (#14081) (@simcha90) +- [13546](https://github.com/apache/superset/pull/13546) fix: fix initial filter loading (#13546) (@simcha90) +- [14082](https://github.com/apache/superset/pull/14082) fix: flacky test in test_update_dataset_item_w_override_columns (#14082) (@Hugh A. Miles II) +- [14347](https://github.com/apache/superset/pull/14347) fix: flaky test for alerts and reports (#14347) (@Daniel Vaz Gaspar) +- [13979](https://github.com/apache/superset/pull/13979) fix: import dataset/dashboard empty keys (#13979) (@Beto Dealmeida) +- [14258](https://github.com/apache/superset/pull/14258) fix: incorrect module reference (#14258) (@Yongjie Zhao) +- [13737](https://github.com/apache/superset/pull/13737) fix: log exceptions for thumbnail generation as warnings (#13737) (@Sam Faber-Manning) +- [14057](https://github.com/apache/superset/pull/14057) fix: logs table - user_id is NULL (#14057) (@Hugh A. Miles II) +- [13493](https://github.com/apache/superset/pull/13493) fix: merge 67da9ef1ef9c and 1412ec1e5a7b migrations (#13493) (@Hugh A. Miles II) +- [13921](https://github.com/apache/superset/pull/13921) fix: new import/export CLI (#13921) (@Beto Dealmeida) +- [13955](https://github.com/apache/superset/pull/13955) fix: null exception from adhoc metric popover (#13955) (@Grace Guo) +- [14509](https://github.com/apache/superset/pull/14509) fix: parameterize titles correctly (#14509) (@David Aaron Suddjian) +- [14338](https://github.com/apache/superset/pull/14338) fix: pybabel extract fails (#14338) (@ume) +- [13780](https://github.com/apache/superset/pull/13780) fix: restart superset container in test script (#13780) (@Ben Reinhart) +- [13694](https://github.com/apache/superset/pull/13694) fix: select table overlay (#13694) (@AAfghahi) +- [14074](https://github.com/apache/superset/pull/14074) fix: unable to apply logging format (#14074) (@Yongjie Zhao) +- [14319](https://github.com/apache/superset/pull/14319) fix: unbreak CI (#14319) (@Erik Ritter) +- [13679](https://github.com/apache/superset/pull/13679) fix: use FeatureFlags in @superset-ui/core (#13679) (@Jesse Yang) +- [14099](https://github.com/apache/superset/pull/14099) fix:fix get permission function (#14099) (@simcha90) + +**Documentation** +- [13770](https://github.com/apache/superset/pull/13770) docs(contributing): fix backend port (#13770) (@Cemre Mengu) +- [13813](https://github.com/apache/superset/pull/13813) docs: Documenting how to debug Flask app (#13813) (@cccs-jc) +- [13455](https://github.com/apache/superset/pull/13455) docs: Fix typo in footer (#13455) (@Holger Stitz) +- [14304](https://github.com/apache/superset/pull/14304) docs: add publish step (#14304) (@ʈᵃᵢ) +- [13611](https://github.com/apache/superset/pull/13611) docs: correct sentence (#13611) (@Erfan Mirzapour) +- [13440](https://github.com/apache/superset/pull/13440) docs: deprecate old alerts and dash/charts reports (#13440) (@Daniel Vaz Gaspar) +- [14394](https://github.com/apache/superset/pull/14394) docs: improved clickhouse connection details (#14394) (@Srini Kadamati) +- [14321](https://github.com/apache/superset/pull/14321) docs: multiple small fixes around databases (#14321) (@Srini Kadamati) +- [12932](https://github.com/apache/superset/pull/12932) docs: update helm documentation (#12932) (@Anthony Corletti) +- [14027](https://github.com/apache/superset/pull/14027) docs: update outdated country map tools instructions (#14027) (@Jesse Yang) +- [13781](https://github.com/apache/superset/pull/13781) docs: update setup instructions (#13781) (@Ben Reinhart) + +**Other** +- [13759](https://github.com/apache/superset/pull/13759) Add CODEOWNERS (#13759) (@Rob DiCiuccio) +- [14238](https://github.com/apache/superset/pull/14238) Add Preset code owners for ephemeral test env workflows (#14238) (@Rob DiCiuccio) +- [13553](https://github.com/apache/superset/pull/13553) Add dedicated dir and storybook (#13553) (@Geido) +- [13961](https://github.com/apache/superset/pull/13961) Add docs for configuring Docker Compose setup (#13961) (@Alex Kreidler) +- [13728](https://github.com/apache/superset/pull/13728) Add extraSecrets config to allow specifying extra secret files (#13728) (@Andreas Eberle) +- [14223](https://github.com/apache/superset/pull/14223) Add superset helm repository (#14223) (@jawabuu) +- [13778](https://github.com/apache/superset/pull/13778) Add tests (#13778) (@Geido) +- [13950](https://github.com/apache/superset/pull/13950) Add tests for FilterBar CascadeFilterControl (#13950) (@Geido) +- [13425](https://github.com/apache/superset/pull/13425) Adding v0 import/export fields to dashboard metadata schema (#13425) (@michellethomas) +- [13359](https://github.com/apache/superset/pull/13359) Adds tests and storybook to CopyToClipboard component (#13359) (@Michael S. Molina) +- [13658](https://github.com/apache/superset/pull/13658) Bump pyyaml to latest (#13658) (@Rob DiCiuccio) +- [13854](https://github.com/apache/superset/pull/13854) Displays row limit warning with Alert component (#13854) (@Michael S. Molina) +- [14187](https://github.com/apache/superset/pull/14187) Enable the new pivot table (#14187) (@Kamil Gabryjelski) +- [13861](https://github.com/apache/superset/pull/13861) Fix broken link (#13861) (@Eric Anderson) +- [14039](https://github.com/apache/superset/pull/14039) Fix broken port-forward instructions (#14039) (@Caleb Collins-Parks) +- [14243](https://github.com/apache/superset/pull/14243) Fix filter edit popover not opening in DnD mode (#14243) (@Kamil Gabryjelski) +- [13608](https://github.com/apache/superset/pull/13608) Fix: Add SASL dependency module (#13608) (@Daniel Wood) +- [13505](https://github.com/apache/superset/pull/13505) Fixed KeyError by making kwarg explicit (#13505) (@Richard Nordin) +- [13905](https://github.com/apache/superset/pull/13905) Fixing condition around left margin for dashboard layout. Fixes #13863 (#13905) (@Evan Rusackas) +- [14323](https://github.com/apache/superset/pull/14323) Fixing spelling: `clint` -> `client` (#14323) (@Chris Carini) +- [13570](https://github.com/apache/superset/pull/13570) Given port in dockerfile set to same port in config (#13570) (@İbrahim Ercan) +- [13273](https://github.com/apache/superset/pull/13273) Improves ButtonGroup tests (#13273) (@Michael S. Molina) +- [14046](https://github.com/apache/superset/pull/14046) Make chart exclude itself from cross filtering (#14046) (@Kamil Gabryjelski) +- [14287](https://github.com/apache/superset/pull/14287) Make g.user attribute access safe for public users (#14287) (@Rob DiCiuccio) +- [13643](https://github.com/apache/superset/pull/13643) Make preliminary suggested changes to CONTRIBUTING.md (#13643) (@Kris Stern) +- [13314](https://github.com/apache/superset/pull/13314) Moves AsyncSelect component and tests to own folder (#13314) (@Michael S. Molina) +- [13901](https://github.com/apache/superset/pull/13901) Revert "fix: select table overlay (#13694)" (#13901) (@Erik Ritter) +- [13876](https://github.com/apache/superset/pull/13876) Tests for "CrossFilterScopingModal/utils" (#13876) (@Bruno Motta) +- [13991](https://github.com/apache/superset/pull/13991) Tests for ColumnSelect (#13991) (@Bruno Motta) +- [13975](https://github.com/apache/superset/pull/13975) Tests for ControlItems (#13975) (@Bruno Motta) +- [13872](https://github.com/apache/superset/pull/13872) Tests for DetailsPanel (#13872) (@Bruno Motta) +- [14025](https://github.com/apache/superset/pull/14025) Update CONTRIBUTING.md (#14025) (@Hugh A. Miles II) +- [13607](https://github.com/apache/superset/pull/13607) Update UPDATING.md (#13607) (@Beto Dealmeida) +- [13934](https://github.com/apache/superset/pull/13934) Update docs to use the newer WEBDRIVER_TYPE name (#13934) (@Octavian) +- [14219](https://github.com/apache/superset/pull/14219) Update trino connection docs (#14219) (@Aakash Nand) +- [14031](https://github.com/apache/superset/pull/14031) Use consistent chart value (#14031) (@Tom Vendetta) +- [13323](https://github.com/apache/superset/pull/13323) Use redis:latest in docker-compose workflow (#13323) (@Rob DiCiuccio) +- [13730](https://github.com/apache/superset/pull/13730) Use stringData instead of data in Secrets and remove manual base64 encoding (#13730) (@Andreas Eberle) +- [13936](https://github.com/apache/superset/pull/13936) Use the right variable for Celery results in Redis (#13936) (@Octavian) +- [14252](https://github.com/apache/superset/pull/14252) WIP (#14252) (@Beto Dealmeida) +- [14446](https://github.com/apache/superset/pull/14446) add migration (#14446) (@Ville Brofeldt) +- [13365](https://github.com/apache/superset/pull/13365) add more documentation around python testing and linting (#13365) (@Elizabeth Thompson) +- [13527](https://github.com/apache/superset/pull/13527) build(deps): bump elliptic from 6.5.3 to 6.5.4 in /docs (#13527) (@dependabot[bot]) +- [12636](https://github.com/apache/superset/pull/12636) build(deps): bump socket.io from 2.3.0 to 2.4.1 in /docs (#12636) (@dependabot[bot]) +- [13391](https://github.com/apache/superset/pull/13391) build(deps): bump three from 0.68.87 to 0.125.0 in /docs (#13391) (@dependabot[bot]) +- [13393](https://github.com/apache/superset/pull/13393) build(deps): bump urijs from 1.19.4 to 1.19.6 in /superset-frontend (#13393) (@dependabot[bot]) +- [13874](https://github.com/apache/superset/pull/13874) build(deps): bump y18n from 4.0.0 to 4.0.1 in /docs (#13874) (@dependabot[bot]) +- [13886](https://github.com/apache/superset/pull/13886) build(deps): bump y18n from 4.0.0 to 4.0.1 in /superset-frontend (#13886) (@dependabot[bot]) +- [13452](https://github.com/apache/superset/pull/13452) build(test env): Shutdown test environments on PR close (#13452) (@Rob DiCiuccio) +- [13189](https://github.com/apache/superset/pull/13189) build: Ephemeral environments for PRs via slash command (#13189) (@Rob DiCiuccio) +- [13498](https://github.com/apache/superset/pull/13498) build: check potential db migration conflict for open PRs (#13498) (@Jesse Yang) +- [13375](https://github.com/apache/superset/pull/13375) build: fix codecov informational config (#13375) (@Jesse Yang) +- [13421](https://github.com/apache/superset/pull/13421) build: ignore Storybook stories in CodeCov (#13421) (@Jesse Yang) +- [13677](https://github.com/apache/superset/pull/13677) build: implement codecov carryforward flags (#13677) (@Jack Fragassi) +- [13927](https://github.com/apache/superset/pull/13927) catch collapse onchange (#13927) (@Elizabeth Thompson) +- [13757](https://github.com/apache/superset/pull/13757) chore(CI): dont run Hive tests on Py 3.7 (#13757) (@Jesse Yang) +- [13784](https://github.com/apache/superset/pull/13784) chore(cypress): Make the e2e tests more behavior-driven (#13784) (@David Aaron Suddjian) +- [13939](https://github.com/apache/superset/pull/13939) chore(dashboard): Datasource -> Dataset (#13939) (@David Aaron Suddjian) +- [14342](https://github.com/apache/superset/pull/14342) chore(docker-compose): use dev image for docker-compose-non-dev.yml (#14342) (@ʈᵃᵢ) +- [14331](https://github.com/apache/superset/pull/14331) chore(docs): remove .py from db migrate -m (#14331) (@Amit Miran) +- [13660](https://github.com/apache/superset/pull/13660) chore(explore): added debounce in DateFilter (#13660) (@Yongjie Zhao) +- [13593](https://github.com/apache/superset/pull/13593) chore(explore): bump superset-ui 0.17.19 (#13593) (@Yongjie Zhao) +- [13767](https://github.com/apache/superset/pull/13767) chore(git): ignore superset/app/ folder (#13767) (@Cemre Mengu) +- [13500](https://github.com/apache/superset/pull/13500) chore(homepage): separate out api calls to make homepage load more dynamically (#13500) (@Phillip Kelley-Dotson) +- [14228](https://github.com/apache/superset/pull/14228) chore(prophet): bump prophet to 1.0.1 (#14228) (@Ville Brofeldt) +- [13306](https://github.com/apache/superset/pull/13306) chore(spa refactor): refactoring dashboard to use api's instead of bootstrapdata (#13306) (@Phillip Kelley-Dotson) +- [14013](https://github.com/apache/superset/pull/14013) chore(toggle thumbnails): use localstorage for toggle (#14013) (@Phillip Kelley-Dotson) +- [14274](https://github.com/apache/superset/pull/14274) chore: Add Redis password option to helm chart (#14274) (@Rob DiCiuccio) +- [13418](https://github.com/apache/superset/pull/13418) chore: Added rockset and firebird (#13418) (@Srini Kadamati) +- [13951](https://github.com/apache/superset/pull/13951) chore: Adds dataMask reducer to reducerIndex (#13951) (@Michael S. Molina) +- [14203](https://github.com/apache/superset/pull/14203) chore: Bumping lodash version in Superset (#14203) (@Evan Rusackas) +- [14196](https://github.com/apache/superset/pull/14196) chore: Code cleanup in DatabaseModal (#14196) (@Lyndsi Kay Williams) +- [14164](https://github.com/apache/superset/pull/14164) chore: Configures Jest to ignore storybook files when collecting coverage statistics (#14164) (@Michael S. Molina) +- [14339](https://github.com/apache/superset/pull/14339) chore: Improve Japanese translation (#14339) (@ume) +- [13722](https://github.com/apache/superset/pull/13722) chore: Improve Korean translation (#13722) (@김세환) +- [13376](https://github.com/apache/superset/pull/13376) chore: Improve japannese translation (#13376) (@Tetsushi Watanabe) +- [14193](https://github.com/apache/superset/pull/14193) chore: Improves Icons storybook (#14193) (@Michael S. Molina) +- [13274](https://github.com/apache/superset/pull/13274) chore: Migrates RefreshChartOverlay into Chart (#13274) (@Michael S. Molina) +- [13361](https://github.com/apache/superset/pull/13361) chore: Migrating dashboard/components/menu from jsx to tsx (#13361) (@Ayan Ginet) +- [13460](https://github.com/apache/superset/pull/13460) chore: Moves BootstrapSliderWrapper to own folder (#13460) (@Michael S. Molina) +- [14139](https://github.com/apache/superset/pull/14139) chore: Moves Card to the components folder (#14139) (@Michael S. Molina) +- [13454](https://github.com/apache/superset/pull/13454) chore: Moves ChartIcon to own folder (#13454) (@Michael S. Molina) +- [13459](https://github.com/apache/superset/pull/13459) chore: Moves CheckboxIcons to Checkbox folder (#13459) (@Michael S. Molina) +- [14124](https://github.com/apache/superset/pull/14124) chore: Moves Collapse to the components folder (#14124) (@Michael S. Molina) +- [14125](https://github.com/apache/superset/pull/14125) chore: Moves CronPicker to the components folder (#14125) (@Michael S. Molina) +- [14126](https://github.com/apache/superset/pull/14126) chore: Moves Dropdown to the components folder (#14126) (@Michael S. Molina) +- [14127](https://github.com/apache/superset/pull/14127) chore: Moves DropdownButton to the components folder (#14127) (@Michael S. Molina) +- [13458](https://github.com/apache/superset/pull/13458) chore: Moves Fade component into TableElement (#13458) (@Michael S. Molina) +- [13322](https://github.com/apache/superset/pull/13322) chore: Moves FilterableTable test to component folder (#13322) (@Michael S. Molina) +- [13485](https://github.com/apache/superset/pull/13485) chore: Moves InfoTooltip component to own folder (#13485) (@Michael S. Molina) +- [14128](https://github.com/apache/superset/pull/14128) chore: Moves InfoTooltip to the components folder (#14128) (@Michael S. Molina) +- [13324](https://github.com/apache/superset/pull/13324) chore: Moves ListView test to component folder (#13324) (@Michael S. Molina) +- [14130](https://github.com/apache/superset/pull/14130) chore: Moves Modal to the components folder (#14130) (@Michael S. Molina) +- [13338](https://github.com/apache/superset/pull/13338) chore: Moves OnPasteSelect tests to component's folder (#13338) (@Michael S. Molina) +- [14133](https://github.com/apache/superset/pull/14133) chore: Moves Radio to the components folder (#14133) (@Michael S. Molina) +- [14135](https://github.com/apache/superset/pull/14135) chore: Moves Switch to the components folder (#14135) (@Michael S. Molina) +- [13411](https://github.com/apache/superset/pull/13411) chore: Moves TableSelector tests to component's folder (#13411) (@Michael S. Molina) +- [14136](https://github.com/apache/superset/pull/14136) chore: Moves Tabs to the components folder (#14136) (@Michael S. Molina) +- [14137](https://github.com/apache/superset/pull/14137) chore: Moves Tooltip to the components folder (#14137) (@Michael S. Molina) +- [14123](https://github.com/apache/superset/pull/14123) chore: Moves WarningIconWithTooltip to own folder (#14123) (@Michael S. Molina) +- [14200](https://github.com/apache/superset/pull/14200) chore: Moves spec files to the src folder - iteration 1 (#14200) (@Michael S. Molina) +- [14201](https://github.com/apache/superset/pull/14201) chore: Moves spec files to the src folder - iteration 2 (#14201) (@Michael S. Molina) +- [14202](https://github.com/apache/superset/pull/14202) chore: Moves spec files to the src folder - iteration 3 (#14202) (@Michael S. Molina) +- [14166](https://github.com/apache/superset/pull/14166) chore: Moves the branding folder to the assets folder (#14166) (@Michael S. Molina) +- [14167](https://github.com/apache/superset/pull/14167) chore: Moves the staticPages folder to the assets folder (#14167) (@Michael S. Molina) +- [13462](https://github.com/apache/superset/pull/13462) chore: Removes Popover duplication (#13462) (@Michael S. Molina) +- [13477](https://github.com/apache/superset/pull/13477) chore: Removes Select duplication (#13477) (@Michael S. Molina) +- [13408](https://github.com/apache/superset/pull/13408) chore: Removes unused Hotkeys component (#13408) (@Michael S. Molina) +- [14298](https://github.com/apache/superset/pull/14298) chore: SQLLab row limit warning using Alert component (#14298) (@zuzana-vej) +- [13332](https://github.com/apache/superset/pull/13332) chore: Update OAuth2 configuration guide to match with FlaskAppBuilder (#13332) (@Liam) +- [13855](https://github.com/apache/superset/pull/13855) chore: Update PR template for SIP-59 DB migrations process (#13855) (@Rob DiCiuccio) +- [13738](https://github.com/apache/superset/pull/13738) chore: Update presto.mdx (#13738) (@Hugh A. Miles II) +- [14257](https://github.com/apache/superset/pull/14257) chore: WebSocket server improvements (#14257) (@Ben Reinhart) +- [14354](https://github.com/apache/superset/pull/14354) chore: add some basic instrumentation to Node WebSocket server (#14354) (@Ben Reinhart) +- [13811](https://github.com/apache/superset/pull/13811) chore: add success log whenever a connection is working (#13811) (@Hugh A. Miles II) +- [14355](https://github.com/apache/superset/pull/14355) chore: bump 0.17.38 (#14355) (@Amit Miran) +- [13953](https://github.com/apache/superset/pull/13953) chore: bump @superset-ui/plugin-chart-table to 0.17.28 (#13953) (@Jesse Yang) +- [14222](https://github.com/apache/superset/pull/14222) chore: bump package.json (#14222) (@Amit Miran) +- [14253](https://github.com/apache/superset/pull/14253) chore: bump superset-ui (#14253) (@Erik Ritter) +- [14460](https://github.com/apache/superset/pull/14460) chore: bump table plugin to 0.17.42 (#14460) (@Amit Miran) +- [13932](https://github.com/apache/superset/pull/13932) chore: bump to new superset-ui version (#13932) (@Amit Miran) +- [13374](https://github.com/apache/superset/pull/13374) chore: convert chartReducer to TypeScript (#13374) (@Jesse Yang) +- [13450](https://github.com/apache/superset/pull/13450) chore: disable drag metrics and columns in datasourcePanel (#13450) (@Yongjie Zhao) +- [13342](https://github.com/apache/superset/pull/13342) chore: improve modal error handling (#13342) (@Lily Kuang) +- [13596](https://github.com/apache/superset/pull/13596) chore: metric refactor iteration 1 (#13596) (@Yongjie Zhao) +- [13296](https://github.com/apache/superset/pull/13296) chore: migrating some LESS (#13296) (@Evan Rusackas) +- [14169](https://github.com/apache/superset/pull/14169) chore: rename connection errors (#14169) (@Beto Dealmeida) +- [13970](https://github.com/apache/superset/pull/13970) chore: starter make file (#13970) (@Hugh A. Miles II) +- [14088](https://github.com/apache/superset/pull/14088) chore: stop logging `SupersetExceptions` if status < 500 (#14088) (@Hugh A. Miles II) +- [14178](https://github.com/apache/superset/pull/14178) chore: type some SQL Lab components (#14178) (@Erik Ritter) +- [14152](https://github.com/apache/superset/pull/14152) chore: update RELEASING readme (#14152) (@Lily Kuang) +- [13104](https://github.com/apache/superset/pull/13104) chore: update docs for new alerts and reporting feature (#13104) (@leocape) +- [14033](https://github.com/apache/superset/pull/14033) chore: well component to card component(Updating PR #12200) (#14033) (@Bruno Motta) +- [13518](https://github.com/apache/superset/pull/13518) ci: increase page size of GH PR API call (#13518) (@Ville Brofeldt) +- [14350](https://github.com/apache/superset/pull/14350) ci: set remote URL to https and bump sha (#14350) (@Daniel Vaz Gaspar) +- [14211](https://github.com/apache/superset/pull/14211) ci: use git submodules for Chart Actions (#14211) (@jawabuu) +- [13587](https://github.com/apache/superset/pull/13587) create better link for badge (#13587) (@Elizabeth Thompson) +- [13626](https://github.com/apache/superset/pull/13626) dynamic dttm fix for test_convert_dttm (#13626) (@Nikola Gigić) +- [14140](https://github.com/apache/superset/pull/14140) horizontal scroll (#14140) (@AAfghahi) +- [13367](https://github.com/apache/superset/pull/13367) npm audit security updates (#13367) (@Elizabeth Thompson) +- [14443](https://github.com/apache/superset/pull/14443) perf(native-filters): Load native filters after charts (#14443) (@simcha90) +- [14408](https://github.com/apache/superset/pull/14408) perf(native-filters): avoid unnecessary reloading of charts (#14408) (@simcha90) +- [14035](https://github.com/apache/superset/pull/14035) refactor(addslicecontainer): move slice container panel from bootstrap panel to ant-d (#14035) (@Phillip Kelley-Dotson) +- [14366](https://github.com/apache/superset/pull/14366) refactor(db_engine_specs): Removing top-level import of app (#14366) (@John Bodley) +- [13221](https://github.com/apache/superset/pull/13221) refactor(explore): convert ControlPanelsContainer to typescript (#13221) (@Jesse Yang) +- [13437](https://github.com/apache/superset/pull/13437) refactor(native-filters): Move `filtersState` to `dataMask` redux root (#13437) (@simcha90) +- [14441](https://github.com/apache/superset/pull/14441) refactor(native-filters): allow cascading only for filter_select (#14441) (@simcha90) +- [13723](https://github.com/apache/superset/pull/13723) refactor(native-filters): refactor filter bar (#13723) (@simcha90) +- [13983](https://github.com/apache/superset/pull/13983) refactor(native-filters): update dataMask and ExtraFormData schema (#13983) (@simcha90) +- [13137](https://github.com/apache/superset/pull/13137) refactor(self-trigger): Split native filters state (#13137) (@simcha90) +- [14090](https://github.com/apache/superset/pull/14090) refactor(userInfo): userinfo panel to styled component (#14090) (@Phillip Kelley-Dotson) +- [14100](https://github.com/apache/superset/pull/14100) refactor: Boostrap to AntD - Row/Col (#14100) (@Michael S. Molina) +- [14048](https://github.com/apache/superset/pull/14048) refactor: Boostrap to AntD - Tabs (#14048) (@Michael S. Molina) +- [14106](https://github.com/apache/superset/pull/14106) refactor: Bootstrap to AntD - Form - iteration 1 (#14106) (@Michael S. Molina) +- [13996](https://github.com/apache/superset/pull/13996) refactor: Bootstrap to AntD - ListGroup (#13996) (@Michael S. Molina) +- [13989](https://github.com/apache/superset/pull/13989) refactor: Bootstrap to AntD - Slider (#13989) (@Michael S. Molina) +- [13218](https://github.com/apache/superset/pull/13218) refactor: Introduce api resource hooks, fetch owners for chart errors (#13218) (@David Aaron Suddjian) +- [12229](https://github.com/apache/superset/pull/12229) refactor: New Icon system with Enhanced Antd Custom Icon (#12229) (@Geido) +- [13797](https://github.com/apache/superset/pull/13797) refactor: New schedule query button (#13797) (@AAfghahi) +- [13102](https://github.com/apache/superset/pull/13102) refactor: Query search into functional component (#13102) (@AAfghahi) +- [13785](https://github.com/apache/superset/pull/13785) refactor: Revert "refactor: ScheduleQueryButton into functional component (#134… (#13785) (@Hugh A. Miles II) +- [13443](https://github.com/apache/superset/pull/13443) refactor: ScheduleQueryButton into functional component (#13443) (@AAfghahi) +- [13630](https://github.com/apache/superset/pull/13630) refactor: Share sql lab query (#13630) (@AAfghahi) +- [13676](https://github.com/apache/superset/pull/13676) refactor: SouthPane into functional component (#13676) (@AAfghahi) +- [13417](https://github.com/apache/superset/pull/13417) refactor: add set data mask to build query (#13417) (@simcha90) +- [13401](https://github.com/apache/superset/pull/13401) refactor: convert controlUtils to TypeScript (1 of 2) (#13401) (@Jesse Yang) +- [13520](https://github.com/apache/superset/pull/13520) refactor: convert controlUtils to TypeScript (2 of 2) (#13520) (@Jesse Yang) +- [13877](https://github.com/apache/superset/pull/13877) refactor: move CTAS/CVAS field II (#13877) (@Hugh A. Miles II) +- [14309](https://github.com/apache/superset/pull/14309) refactor: reorganize dateFilterUtils.tx (#14309) (@Yongjie Zhao) +- [13174](https://github.com/apache/superset/pull/13174) sql Lab buttons (#13174) (@AAfghahi) +- [13537](https://github.com/apache/superset/pull/13537) switch message content between chart and dashboard AlertReportModal (#13537) (@Lily Kuang) +- [14371](https://github.com/apache/superset/pull/14371) test(maximize-chart): Add tests to maximize chart action (#14371) (@simcha90) +- [14245](https://github.com/apache/superset/pull/14245) test(native-filters): Filter config modal test (#14245) (@simcha90) +- [14098](https://github.com/apache/superset/pull/14098) test(native-filters): add integration tests for filter bar (#14098) (@simcha90) +- [13973](https://github.com/apache/superset/pull/13973) test: Add tests for Dashboard Header and HeaderActionsDropdown components (#13973) (@Geido) +- [13517](https://github.com/apache/superset/pull/13517) test: Adds storybook and tests to AsyncEsmComponent (#13517) (@Michael S. Molina) +- [13547](https://github.com/apache/superset/pull/13547) test: Adds storybook and tests to PopoverDropdown component (#13547) (@Michael S. Molina) +- [13516](https://github.com/apache/superset/pull/13516) test: Adds storybook to AlteredSliceTag component (#13516) (@Michael S. Molina) +- [13406](https://github.com/apache/superset/pull/13406) test: Adds storybook to AnchorLink component (#13406) (@Michael S. Molina) +- [13412](https://github.com/apache/superset/pull/13412) test: Adds storybook to ConfirmStatusChange component (#13412) (@Michael S. Molina) +- [13482](https://github.com/apache/superset/pull/13482) test: Adds storybook to Dropdown and DropdownButton components (#13482) (@Michael S. Molina) +- [13362](https://github.com/apache/superset/pull/13362) test: Adds storybook to EditableTitle component (#13362) (@Michael S. Molina) +- [13407](https://github.com/apache/superset/pull/13407) test: Adds storybook to FormRow component (#13407) (@Michael S. Molina) +- [13415](https://github.com/apache/superset/pull/13415) test: Adds storybook to IconTooltip component (#13415) (@Michael S. Molina) +- [13410](https://github.com/apache/superset/pull/13410) test: Adds storybook to SearchInput component (#13410) (@Michael S. Molina) +- [13479](https://github.com/apache/superset/pull/13479) test: Adds storybook to Switch component (#13479) (@Michael S. Molina) +- [13343](https://github.com/apache/superset/pull/13343) test: Adds storybook to Timer component (#13343) (@Michael S. Molina) +- [13457](https://github.com/apache/superset/pull/13457) test: Adds tests and storybook to CertifiedIcon component (#13457) (@Michael S. Molina) +- [13438](https://github.com/apache/superset/pull/13438) test: Adds tests and storybook to PopoverSection component (#13438) (@Michael S. Molina) +- [13409](https://github.com/apache/superset/pull/13409) test: Adds tests and storybook to RefreshLabel (#13409) (@Michael S. Molina) +- [13705](https://github.com/apache/superset/pull/13705) test: Adds tests for the FixedOrMetricControl component (#13705) (@Michael S. Molina) +- [13299](https://github.com/apache/superset/pull/13299) test: Adds tests to TableLoader component (#13299) (@Michael S. Molina) +- [13319](https://github.com/apache/superset/pull/13319) test: Adds tests to URLShortLinkButton component (#13319) (@Michael S. Molina) +- [13650](https://github.com/apache/superset/pull/13650) test: Adds tests to dnd controls (#13650) (@Michael S. Molina) +- [13664](https://github.com/apache/superset/pull/13664) test: Adds tests to the AdvancedFrame component (#13664) (@Michael S. Molina) +- [13748](https://github.com/apache/superset/pull/13748) test: Adds tests to the AnnotationLayer component (#13748) (@Michael S. Molina) +- [13933](https://github.com/apache/superset/pull/13933) test: Adds tests to the CssEditor component (#13933) (@Michael S. Molina) +- [13675](https://github.com/apache/superset/pull/13675) test: Adds tests to the CustomFrame component (#13675) (@Michael S. Molina) +- [13729](https://github.com/apache/superset/pull/13729) test: Adds tests to the OptionControls component (#13729) (@Michael S. Molina) +- [13892](https://github.com/apache/superset/pull/13892) test: Adds tests to the PublishedStatus component (#13892) (@Michael S. Molina) +- [13712](https://github.com/apache/superset/pull/13712) test: Adds tests to the TimeSeriesColumnControl component (#13712) (@Michael S. Molina) +- [13919](https://github.com/apache/superset/pull/13919) test: Adds tests to the UndoRedoKeyListeners component (#13919) (@Michael S. Molina) +- [13887](https://github.com/apache/superset/pull/13887) test: Adds tests to the filter scope components (#13887) (@Michael S. Molina) +- [13787](https://github.com/apache/superset/pull/13787) test: AdhocMetricEditPopover (#13787) (@Bruno Motta) +- [13318](https://github.com/apache/superset/pull/13318) test: CacheLabel (#13318) (@Bruno Motta) +- [13656](https://github.com/apache/superset/pull/13656) test: CollectionControl (#13656) (@Bruno Motta) +- [13549](https://github.com/apache/superset/pull/13549) test: CronPicker (#13549) (@Bruno Motta) +- [13875](https://github.com/apache/superset/pull/13875) test: CrossFilterScopingForm (#13875) (@Bruno Motta) +- [13668](https://github.com/apache/superset/pull/13668) test: DataTableControl (#13668) (@Bruno Motta) +- [13736](https://github.com/apache/superset/pull/13736) test: DataTablesPane (#13736) (@Bruno Motta) +- [13581](https://github.com/apache/superset/pull/13581) test: DatabaseSelector (#13581) (@Bruno Motta) +- [13605](https://github.com/apache/superset/pull/13605) test: DatasourceControl (#13605) (@Bruno Motta) +- [13627](https://github.com/apache/superset/pull/13627) test: DatasourcePanelDragWrapper (#13627) (@Bruno Motta) +- [13750](https://github.com/apache/superset/pull/13750) test: DisplayQueryButton (#13750) (@Bruno Motta) +- [13745](https://github.com/apache/superset/pull/13745) test: Enhance tests and directory structure for FilterControl components (#13745) (@Geido) +- [13276](https://github.com/apache/superset/pull/13276) test: ErrorBoundary dedicated directory and tests (#13276) (@Geido) +- [13358](https://github.com/apache/superset/pull/13358) test: ErrorMessage components tests (#13358) (@Geido) +- [13753](https://github.com/apache/superset/pull/13753) test: FilterBoxItemControl (#13753) (@Bruno Motta) +- [14028](https://github.com/apache/superset/pull/14028) test: FilterSets-utils (#14028) (@Bruno Motta) +- [13548](https://github.com/apache/superset/pull/13548) test: Fixes PropertiesModal_spec (#13548) (@Michael S. Molina) +- [13270](https://github.com/apache/superset/pull/13270) test: FormLabel dedicated directory and tests (#13270) (@Geido) +- [13302](https://github.com/apache/superset/pull/13302) test: Menu component tests (#13302) (@Geido) +- [13277](https://github.com/apache/superset/pull/13277) test: Pagination component (#13277) (@Bruno Motta) +- [13818](https://github.com/apache/superset/pull/13818) test: PropertiesModal (#13818) (@Bruno Motta) +- [13931](https://github.com/apache/superset/pull/13931) test: PropertiesModal (Dashboard) (#13931) (@Bruno Motta) +- [13799](https://github.com/apache/superset/pull/13799) test: Refactor and enhance tests for the Explore DatasourcePanel Component (#13799) (@Geido) +- [13638](https://github.com/apache/superset/pull/13638) test: SelectAsyncControl (#13638) (@Bruno Motta) +- [13860](https://github.com/apache/superset/pull/13860) test: ShareMenuItems (#13860) (@Bruno Motta) +- [13888](https://github.com/apache/superset/pull/13888) test: SliceHeader (#13888) (@Bruno Motta) +- [13895](https://github.com/apache/superset/pull/13895) test: SliceHeaderControls (#13895) (@Bruno Motta) +- [13937](https://github.com/apache/superset/pull/13937) test: Tab (#13937) (@Bruno Motta) +- [13583](https://github.com/apache/superset/pull/13583) test: TableCollection (#13583) (@Bruno Motta) +- [13941](https://github.com/apache/superset/pull/13941) test: Tabs (#13941) (@Bruno Motta) +- [13502](https://github.com/apache/superset/pull/13502) test: Tests and Storybook entry for the IndeterminateCheckbox Component (#13502) (@Geido) +- [13501](https://github.com/apache/superset/pull/13501) test: Tests and Storybook entry for the TableView Component (#13501) (@Geido) +- [13508](https://github.com/apache/superset/pull/13508) test: Tests and dedicated directory for the SupersetResourceSelect Component (#13508) (@Geido) +- [13916](https://github.com/apache/superset/pull/13916) test: Tests audit for the Dashboard FilterBar (#13916) (@Geido) +- [13286](https://github.com/apache/superset/pull/13286) test: Tests for DeleteModal component (#13286) (@Bruno Motta) +- [13305](https://github.com/apache/superset/pull/13305) test: Tests for OmniContainer (#13305) (@Bruno Motta) +- [13513](https://github.com/apache/superset/pull/13513) test: Tests, dedicated directory and Storybook for the Badge component (#13513) (@Geido) +- [13534](https://github.com/apache/superset/pull/13534) test: Tests, dedicated directory and Storybook for the ProgressBar component (#13534) (@Geido) +- [13751](https://github.com/apache/superset/pull/13751) test: changing logger.exception to logger.errors in databases api (#13751) (@AAfghahi) +- [13719](https://github.com/apache/superset/pull/13719) test: exploreUtils (#13719) (@Bruno Motta) +- [13329](https://github.com/apache/superset/pull/13329) test: optimize codecov config (#13329) (@Jesse Yang) +- [13984](https://github.com/apache/superset/pull/13984) test: prevent flaky frontend unit test case (#13984) (@Yongjie Zhao) +- [13320](https://github.com/apache/superset/pull/13320) test: tests for component FaveStar (#13320) (@Bruno Motta) +- [13555](https://github.com/apache/superset/pull/13555) test: useChangeEffect (#13555) (@Bruno Motta) +- [13554](https://github.com/apache/superset/pull/13554) test: usePrevious hook (#13554) (@Bruno Motta) +- [13873](https://github.com/apache/superset/pull/13873) tests for FilterIndicator (#13873) (@Bruno Motta) +- [13896](https://github.com/apache/superset/pull/13896) tests for function handleScroll (#13896) (@Bruno Motta) +- [14363](https://github.com/apache/superset/pull/14363) update default cron settings for new alerts and reports (#14363) (@Sam Faber-Manning) +- [13463](https://github.com/apache/superset/pull/13463) update test (#13463) (@Lily Kuang) +- [13897](https://github.com/apache/superset/pull/13897) updates load_examples to load-examples (#13897) (@Alex Simoes) +- [14009](https://github.com/apache/superset/pull/14009) use dynamic time_grains for schema (#14009) (@Elizabeth Thompson) +- [13424](https://github.com/apache/superset/pull/13424) use semver for badge sort (#13424) (@Elizabeth Thompson) ### 1.1 (Wed Feb 24 12:08:28 2021 +0200) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8a20d1c1e04..ff9475858f83 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,6 +77,8 @@ little bit helps, and credit will always be given. - [Python Testing](#python-testing) - [Frontend Testing](#frontend-testing) - [Integration Testing](#integration-testing) + - [Debugging Server App](#debugging-server-app) + - [Debugging Server App in Kubernetes Environment](#debugging-server-app-in-kubernetes-environment) - [Storybook](#storybook) - [Translating](#translating) - [Enabling language selection](#enabling-language-selection) @@ -418,22 +420,23 @@ For example, the image referenced above actually lives in `superset-frontend/ima #### OS Dependencies -Make sure your machine meets the [OS dependencies](https://superset.apache.org/docs/installation/installing-superset-from-scratch#os-dependencies) before following these steps. +Make sure your machine meets the [OS dependencies](https://superset.apache.org/docs/installation/installing-superset-from-scratch#os-dependencies) before following these steps. +You also need to install MySQL or [MariaDB](https://mariadb.com/downloads). -Ensure Python versions >3.7, Then proceed with: +Ensure that you are using Python version 3.7 or 3.8, then proceed with: -```bash +````bash # Create a virtual environment and activate it (recommended) python3 -m venv venv # setup a python3 virtualenv source venv/bin/activate # Install external dependencies -pip install -r requirements/local.txt +pip install -r requirements/testing.txt # Install Superset in editable (development) mode pip install -e . -# Create an admin user in your metadata database +# Create an admin user in your metadata database (use `admin` as username to be able to load the examples) superset fab create-admin # Initialize the database @@ -442,13 +445,15 @@ superset db upgrade # Create default roles and permissions superset init -# Load some data to play with (you must create an Admin user with the username `admin` for this command to work) +# Load some data to play with. +# Note: you MUST have previously created an admin user with the username `admin` for this command to work. superset load-examples # Start the Flask dev web server from inside your virtualenv. -# Note that your page may not have css at this point. +# Note that your page may not have CSS at this point. # See instructions below how to build the front-end assets. FLASK_ENV=development superset run -p 8088 --with-threads --reload --debugger +``` Or you can install via our Makefile @@ -465,7 +470,7 @@ $ make superset # Setup pre-commit only $ make pre-commit -``` +```` **Note: the FLASK_APP env var should not need to be set, as it's currently controlled via `.flaskenv`, however if needed, it should be set to `superset.app:create_app()`** @@ -473,6 +478,17 @@ via `.flaskenv`, however if needed, it should be set to `superset.app:create_app If you have made changes to the FAB-managed templates, which are not built the same way as the newer, React-powered front-end assets, you need to start the app without the `--with-threads` argument like so: `FLASK_ENV=development superset run -p 8088 --reload --debugger` +#### Dependencies + +If you add a new requirement or update an existing requirement (per the `install_requires` section in `setup.py`) you must recompile (freeze) the Python dependencies to ensure that for CI, testing, etc. the build is deterministic. This can be achieved via, + +```bash +$ python3 -m venv venv +$ source venv/bin/activate +$ python3 -m pip install -r requirements/integration.txt +$ pip-compile-multi --no-upgrade +``` + #### Logging to the browser console This feature is only available on Python 3. When debugging your application, you can have the server logs sent directly to the browser console using the [ConsoleLog](https://github.com/betodealmeida/consolelog) package. You need to mutate the app, by adding the following to your `config.py` or `superset_config.py`: @@ -517,6 +533,7 @@ nvm use --lts ``` Or if you use the default macOS starting with Catalina shell `zsh`, try: + ```zsh sh -c "$(curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.0/install.sh)" ``` @@ -596,7 +613,7 @@ FEATURE_FLAGS = { } ``` -If you want to use the same flag in the client code, also add it to the FeatureFlag TypeScript enum in `superset-frontend/src/featureFlags.ts`. For example, +If you want to use the same flag in the client code, also add it to the FeatureFlag TypeScript enum in [@superset-ui/core](https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/utils/featureFlags.ts). For example, ```typescript export enum FeatureFlag { @@ -628,6 +645,7 @@ tox -e pre-commit ``` Or by running pre-commit manually: + ```bash pre-commit run --all-files ``` @@ -655,7 +673,7 @@ npm run lint ### Python -Parameters in the `config.py` (which are accessible via the Flask app.config dictionary) are assummed to always be defined and thus should be accessed directly via, +Parameters in the `config.py` (which are accessible via the Flask app.config dictionary) are assumed to always be defined and thus should be accessed directly via, ```python blueprints = app.config["BLUEPRINTS"] @@ -740,6 +758,7 @@ There is also a utility script included in the Superset codebase to run python t found here](https://github.com/apache/superset/tree/master/scripts/tests) To run all tests for example, run this script from the root directory: + ```bash scripts/tests/run.sh ``` @@ -764,7 +783,7 @@ npm run test -- path/to/file.js We use [Cypress](https://www.cypress.io/) for integration tests. Tests can be run by `tox -e cypress`. To open Cypress and explore tests first setup and run test server: ```bash -export SUPERSET_CONFIG=tests.superset_test_config +export SUPERSET_CONFIG=tests.integration_tests.superset_test_config export SUPERSET_TESTENV=true export ENABLE_REACT_CRUD_VIEWS=true export CYPRESS_BASE_URL="http://localhost:8081" @@ -851,6 +870,7 @@ superset: ``` Start Superset as usual + ```bash docker-compose up ``` @@ -858,12 +878,14 @@ docker-compose up Install the required libraries and packages to the docker container Enter the superset_app container + ```bash docker exec -it superset_app /bin/bash root@39ce8cf9d6ab:/app# ``` Run the following commands inside the container + ```bash apt update apt install -y gdb @@ -883,11 +905,13 @@ root 10 6 7 14:09 ? 00:00:07 /usr/local/bin/python /usr/bin/f ``` Inject debugpy into the running Flask process. In this case PID 6. + ```bash python3 -m debugpy --listen 0.0.0.0:5678 --pid 6 ``` Verify that debugpy is listening on port 5678 + ```bash netstat -tunap @@ -898,6 +922,7 @@ tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN ``` You are now ready to attach a debugger to the process. Using VSCode you can configure a launch configuration file .vscode/launch.json like so. + ``` { "version": "0.2.0", @@ -923,7 +948,6 @@ You are now ready to attach a debugger to the process. Using VSCode you can conf VSCode will not stop on breakpoints right away. We've attached to PID 6 however it does not yet know of any sub-processes. In order to "wakeup" the debugger you need to modify a python file. This will trigger Flask to reload the code and create a new sub-process. This new sub-process will be detected by VSCode and breakpoints will be activated. - ### Debugging Server App in Kubernetes Environment To debug Flask running in POD inside kubernetes cluster. You'll need to make sure the pod runs as root and is granted the SYS_TRACE capability.These settings should not be used in production environments. @@ -948,7 +972,6 @@ kubectl port-forward pod/superset-<some random id> 5678:5678 You can now launch your VSCode debugger with the same config as above. VSCode will connect to to 127.0.0.1:5678 which is forwarded by kubectl to your remote kubernetes POD. - ### Storybook Superset includes a [Storybook](https://storybook.js.org/) to preview the layout/styling of various Superset components, and variations thereof. To open and view the Storybook: @@ -1264,7 +1287,7 @@ To do this, you'll need to: - Start up a celery worker ```shell script - celery worker --app=superset.tasks.celery_app:app -Ofair + celery --app=superset.tasks.celery_app:app worker -Ofair ``` Note that: @@ -1364,6 +1387,7 @@ Note not all fields are correctly catagorized. The fields vary based on visualiz | Field | Type | Notes | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------- | | `adhoc_filters` | _array(object)_ | The **Filters** widget | +| `extra_filters` | _array(object)_ | Another pathway to the **Filters** widget.<br/>It is generally used to pass dashboard filter parameters to a chart.<br/>It can be used for appending additional filters to a chart that has been saved with its own filters on an ad-hoc basis if the chart is being used as a standalone widget.<br/><br/>For implementation examples see : [utils test.py](https://github.com/apache/superset/blob/66a4c94a1ed542e69fe6399bab4c01d4540486cf/tests/utils_tests.py#L181)<br/>For insight into how superset processes the contents of this parameter see: [exploreUtils/index.js](https://github.com/apache/superset/blob/93c7f5bb446ec6895d7702835f3157426955d5a9/superset-frontend/src/explore/exploreUtils/index.js#L159) | | `columns` | _array(string)_ | The **Breakdowns** widget | | `groupby` | _array(string)_ | The **Group by** or **Series** widget | | `limit` | _number_ | The **Series Limit** widget | @@ -1410,7 +1434,6 @@ Note the `y_axis_format` is defined under various section for some charts. | `default_filters` | _N/A_ | | | `entity` | _N/A_ | | | `expanded_slices` | _N/A_ | | -| `extra_filters` | _N/A_ | | | `filter_immune_slice_fields` | _N/A_ | | | `filter_immune_slices` | _N/A_ | | | `flt_col_0` | _N/A_ | | diff --git a/Dockerfile b/Dockerfile index 6ca89e889fd7..c73e6cad6198 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ RUN /frontend-mem-nag.sh \ # Next, copy in the rest and let webpack do its thing COPY ./superset-frontend /app/superset-frontend -# This is BY FAR the most expensive step (thanks Terser!) +# This seems to be the most expensive step RUN cd /app/superset-frontend \ && npm run ${BUILD_CMD} \ && rm -rf node_modules diff --git a/INSTALL.md b/INSTALL.md index c96ba33a8a65..afe091241f8a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,6 +18,6 @@ under the License. --> # INSTALL / BUILD instructions for Apache Superset -At this time, the docker file at RELEASING/Dockerfile.from_tarball +At this time, the docker file at RELEASING/Dockerfile.from_local_tarball constitutes the recipe on how to get to a working release from a source release tarball. diff --git a/Makefile b/Makefile index a51b96fdb23f..ef0d3edbec13 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ # limitations under the License. # +# Python version installed; we need 3.8 or 3.7 +PYTHON=`command -v python3.8 || command -v python3.7` + .PHONY: install superset venv pre-commit install: superset pre-commit @@ -55,12 +58,13 @@ update-py: update-js: # Install js packages - cd superset-frontend; npm install + cd superset-frontend; npm ci venv: # Create a virtual environment and activate it (recommended) - python3 -m venv venv # setup a python3 virtualenv - source venv/bin/activate + if ! [ -x "${PYTHON}" ]; then echo "You need Python 3.7 or 3.8 installed"; exit 1; fi + test -d venv || ${PYTHON} -m venv venv # setup a python3 virtualenv + . venv/bin/activate pre-commit: # setup pre commit dependencies @@ -72,5 +76,14 @@ format: py-format js-format py-format: pre-commit pre-commit run black --all-files +py-lint: pre-commit + pylint -j 0 superset + js-format: cd superset-frontend; npm run prettier + +flask-app: + flask run -p 8088 --with-threads --reload --debugger + +node-app: + cd superset-frontend; npm run dev-server diff --git a/README.md b/README.md index d1cb206a65c0..a2992d1a341f 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Here are some of the major database solutions that are supported: <img src="superset-frontend/images/monet-db.png" alt="monet-db" border="0" width="106" height="46" /> <img src="superset-frontend/images/apache-kylin.png" alt="apache-kylin" border="0" width="56" height="64"/> <img src="superset-frontend/images/hologres.png" alt="hologres" border="0" width="71" height="64"/> + <img src="superset-frontend/images/netezza.png" alt="netezza" border="0" width="64" height="64"/> </p> **A more comprehensive list of supported databases** along with the configuration instructions can be found diff --git a/RELEASING/README.md b/RELEASING/README.md index 3f5fc291a284..a33835893737 100644 --- a/RELEASING/README.md +++ b/RELEASING/README.md @@ -29,7 +29,9 @@ on the Superset Slack. People crafting releases and those interested in partaking in the process should join the channel. ## Release notes for recent releases -- [1.0.0](release-notes-1-0/README.md) +- [1.2](release-notes-1-2/README.md) +- [1.1](release-notes-1-1/README.md) +- [1.0](release-notes-1-0/README.md) - [0.38](release-notes-0-38/README.md) ## Release setup (First Time Only) @@ -56,6 +58,9 @@ need to be done at every release. # Commit the changes svn commit -m "Add PGP keys of new Superset committer" + + # push the changes + svn update ``` ## Setting up the release environment (do every time) @@ -194,6 +199,7 @@ Now let's ship this RC into svn's dev folder cd ~/svn/superset_dev/ svn add ${SUPERSET_VERSION_RC} svn commit -m "Release ${SUPERSET_VERSION_RC}" +svn update ``` ### Build and test from SVN source tarball @@ -267,6 +273,7 @@ cd ~/svn/superset/ for f in ${SUPERSET_VERSION}/*; do mv "$f" "${f/${SUPERSET_VERSION_RC}/${SUPERSET_VERSION}}"; done svn add ${SUPERSET_VERSION} svn commit -m "Release ${SUPERSET_VERSION}" +svn update ``` Then tag the final release: diff --git a/RELEASING/changelog.py b/RELEASING/changelog.py index 109ff738ae90..e9ff2de041a2 100644 --- a/RELEASING/changelog.py +++ b/RELEASING/changelog.py @@ -24,6 +24,7 @@ from typing import Any, Dict, Iterator, List, Optional, Union import click +from click.core import Context try: from github import BadCredentialsException, Github, PullRequest, Repository @@ -50,7 +51,7 @@ class GitLog: author_email: str = "" def __eq__(self, other: object) -> bool: - """ A log entry is considered equal if it has the same PR number """ + """A log entry is considered equal if it has the same PR number""" if isinstance(other, self.__class__): return other.pr_number == self.pr_number return False @@ -170,7 +171,7 @@ def _get_changelog_version_head(self) -> str: def _parse_change_log( self, changelog: Dict[str, str], pr_info: Dict[str, str], github_login: str, - ): + ) -> None: formatted_pr = ( f"- [#{pr_info.get('id')}]" f"(https://github.com/{SUPERSET_REPO}/pull/{pr_info.get('id')}) " @@ -324,8 +325,8 @@ def print_title(message: str) -> None: @click.pass_context @click.option("--previous_version", help="The previous release version", required=True) @click.option("--current_version", help="The current release version", required=True) -def cli(ctx, previous_version: str, current_version: str) -> None: - """ Welcome to change log generator """ +def cli(ctx: Context, previous_version: str, current_version: str) -> None: + """Welcome to change log generator""" previous_logs = GitLogs(previous_version) current_logs = GitLogs(current_version) previous_logs.fetch() @@ -337,7 +338,7 @@ def cli(ctx, previous_version: str, current_version: str) -> None: @cli.command("compare") @click.pass_obj def compare(base_parameters: BaseParameters) -> None: - """ Compares both versions (by PR) """ + """Compares both versions (by PR)""" previous_logs = base_parameters.previous_logs current_logs = base_parameters.current_logs print_title( @@ -369,7 +370,7 @@ def compare(base_parameters: BaseParameters) -> None: def change_log( base_parameters: BaseParameters, csv: str, access_token: str, risk: bool ) -> None: - """ Outputs a changelog (by PR) """ + """Outputs a changelog (by PR)""" previous_logs = base_parameters.previous_logs current_logs = base_parameters.current_logs previous_diff_logs = previous_logs.diff(current_logs) @@ -383,12 +384,12 @@ def change_log( with open(csv, "w") as csv_file: log_items = list(logs) field_names = log_items[0].keys() - writer = lib_csv.DictWriter( + writer = lib_csv.DictWriter( # type: ignore csv_file, delimiter=",", quotechar='"', quoting=lib_csv.QUOTE_ALL, - fieldnames=field_names, + fieldnames=field_names, # type: ignore ) writer.writeheader() for log in logs: diff --git a/RELEASING/release-notes-1-2/README.md b/RELEASING/release-notes-1-2/README.md new file mode 100644 index 000000000000..2ae0a728f34f --- /dev/null +++ b/RELEASING/release-notes-1-2/README.md @@ -0,0 +1,122 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +# Release Notes for Superset 1.2 + +Superset 1.2 continues the Apache ECharts migration by introducing several chart types. It also brings with it tons of user experience improvements, API improvements, bug fixes, and continued development of experimental features included in previous releases. Keep reading for more details on these categories: + +- [**User Experience**](#user-experience) +- [**Dashboard Level Security**](#dashboard-level-security) +- [**Database Connectivity**](#database-connectivity) +- [**Developer Experience**](#developer-experience) +- [**PR Highlights**](#pr-highlights) +- [**Breaking Changes and Full Changelog**](#breaking-changes-and-full-changelog) + +# User Experience + +The migration to Apache ECharts continues by introducing several new high-quality visualizations in this release. + +The mixed time-series multi chart allows different kinds of time-series visualization to be overlayed. + +![mixed time series](media/time_series_multichart.png) + +The radar chart provides a good way to compare two or more groups over various features of interest. + +![radar chart](media/radar_chart.png) + +By popular demand, we have introduced a new and improved version of the pivot table visualization as well. + +![pivot table](media/pivot_table_v2.png) + +Several UI tweaks in Explore and SQL Lab made it into this release as well, including new buttons and menu options to make common workflows easier, as well as more communicative error messages, particularly in the database connection menus. + +The dashboard native filter feature, [while still behind a feature flag in this release](https://github.com/apache/superset/blob/master/RELEASING/release-notes-1-0/README.md#feature-flags), has received plenty of new functionality and is closer than ever to being ready for prime-time. This feature provides a way to apply and manipulate filters over many charts at the dashboard level. 1.2 adds more controls, more options for aggregations, and better support for temporal filters, among other things. + +![Native Filter](media/native_filters.png) + +![Native Filter](media/native_filters_temporal.png) + +Last but not least, the alerts and reports feature and its dependencies have been added to the [docker-compose](https://superset.apache.org/docs/installation/installing-superset-using-docker-compose) setup, making it easier to use outside of well-supported enterprise deployments. + + +# Dashboard Level Security + +Superset has so far relied on a role-based access system implemented at the dataset level. While this provides granular security options that satisfy many use cases, some organizations need more options. [SIP-51](https://github.com/apache/superset/issues/10408) lays out a vision for dashboard-level role-based access control as a fully backwards compatible extension to Superset's security options. + +The 1.1 release saw steps taken in the direction of this vision, and 1.2 builds on that with new permissions for sharing charts and dashboards that can be assigned to roles. **Note that this functionality is still experimental and hidden behind a feature flag as of 1.2.** + +![dashboard rbac](media/dashboard_rbac.png) + +# Database Connectivity +The 1.2 release adds support for [Crate DB](https://github.com/apache/superset/pull/13152) and the [Databricks DB](https://github.com/apache/superset/pull/13682) engine spec. + + +# Developer Experience +Expanding the API has been an ongoing effort, and 1.2 introduces several new API routes to allow developers to get available databases, get a given dashboard's charts, and import saved queries, among other things. + +# PR Highlights + +**New Charts and User Experience** + +- [14197](https://github.com/apache/superset/pull/14197) feat(viz): add mixed and radar chart (#14197) (@Ville Brofeldt) +- [14187](https://github.com/apache/superset/pull/14187) Enable the new pivot table (#14187) (@Kamil Gabryjelski) +- [13210](https://github.com/apache/superset/pull/13210) feat(explore): ColumnSelectControl with drag-and-drop (#13210) (@Yongjie Zhao) +- [13598](https://github.com/apache/superset/pull/13598) feat(explore): Drag and drop UX improvements (#13598) (@Kamil Gabryjelski) +- [13294](https://github.com/apache/superset/pull/13294) feat(explore): Postgres datatype conversion (#13294) (@Nikola Gigić) +- [13758](https://github.com/apache/superset/pull/13758) feat(explore): adhoc column formatting for Table chart (#13758) (@Jesse Yang) + +**Progress On Dashboard Native Filters** + +- [13726](https://github.com/apache/superset/pull/13726) feat(native-filters): Add default first value to select filter (#13726) (@simcha90) +- [14461](https://github.com/apache/superset/pull/14461) feat(native-filters): Auto apply changes in FiltersConfigModal (#14461) (@simcha90) +- [13507](https://github.com/apache/superset/pull/13507) feat(native-filters): Filter set tabs (#13507) (@simcha90) +- [14313](https://github.com/apache/superset/pull/14313) feat(native-filters): Implement adhoc filters and time picker in Range and Select native filters (#14313) (@Kamil Gabryjelski) +- [14261](https://github.com/apache/superset/pull/14261) feat(native-filters): Show/Hide filter bar by metdata ff (#14261) (@simcha90) +- [13506](https://github.com/apache/superset/pull/13506) feat(native-filters): Update filter bar buttons (#13506) (@simcha90) +- [14374](https://github.com/apache/superset/pull/14374) feat(native-filters): Use datasets in dashboard as default options for native filters (#14374) (@Kamil Gabryjelski) +- [14314](https://github.com/apache/superset/pull/14314) feat(native-filters): add option to create value in select filter (#14314) (@Ville Brofeldt) +- [14346](https://github.com/apache/superset/pull/14346) feat(native-filters): add optional sort metric to select filter (#14346) (@Ville Brofeldt) +- [14375](https://github.com/apache/superset/pull/14375) feat(native-filters): add refresh button to default value picker (#14375) (@Ville Brofeldt) +- [13569](https://github.com/apache/superset/pull/13569) feat(native-filters): add sort option to select filter (#13569) (@Ville Brofeldt) +- [13622](https://github.com/apache/superset/pull/13622) feat(native-filters): add temporal support to select filter (#13622) (@Ville Brofeldt) +- [13484](https://github.com/apache/superset/pull/13484) feat(native-filters): add timegrain and column filter (#13484) (@Ville Brofeldt) +- [14312](https://github.com/apache/superset/pull/14312) feat(native-filters): add tooltip to control values (#14312) (@Ville Brofeldt) +- [14217](https://github.com/apache/superset/pull/14217) feat(native-filters): select group by support (#14217) (@Amit Miran) + +**Progress On Dashboard Level Access** + +- [13145](https://github.com/apache/superset/pull/13145) feat(dashboard_rbac): manage roles for dashboard (#13145) (@simcha90) +- [13992](https://github.com/apache/superset/pull/13992) feat(dashboard_rbac): provide data access based on dashboard access (#13992) (@Amit Miran) +- [#12865](https://github.com/apache/superset/pull/12865) feat(dashboard_rbac): dashboards API support for roles create/update + roles validation (@amitmiran137) + + +**Improvements to Developer Experience** + +- [14208](https://github.com/apache/superset/pull/14208) feat: add endpoint to fetch available DBs (#14208) (@Beto Dealmeida) +- [13331](https://github.com/apache/superset/pull/13331) fix(query-object): extra time-range-endpoints (#13331) (@John Bodley) +- [13893](https://github.com/apache/superset/pull/13893) feat: create backend routes and API for importing saved queries (#13893) (@AAfghahi) +- [13960](https://github.com/apache/superset/pull/13960) feat: initial work to make v1 API compatible with SIP-40 and SIP-41 (#13960) (@Beto Dealmeida) +- [13444](https://github.com/apache/superset/pull/13444) fix: API to allow importing old exports (JSON/YAML) (#13444) (@Beto Dealmeida) +- [13893](https://github.com/apache/superset/pull/13893) feat: create backend routes and API for importing saved queries (#13893) (@AAfghahi) + + +## Breaking Changes and Full Changelog + +- To see the complete changelog in this release, head to [CHANGELOG.MD](https://github.com/apache/superset/blob/master/CHANGELOG.md). +- In line with the semantic versioning scheme adopted by the community, 1.2.0 does not contain any backwards incompatible changes. diff --git a/RELEASING/release-notes-1-2/media/dashboard_rbac.png b/RELEASING/release-notes-1-2/media/dashboard_rbac.png new file mode 100644 index 000000000000..01e7678313db Binary files /dev/null and b/RELEASING/release-notes-1-2/media/dashboard_rbac.png differ diff --git a/RELEASING/release-notes-1-2/media/native_filters.png b/RELEASING/release-notes-1-2/media/native_filters.png new file mode 100644 index 000000000000..275a2c6cb831 Binary files /dev/null and b/RELEASING/release-notes-1-2/media/native_filters.png differ diff --git a/RELEASING/release-notes-1-2/media/native_filters_temporal.png b/RELEASING/release-notes-1-2/media/native_filters_temporal.png new file mode 100644 index 000000000000..8d7c54974329 Binary files /dev/null and b/RELEASING/release-notes-1-2/media/native_filters_temporal.png differ diff --git a/RELEASING/release-notes-1-2/media/pivot_table_v2.png b/RELEASING/release-notes-1-2/media/pivot_table_v2.png new file mode 100644 index 000000000000..88b13d87d056 Binary files /dev/null and b/RELEASING/release-notes-1-2/media/pivot_table_v2.png differ diff --git a/RELEASING/release-notes-1-2/media/radar_chart.png b/RELEASING/release-notes-1-2/media/radar_chart.png new file mode 100644 index 000000000000..b532f145cacb Binary files /dev/null and b/RELEASING/release-notes-1-2/media/radar_chart.png differ diff --git a/RELEASING/release-notes-1-2/media/time_series_multichart.png b/RELEASING/release-notes-1-2/media/time_series_multichart.png new file mode 100644 index 000000000000..3c3de4e797d8 Binary files /dev/null and b/RELEASING/release-notes-1-2/media/time_series_multichart.png differ diff --git a/RELEASING/send_email.py b/RELEASING/send_email.py index 409672e3461a..ddf823f1c92b 100755 --- a/RELEASING/send_email.py +++ b/RELEASING/send_email.py @@ -17,7 +17,9 @@ # import smtplib import ssl -from typing import List +from typing import Any, Dict, List, Optional + +from click.core import Context try: import jinja2 @@ -50,7 +52,7 @@ def send_email( sender_email: str, receiver_email: str, message: str, -): +) -> None: """ Send a simple text email (SMTP) """ @@ -61,7 +63,7 @@ def send_email( server.sendmail(sender_email, receiver_email, message) -def render_template(template_file: str, **kwargs) -> str: +def render_template(template_file: str, **kwargs: Any) -> str: """ Simple render template based on named parameters @@ -73,7 +75,9 @@ def render_template(template_file: str, **kwargs) -> str: return template.render(kwargs) -def inter_send_email(username, password, sender_email, receiver_email, message): +def inter_send_email( + username: str, password: str, sender_email: str, receiver_email: str, message: str +) -> None: print("--------------------------") print("SMTP Message") print("--------------------------") @@ -102,16 +106,16 @@ def inter_send_email(username, password, sender_email, receiver_email, message): class BaseParameters(object): def __init__( - self, email=None, username=None, password=None, version=None, version_rc=None - ): + self, email: str, username: str, password: str, version: str, version_rc: str, + ) -> None: self.email = email self.username = username self.password = password self.version = version self.version_rc = version_rc - self.template_arguments = dict() + self.template_arguments: Dict[str, Any] = {} - def __repr__(self): + def __repr__(self) -> str: return f"Apache Credentials: {self.email}/{self.username}/{self.version}/{self.version_rc}" @@ -133,8 +137,15 @@ def __repr__(self): ) @click.option("--version", envvar="SUPERSET_VERSION") @click.option("--version_rc", envvar="SUPERSET_VERSION_RC") -def cli(ctx, apache_email, apache_username, apache_password, version, version_rc): - """ Welcome to releasing send email CLI interface! """ +def cli( + ctx: Context, + apache_email: str, + apache_username: str, + apache_password: str, + version: str, + version_rc: str, +) -> None: + """Welcome to releasing send email CLI interface!""" base_parameters = BaseParameters( apache_email, apache_username, apache_password, version, version_rc ) @@ -155,7 +166,7 @@ def cli(ctx, apache_email, apache_username, apache_password, version, version_rc prompt="The receiver email (To:)", ) @click.pass_obj -def vote_pmc(base_parameters, receiver_email): +def vote_pmc(base_parameters: BaseParameters, receiver_email: str) -> None: template_file = "email_templates/vote_pmc.j2" base_parameters.template_arguments["receiver_email"] = receiver_email message = render_template(template_file, **base_parameters.template_arguments) @@ -202,13 +213,13 @@ def vote_pmc(base_parameters, receiver_email): ) @click.pass_obj def result_pmc( - base_parameters, - receiver_email, - vote_bindings, - vote_nonbindings, - vote_negatives, - vote_thread, -): + base_parameters: BaseParameters, + receiver_email: str, + vote_bindings: str, + vote_nonbindings: str, + vote_negatives: str, + vote_thread: str, +) -> None: template_file = "email_templates/result_pmc.j2" base_parameters.template_arguments["receiver_email"] = receiver_email base_parameters.template_arguments["vote_bindings"] = string_comma_to_list( @@ -239,7 +250,7 @@ def result_pmc( prompt="The receiver email (To:)", ) @click.pass_obj -def announce(base_parameters, receiver_email): +def announce(base_parameters: BaseParameters, receiver_email: str) -> None: template_file = "email_templates/announce.j2" base_parameters.template_arguments["receiver_email"] = receiver_email message = render_template(template_file, **base_parameters.template_arguments) diff --git a/RESOURCES/FEATURE_FLAGS.md b/RESOURCES/FEATURE_FLAGS.md index 5461db70a140..c19fac7b0694 100644 --- a/RESOURCES/FEATURE_FLAGS.md +++ b/RESOURCES/FEATURE_FLAGS.md @@ -25,7 +25,6 @@ These features are considered **unfinished** and should only be used on developm - CLIENT_CACHE - DASHBOARD_CACHE - DASHBOARD_NATIVE_FILTERS_SET -- DASHBOARD_RBAC - DISABLE_DATASET_SOURCE_EDIT - ENABLE_EXPLORE_JSON_CSRF_PROTECTION - KV_STORE @@ -49,6 +48,7 @@ These features are **finished** but currently being tested. They are usable, but These features flags are **safe for production** and have been tested. - DASHBOARD_CROSS_FILTERS +- DASHBOARD_RBAC [(docs)](https://superset.apache.org/docs/creating-charts-dashboards/first-dashboard#manage-access-to-dashboards) - ESCAPE_MARKDOWN_HTML - ENABLE_TEMPLATE_PROCESSING - LISTVIEWS_DEFAULT_CARD_VIEW diff --git a/RESOURCES/INTHEWILD.md b/RESOURCES/INTHEWILD.md index 46a8d4fa0e22..98827c4b4b17 100644 --- a/RESOURCES/INTHEWILD.md +++ b/RESOURCES/INTHEWILD.md @@ -32,6 +32,7 @@ Join our growing community! - [Hostnfly](https://www.hostnfly.com/) [@alexisrosuel] - [Lime](https://www.limebike.com/) [@cxmcc] - [Lyft](https://www.lyft.com/) +- [Ontruck](https://www.ontruck.com/) ### Financial Services - [Aktia Bank plc](https://www.aktia.com) [@villebro] @@ -60,6 +61,7 @@ Join our growing community! - [Tails.com](https://tails.com) [@alanmcruickshank] - [THE ICONIC](http://theiconic.com.au/) [@ksaagariconic] - [Utair](https://www.utair.ru) [@utair-digital] +- [VkusVill](https://www.vkusvill.ru) [@ETselikov] - [Zalando](https://www.zalando.com) [@dmigo] - [Zalora](https://www.zalora.com) [@ksaagariconic] @@ -78,6 +80,7 @@ Join our growing community! - [FBK - ICT center](http://ict.fbk.eu) - [GfK Data Lab](http://datalab.gfk.com) [@mherr] - [GrowthSimple](https://growthsimple.ai/) +- [Hydrolix](https://www.hydrolix.io/) - [Intercom](https://www.intercom.com/) [@kate-gallo] - [jampp](https://jampp.com/) - [Konfío](http://konfio.mx) [@uis-rodriguez] @@ -94,6 +97,7 @@ Join our growing community! - [Showmax](https://tech.showmax.com) [@bobek] - [source{d}](https://www.sourced.tech) [@marnovo] - [Steamroot](https://streamroot.io/) +- [TechAudit](https://www.techaudit.info) [@ETselikov] - [Tenable](https://www.tenable.com) [@dflionis] - [timbr.ai](https://timbr.ai/) [@semantiDan] - [Tobii](http://www.tobii.com/) [@dwa] @@ -102,14 +106,13 @@ Join our growing community! - [Windsor.ai](https://www.windsor.ai/) [@octaviancorlade] - [Zeta](https://www.zeta.tech/) [@shaikidris] - ### Entertainment - [6play](https://www.6play.fr) [@CoryChaplin] - [bilibili](https://www.bilibili.com) [@Moinheart] - [Douban](https://www.douban.com/) [@luchuan] - [Kuaishou](https://www.kuaishou.com/) [@zhaoyu89730105] - [Netflix](https://www.netflix.com/) -- [TME QQMUSIC/WESING](https://www.tencentmusic.com/) +- [TME QQMUSIC/WESING](https://www.tencentmusic.com/)[@shenyuanli,@marklaw] - [Xite](https://xite.com/) [@shashankkoppar] - [Zaihang](http://www.zaih.com/) @@ -117,6 +120,7 @@ Join our growing community! - [Brilliant.org](https://brilliant.org/) - [Udemy](https://www.udemy.com/) [@sungjuly] - [VIPKID](https://www.vipkid.com.cn/) [@illpanda] +- [Sunbird](https://www.sunbird.org/) [@eksteporg] ### Energy - [Airboxlab](https://foobot.io) [@antoine-galataud] @@ -126,6 +130,7 @@ Join our growing community! ### Healthcare - [Amino](https://amino.com) [@shkr] +- [Care](https://www.getcare.io/)[@alandao2021] - [Living Goods](https://www.livinggoods.org) [@chelule] - [Maieutical Labs](https://maieuticallabs.it) [@xrmx] - [QPID Health](http://www.qpidhealth.com/) diff --git a/UPDATING.md b/UPDATING.md index a24280e9db01..0e4c3a0577d8 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -23,6 +23,9 @@ This file documents any backwards-incompatible changes in Superset and assists people when migrating to a new version. ## Next +- [15909](https://github.com/apache/incubator-superset/pull/15909): a change which +drops a uniqueness criterion (which may or may not have existed) to the tables table. This constraint was obsolete as it is handled by the ORM due to differences in how MySQL, PostgreSQL, etc. handle uniqueness for NULL values. + - [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users. - [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the confg.py file for more detail. diff --git a/docker-compose-non-dev.yml b/docker-compose-non-dev.yml index b28174a062e8..84bbc3b7a8e5 100644 --- a/docker-compose-non-dev.yml +++ b/docker-compose-non-dev.yml @@ -33,7 +33,7 @@ services: - redis:/data db: - env_file: docker/.env + env_file: docker/.env-non-dev image: postgres:10 container_name: superset_db restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index 8b94f7017222..93a79809178e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -118,6 +118,9 @@ services: depends_on: *superset-depends-on user: *superset-user volumes: *superset-volumes + # Bump memory limit if processing selenium / thumbails on superset-worker + # mem_limit: 2038m + # mem_reservation: 128M superset-worker-beat: image: *superset-image diff --git a/docker/README.md b/docker/README.md index 5cda2aa47258..c867121daece 100644 --- a/docker/README.md +++ b/docker/README.md @@ -24,58 +24,52 @@ Docker is an easy way to get started with Superset. ## Prerequisites 1. Docker! [link](https://www.docker.com/get-started) -1. Docker-compose [link](https://docs.docker.com/compose/install/) +2. Docker-compose [link](https://docs.docker.com/compose/install/) ## Configuration -The `/app/pythonpath` folder is mounted from [./docker/pythonpath_dev](./docker/pythonpath_dev) -which contains a base configuration [./docker/pythonpath_dev/superset_config.py](./docker/pythonpath_dev/superset_config.py) +The `/app/pythonpath` folder is mounted from [`./docker/pythonpath_dev`](./pythonpath_dev) +which contains a base configuration [`./docker/pythonpath_dev/superset_config.py`](./pythonpath_dev/superset_config.py) intended for use with local development. ### Local overrides -In order to override configuration settings locally, simply make a copy of [./docker/pythonpath_dev/superset_config_local.example](./docker/pythonpath_dev/superset_config_local.example) -into [./docker/pythonpath_dev/superset_config_docker.py](./docker/pythonpath_dev/superset_config_docker.py) (git ignored) and fill in your overrides. +In order to override configuration settings locally, simply make a copy of [`./docker/pythonpath_dev/superset_config_local.example`](./pythonpath_dev/superset_config_local.example) +into `./docker/pythonpath_dev/superset_config_docker.py` (git ignored) and fill in your overrides. ### Local packages -If you want to add python packages in order to test things like DBs locally, you can simply add a local requirements.txt (./docker/requirements-local.txt) -and rebuild your docker stack. +If you want to add Python packages in order to test things like databases locally, you can simply add a local requirements.txt (`./docker/requirements-local.txt`) +and rebuild your Docker stack. Steps: - 1. Create ./docker/requirements-local.txt - 2. Add your new packages - 3. Rebuild docker-compose - a. `docker-compose down -v` - b. `docker-compose up` + +1. Create `./docker/requirements-local.txt` +2. Add your new packages +3. Rebuild docker-compose + 1. `docker-compose down -v` + 2. `docker-compose up` ## Initializing Database -The DB will initialize itself upon startup via the init container (superset-init) -(This may take a minute.) +The database will initialize itself upon startup via the init container ([`superset-init`](./docker-init.sh)). This may take a minute. ## Normal Operation -To run the container, simply run: - -```bash -docker-compose up -``` +To run the container, simply run: `docker-compose up` -After several minutes for superset initialization to finish, you can open a browser and view [`http://localhost:8088`](http://localhost:8088) +After waiting several minutes for Superset initialization to finish, you can open a browser and view [`http://localhost:8088`](http://localhost:8088) to start your journey. ## Developing -While running, the container server will reload on modification of the superset python and javascript source code. +While running, the container server will reload on modification of the Superset Python and JavaScript source code. Don't forget to reload the page to take the new frontend into account though. ## Production -It is also possible to run Superset in non-development mode: in the `docker-compose.yml` file remove -the volumes needed for development and change the variable `SUPERSET_ENV` to `production`. +It is possible to run Superset in non-development mode by using [`docker-compose-non-dev.yml`](../docker-compose-non-dev.yml). This file excludes the volumes needed for development and uses [`./docker/.env-non-dev`](./.env-non-dev) which sets the variable `SUPERSET_ENV` to `production`. ## Resource Constraints -If you are attempting to build on a Mac and it exits with 137 you need to increase your docker resources. -OSX instructions: https://docs.docker.com/docker-for-mac/#advanced (Search for memory) +If you are attempting to build on macOS and it exits with 137 you need to increase your Docker resources. See instructions [here](https://docs.docker.com/docker-for-mac/#advanced) (search for memory) diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index 57163ccfe4c4..4a8e27966424 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -21,7 +21,7 @@ set -eo pipefail REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt" # If Cypress run – overwrite the password for admin and export env variables if [ "$CYPRESS_CONFIG" == "true" ]; then - export SUPERSET_CONFIG=tests.superset_test_config + export SUPERSET_CONFIG=tests.integration_tests.superset_test_config export SUPERSET_TESTENV=true export ENABLE_REACT_CRUD_VIEWS=true export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset @@ -38,10 +38,10 @@ fi if [[ "${1}" == "worker" ]]; then echo "Starting Celery worker..." - celery worker --app=superset.tasks.celery_app:app -Ofair -l INFO + celery --app=superset.tasks.celery_app:app worker -Ofair -l INFO elif [[ "${1}" == "beat" ]]; then echo "Starting Celery beat..." - celery beat --app=superset.tasks.celery_app:app --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule + celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid -l INFO -s "${SUPERSET_HOME}"/celerybeat-schedule elif [[ "${1}" == "app" ]]; then echo "Starting web app..." flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0 diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 99f89564a6a6..57ed13d9cf24 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -27,7 +27,7 @@ else --workers 1 \ --worker-class gthread \ --threads 20 \ - --timeout 60 \ + --timeout ${GUNICORN_TIMEOUT:-60} \ --limit-request-line 0 \ --limit-request-field_size 0 \ "${FLASK_APP}" diff --git a/docker/docker-frontend.sh b/docker/docker-frontend.sh index b88215ca826b..4c0d01e07935 100755 --- a/docker/docker-frontend.sh +++ b/docker/docker-frontend.sh @@ -18,6 +18,7 @@ set -e cd /app/superset-frontend +npm install -g npm@7 npm install -f --no-optional --global webpack webpack-cli npm install -f --no-optional diff --git a/docker/pythonpath_dev/superset_config.py b/docker/pythonpath_dev/superset_config.py index a3ca8b1ec6c9..6c58bec79c17 100644 --- a/docker/pythonpath_dev/superset_config.py +++ b/docker/pythonpath_dev/superset_config.py @@ -23,6 +23,7 @@ import logging import os from datetime import timedelta +from typing import Optional from cachelib.file import FileSystemCache from celery.schedules import crontab @@ -30,7 +31,7 @@ logger = logging.getLogger() -def get_env_variable(var_name, default=None): +def get_env_variable(var_name: str, default: Optional[str] = None) -> str: """Get the environment variable or raise exception.""" try: return os.environ[var_name] @@ -63,8 +64,8 @@ def get_env_variable(var_name, default=None): REDIS_HOST = get_env_variable("REDIS_HOST") REDIS_PORT = get_env_variable("REDIS_PORT") -REDIS_CELERY_DB = get_env_variable("REDIS_CELERY_DB", 0) -REDIS_RESULTS_DB = get_env_variable("REDIS_RESULTS_DB", 1) +REDIS_CELERY_DB = get_env_variable("REDIS_CELERY_DB", "0") +REDIS_RESULTS_DB = get_env_variable("REDIS_RESULTS_DB", "1") RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab") diff --git a/docs/.asf.yaml b/docs/.asf.yaml new file mode 100644 index 000000000000..6d232bb2ad1c --- /dev/null +++ b/docs/.asf.yaml @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +publish: + whoami: asf-site + +staging: + whoami: asf-site diff --git a/docs/installation.rst b/docs/installation.rst index edfedce43965..9a696b356422 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -342,7 +342,7 @@ as well as the default values in place. Since ``superset_config.py`` acts as a Flask configuration module, it can be used to alter the settings Flask itself, -as well as Flask extensions like ``flask-wtf``, ``flask-cache``, +as well as Flask extensions like ``flask-wtf``, ``flask-caching``, ``flask-migrate``, and ``flask-appbuilder``. Flask App Builder, the web framework used by Superset offers many configuration settings. Please consult the `Flask App Builder Documentation @@ -365,12 +365,12 @@ auth postback endpoint, you can add them to *WTF_CSRF_EXEMPT_LIST* Caching ------- -Superset uses `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ for +Superset uses `Flask-Caching <https://flask-caching.readthedocs.io/>`_ for caching purpose. Configuring your caching backend is as easy as providing ``CACHE_CONFIG`` and ``DATA_CACHE_CONFIG`, constants in ``superset_config.py`` -that complies with `the Flask-Cache specifications <https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching>`_. +that complies with `the Flask-Caching specifications <https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching>`_. -Flask-Cache supports multiple caching backends (Redis, Memcached, +Flask-Caching supports multiple caching backends (Redis, Memcached, SimpleCache (in-memory), or the local filesystem). If you are going to use Memcached please use the `pylibmc` client library as `python-memcached` does not handle storing binary data correctly. If you use Redis, please install @@ -393,7 +393,7 @@ defined in ``DATA_CACHE_CONFIG``. It is also possible to pass a custom cache initialization function in the config to handle additional caching use cases. The function must return an -object that is compatible with the `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ API. +object that is compatible with the `Flask-Caching <https://flask-caching.readthedocs.io/>`_ API. .. code-block:: python @@ -443,7 +443,7 @@ This is an optional feature that can be turned on by activating its feature flag } -For this feature you will need a cache system and celery workers. All thumbnails are store on cache and are processed +For this feature you will need a cache system and celery workers. All thumbnails are stored on cache and are processed asynchronously by the workers. An example config where images are stored on S3 could be: @@ -1080,11 +1080,11 @@ have the same configuration. * To start a Celery worker to leverage the configuration run: :: - celery worker --app=superset.tasks.celery_app:app --pool=prefork -O fair -c 4 + celery --app=superset.tasks.celery_app:app worker --pool=prefork -O fair -c 4 * To start a job which schedules periodic background jobs, run :: - celery beat --app=superset.tasks.celery_app:app + celery --app=superset.tasks.celery_app:app beat To setup a result backend, you need to pass an instance of a derivative of ``from cachelib.base.BaseCache`` to the ``RESULTS_BACKEND`` @@ -1465,7 +1465,7 @@ install from pip: :: and run via: :: - celery flower --app=superset.tasks.celery_app:app + celery --app=superset.tasks.celery_app:app flower Building from source --------------------- @@ -1571,6 +1571,7 @@ Second step: Create a `CustomSsoSecurityManager` that extends `SupersetSecurityM .. code-block:: python + import logging from superset.security import SupersetSecurityManager class CustomSsoSecurityManager(SupersetSecurityManager): diff --git a/docs/package.json b/docs/package.json index 9458f576d0ed..32559144facb 100644 --- a/docs/package.json +++ b/docs/package.json @@ -52,7 +52,7 @@ ], "license": "0BSD", "scripts": { - "build": "gatsby build && cp .htaccess public/", + "build": "gatsby build && cp .htaccess .asf.yaml public/", "develop": "gatsby develop", "develop-ga": "GATSBY_GTAG_DEBUG=true gatsby develop", "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,mdx}\"", diff --git a/docs/src/images/databases/netezza.png b/docs/src/images/databases/netezza.png new file mode 100644 index 000000000000..2658d8629bf1 Binary files /dev/null and b/docs/src/images/databases/netezza.png differ diff --git a/docs/src/pages/docs/Connecting to Databases/ascend.mdx b/docs/src/pages/docs/Connecting to Databases/ascend.mdx new file mode 100644 index 000000000000..eb0d6c387af9 --- /dev/null +++ b/docs/src/pages/docs/Connecting to Databases/ascend.mdx @@ -0,0 +1,17 @@ +--- +name: Ascend.io +menu: Connecting to Databases +route: /docs/databases/ascend +index: 7 +version: 1 +--- + +## Ascend.io + +The recommended connector library to Ascend.io is [impyla](https://github.com/cloudera/impyla). + +The expected connection string is formatted as follows: + +``` +ascend://{username}:{password}@{hostname}:{port}/{database}?auth_mechanism=PLAIN;use_ssl=true +``` diff --git a/docs/src/pages/docs/Connecting to Databases/docker-add-drivers.mdx b/docs/src/pages/docs/Connecting to Databases/docker-add-drivers.mdx index cf3892f6f3e4..e54d9a94a0be 100644 --- a/docs/src/pages/docs/Connecting to Databases/docker-add-drivers.mdx +++ b/docs/src/pages/docs/Connecting to Databases/docker-add-drivers.mdx @@ -53,12 +53,18 @@ Rebuild your local image with the new driver baked in: docker-compose build --force-rm ``` -After the rebuild is complete, which make take a few minutes, relaunch: +After the rebuild of the Docker images is complete (which make take a few minutes) you can relaunch using the following command: ``` docker-compose up ``` +The other option is to start Superset via Docker Compose is using the recipe in `docker-compose-non-dev.yml`, which will use pre-built frontend assets and skip the building of front-end assets: + +``` +docker-compose -f docker-compose-non-dev.yml up +``` + ### 3. Connect to MySQL Now that you've got a MySQL driver installed locally, you should be able to test it out. diff --git a/docs/src/pages/docs/Connecting to Databases/dremio.mdx b/docs/src/pages/docs/Connecting to Databases/dremio.mdx index 7281f69462ca..6efc17c1f54f 100644 --- a/docs/src/pages/docs/Connecting to Databases/dremio.mdx +++ b/docs/src/pages/docs/Connecting to Databases/dremio.mdx @@ -14,7 +14,7 @@ The recommended connector library for Dremio is The expected connection string for ODBC (Default port is 31010) is formatted as follows: ``` -dremio://{username}:{password}@{host}:{port}/dremio +dremio://{username}:{password}@{host}:{port}/{database_name}/dremio?SSL=1 ``` The expected connection string for Arrow Flight (Dremio 4.9.1+. Default port is 32010) is formatted as follows: diff --git a/docs/src/pages/docs/Connecting to Databases/index.mdx b/docs/src/pages/docs/Connecting to Databases/index.mdx index 40082f76e7bc..4460d5e4b1df 100644 --- a/docs/src/pages/docs/Connecting to Databases/index.mdx +++ b/docs/src/pages/docs/Connecting to Databases/index.mdx @@ -33,6 +33,7 @@ A list of some of the recommended packages. |[Apache Pinot](/docs/databases/pinot)|```pip install pinotdb```|```pinot://BROKER:5436/query?server=http://CONTROLLER:5983/```| |[Apache Solr](/docs/databases/solr)|```pip install sqlalchemy-solr```|```solr://{username}:{password}@{hostname}:{port}/{server_path}/{collection}``` |[Apache Spark SQL](/docs/databases/spark-sql)|```pip install pyhive```|```hive://hive@{hostname}:{port}/{database}``` +|[Ascend.io](/docs/databases/ascend)|```pip install impyla```|```ascend://{username}:{password}@{hostname}:{port}/{database}?auth_mechanism=PLAIN;use_ssl=true```| |[Azure MS SQL](/docs/databases/sql-server)|```pip install pymssql``` |```mssql+pymssql://UserName@presetSQL:TestPassword@presetSQL.database.windows.net:1433/TestSchema``` |[Big Query](/docs/databases/bigquery)|```pip install pybigquery```|```bigquery://{project_id}```| |[ClickHouse](/docs/databases/clickhouse)|```pip install clickhouse-driver==0.2.0 && pip install clickhouse-sqlalchemy==0.1.6```|```clickhouse+native://{username}:{password}@{hostname}:{port}/{database}```| @@ -43,9 +44,10 @@ A list of some of the recommended packages. |[Google Sheets](/docs/databases/google-sheets)|```pip install shillelagh[gsheetsapi]```|```gsheets://```| |[Hologres](/docs/databases/hologres)|```pip install psycopg2```|```postgresql+psycopg2://<UserName>:<DBPassword>@<Database Host>/<Database Name>```| |[IBM Db2](/docs/databases/ibm-db2)|```pip install ibm_db_sa```|```db2+ibm_db://```| +|[IBM Netezza Performance Server](/docs/databases/netezza)|```pip install nzalchemy```|```netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>```| |[MySQL](/docs/databases/mysql)|```pip install mysqlclient```|```mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```| |[Oracle](/docs/databases/oracle)|```pip install cx_Oracle```|```oracle://```| -|[PostgreSQL](/docs/databases/postgresql)|```pip install psycopg2```|```postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```| +|[PostgreSQL](/docs/databases/postgres)|```pip install psycopg2```|```postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```| |[Trino](/docs/databases/trino)|```pip install sqlalchemy-trino```|```trino://{username}:{password}@{hostname}:{port}/{catalog}```| |[Presto](/docs/databases/presto)|```pip install pyhive```|```presto://```| |[SAP Hana](/docs/databases/hana)|```pip install hdbcli sqlalchemy-hana or pip install apache-superset[hana]```|```hana://{username}:{password}@{host}:{port}```| diff --git a/docs/src/pages/docs/Connecting to Databases/netezza.mdx b/docs/src/pages/docs/Connecting to Databases/netezza.mdx new file mode 100644 index 000000000000..76fa77fcca07 --- /dev/null +++ b/docs/src/pages/docs/Connecting to Databases/netezza.mdx @@ -0,0 +1,18 @@ +--- +name: IBM Netezza Performance Server +menu: Connecting to Databases +route: /docs/databases/netezza +index: 19 +version: 1 +--- + +## IBM Netezza Performance Server + +The [nzalchemy](https://pypi.org/project/nzalchemy/) library provides a +Python / SQLAlchemy interface to IBM Netezza Performance Server (aka Netezza). + +Here's the recommended connection string: + +``` +netezza+nzpy://{username}:{password}@{hostname}:{port}/{database} +``` diff --git a/docs/src/pages/docs/Connecting to Databases/postgres.mdx b/docs/src/pages/docs/Connecting to Databases/postgres.mdx index e62e02853c4b..38f7a1d028df 100644 --- a/docs/src/pages/docs/Connecting to Databases/postgres.mdx +++ b/docs/src/pages/docs/Connecting to Databases/postgres.mdx @@ -8,8 +8,8 @@ version: 1 ## Postgres -Note that the Postgres connector library [psycopg2](https://www.psycopg.org/docs/) comes out of the -box with Superset. +Note that, if you're using docker-compose, the Postgres connector library [psycopg2](https://www.psycopg.org/docs/) +comes out of the box with Superset. Postgres sample connection parameters: diff --git a/docs/src/pages/docs/Connecting to Databases/ui.mdx b/docs/src/pages/docs/Connecting to Databases/ui.mdx new file mode 100644 index 000000000000..0646235526d3 --- /dev/null +++ b/docs/src/pages/docs/Connecting to Databases/ui.mdx @@ -0,0 +1,77 @@ +--- +name: Using Database Connection UI +menu: Connecting to Databases +route: /docs/databases/db-connection-ui +index: 1 +version: 1 +--- + +Here is the documentation on how to leverage the new DB Connection UI. This will provide admins the ability to enhance the UX for users who want to connect to new databases. + +![db-conn-docs](https://user-images.githubusercontent.com/27827808/125499607-94e300aa-1c0f-4c60-b199-3f9de41060a3.gif) + +There are now 3 steps when connecting to a database in the new UI: + +Step 1: First the admin must inform superset what engine they want to connect to. This page is powered by the `/available` endpoint which pulls on the engines currently installed in your environment, so that only supported databases are shown. + +Step 2: Next, the admin is prompted to enter database specific parameters. Depending on whether there is a dynamic form available for that specific engine, the admin will either see the new custom form or the legacy SQLAlchemy form. We currently have built dynamic forms for (Redshift, MySQL, Postgres, and BigQuery). The new form prompts the user for the parameters needed to connect (for example, username, password, host, port, etc.) and provides immediate feedback on errors. + +Step 3: Finally, once the admin has connected to their DB using the dynamic form they have the opportunity to update any optional advanced settings. + +We hope this feature will help eliminate a huge bottleneck for users to get into the application and start crafting datasets. + +### How to setup up preferred database options and images + +We added a new configuration option where the admin can define their preferred databases, in order: + +```python +# A list of preferred databases, in order. These databases will be +# displayed prominently in the "Add Database" dialog. You should +# use the "engine_name" attribute of the corresponding DB engine spec +# in `superset/db_engine_specs/`. +PREFERRED_DATABASES: List[str] = [ + "PostgreSQL", + "Presto", + "MySQL", + "SQLite", +] +``` + +For copyright reasons the logos for each database are not distributed with Superset. + +### Setting images + +- To set the images of your preferred database, admins must create a mapping in the `superset_text.yml` file with engine and location of the image. The image can be host locally inside your static/file directory or online (e.g. S3) + +```python +DB_IMAGES: + postgresql: "path/to/image/postgres.jpg" + bigquery: "path/to/s3bucket/bigquery.jpg" + snowflake: "path/to/image/snowflake.jpg" +``` + +### How to add new database engines to available endpoint + +Currently the new modal supports the following databases: + +- Postgres +- Redshift +- MySQL +- BigQuery + +When the user selects a database not in this list they will see the old dialog asking for the SQLAlchemy URI. New databases can be added gradually to the new flow. In order to support the rich configuration a DB engine spec needs to have the following attributes: + +1. `parameters_schema`: a Marshmallow schema defining the parameters needed to configure the database. For Postgres this includes username, password, host, port, etc. ([see](https://github.com/apache/superset/blob/accee507c0819cd0d7bcfb5a3e1199bc81eeebf2/superset/db_engine_specs/base.py#L1309-L1320)). +2. `default_driver`: the name of the recommended driver for the DB engine spec. Many SQLAlchemy dialects support multiple drivers, but usually one is the official recommendation. For Postgres we use "psycopg2". +3. `sqlalchemy_uri_placeholder`: a string that helps the user in case they want to type the URI directly. +4. `encryption_parameters`: parameters used to build the URI when the user opts for an encrypted connection. For Postgres this is `{"sslmode": "require"}`. + +In addition, the DB engine spec must implement these class methods: + +- `build_sqlalchemy_uri(cls, parameters, encrypted_extra)`: this method receives the distinct parameters and builds the URI from them. +- `get_parameters_from_uri(cls, uri, encrypted_extra)`: this method does the opposite, extracting the parameters from a given URI. +- `validate_parameters(cls, parameters)`: this method is used for `onBlur` validation of the form. It should return a list of `SupersetError` indicating which parameters are missing, and which parameters are definitely incorrect ([example](https://github.com/apache/superset/blob/accee507c0819cd0d7bcfb5a3e1199bc81eeebf2/superset/db_engine_specs/base.py#L1404)). + +For databases like MySQL and Postgres that use the standard format of `engine+driver://user:password@host:port/dbname` all you need to do is add the `BasicParametersMixin` to the DB engine spec, and then define the parameters 2-4 (`parameters_schema` is already present in the mixin). + +For other databases you need to implement these methods yourself. The BigQuery DB engine spec is a good example of how to do that. diff --git a/docs/src/pages/docs/Creating Charts and Dashboards/index.mdx b/docs/src/pages/docs/Creating Charts and Dashboards/index.mdx index fa665f2298dd..ae7dd1da3b55 100644 --- a/docs/src/pages/docs/Creating Charts and Dashboards/index.mdx +++ b/docs/src/pages/docs/Creating Charts and Dashboards/index.mdx @@ -172,3 +172,19 @@ into a position you like onto the underlying grid. Congrats! You’ve successfully linked, analyzed, and visualized data in Superset. There are a wealth of other table configuration and visualization options, so please start exploring and creating slices and dashboards of your own + +ֿ +### Manage access to Dashboards + + +Access to dashboards is managed via owners (users that have edit permissions to the dashboard) + +Non-owner users access can be managed two different ways: + +1. Dataset permissions - if you add to the relevant role permissions to datasets it automatically grants implict access to all dashboards that uses those permitted datasets +2. Dashboard roles - if you enable **DASHBOARD_RBAC** feature flag then you be able to manage which roles can access the dashboard +- Having dashboard access implicitly grants read access to the associated datasets, therefore +all charts will load their data even if feature flag is turned on and no roles assigned +to roles the access will fallback to **Dataset permissions** + +<img src="/images/tutorial_dashboard_access.png" /> diff --git a/docs/src/pages/docs/Miscellaneous/issue_codes.mdx b/docs/src/pages/docs/Miscellaneous/issue_codes.mdx index ad26c70a798e..9666dcfdbd36 100644 --- a/docs/src/pages/docs/Miscellaneous/issue_codes.mdx +++ b/docs/src/pages/docs/Miscellaneous/issue_codes.mdx @@ -207,3 +207,131 @@ The submitted payload has the incorrect schema. ``` Please check that the request payload has the expected schema. + +## Issue 1021 + +``` +Results backend needed for asynchronous queries is not configured. +``` + +Your instance of Superset doesn't have a results backend configured, which is needed for asynchronous queries. Please contact an administrator for further assistance. + +## Issue 1022 + +``` +Database does not allow data manipulation. +``` + +Only `SELECT` statements are allowed against this database. Please contact an administrator if you need to run DML (data manipulation language) on this database. + +## Issue 1023 + +``` +CTAS (create table as select) doesn't have a SELECT statement at the end. +``` + +The last statement in a query run as CTAS (create table as select) MUST be a SELECT statement. Please make sure the last statement in the query is a SELECT. + +## Issue 1024 + +``` +CVAS (create view as select) query has more than one statement. +``` + +When running a CVAS (create view as select) the query should have a single statement. Please make sure the query has a single statement, and no extra semi-colons other than the last one. + +## Issue 1025 + +``` +CVAS (create view as select) query is not a SELECT statement. +``` + +When running a CVAS (create view as select) the query should be a SELECT statement. Please make sure the query has a single statement and it's a SELECT statement. + +## Issue 1026 + +``` +Query is too complex and takes too long to run. +``` + +The submitted query might be too complex to run under the time limit defined by your Superset administrator. Please double check your query and verify if it can be optimized. Alternatively, contact your administrator to increase the timeout period. + +## Issue 1027 + +``` +The database is currently running too many queries. +``` + +The database might be under heavy load, running too many queries. Please try again later, or contact an administrator for further assistance. + +## Issue 1028 + +``` +One or more parameters specified in the query are malformatted. +``` + +The query contains one or more malformed template parameters. Please check your query and confirm that all template parameters are surround by double braces, for example, "{{ ds }}". Then, try running your query again. + +## Issue 1029 + +``` +The object does not exist in this database. +``` + +Either the schema, column, or table do not exist in the database. + +## Issue 1030 + +``` +The query potentially has a syntax error. +``` + +The query might have a syntax error. Please check and run again. + +## Issue 1031 + +``` +The results backend no longer has the data from the query. +``` + +The results from the query might have been deleted from the results backend after some period. Please re-run your query. + +## Issue 1032 + +``` +The query associated with the results was deleted. +``` + +The query associated with the stored results no longer exists. Please re-run your query. + +## Issue 1033 + +``` +The results stored in the backend were stored in a different format, and no longer can be deserialized. +``` + +The query results were stored in a format that is no longer supported. Please re-run your query. + +## Issue 1034 + +``` +The database port provided is invalid. +``` + +Please check that the provided database port is an integer between 0 and 65535 (inclusive). + +## Issue 1035 + +``` +Failed to start remote query on a worker. +``` + +The query was not started by an asynchronous worker. Please reach out to your administrator for further assistance. + +## Issue 1036 + +``` +The database was deleted. +``` + +The operation failed because the database referenced no longer exists. Please reach out to your administrator for further assistance. diff --git a/docs/src/pages/docs/frequently-asked-questions-page.mdx b/docs/src/pages/docs/frequently-asked-questions-page.mdx index 5b4ab72fb3a1..1a2e83d324f0 100644 --- a/docs/src/pages/docs/frequently-asked-questions-page.mdx +++ b/docs/src/pages/docs/frequently-asked-questions-page.mdx @@ -76,7 +76,7 @@ SUPERSET_WEBSERVER_TIMEOUT = 60 ### Why is the map not visible in the geospatial visualization? -You need to register a free account at [Mapbox.com](www.mapbox.com), obtain an API key, and add it +You need to register a free account at [Mapbox.com](https://www.mapbox.com), obtain an API key, and add it to **superset_config.py** at the key MAPBOX_API_KEY: ``` diff --git a/docs/src/pages/docs/installation/alerts_reports.mdx b/docs/src/pages/docs/installation/alerts_reports.mdx index 6ac292b1a3fa..f81cf7e43de4 100644 --- a/docs/src/pages/docs/installation/alerts_reports.mdx +++ b/docs/src/pages/docs/installation/alerts_reports.mdx @@ -38,6 +38,7 @@ To send alerts and reports to Slack channels, you need to create a new Slack App 3. Go to "OAuth & Permissions" section, and give the following scopes to your app: - `incoming-webhook` - `files:write` + - `chat:write` 4. At the top of the "OAuth and Permissions" section, click "install to workspace". 5. Select a default channel for your app and continue. (You can post to any channel by inviting your Superset app into that channel). @@ -246,7 +247,7 @@ services: - superset - postgres - redis - command: "celery worker --app=superset.tasks.celery_app:app --pool=gevent --concurrency=500" + command: "celery --app=superset.tasks.celery_app:app worker --pool=gevent --concurrency=500" volumes: - ./config/:/app/pythonpath/ beat: @@ -258,7 +259,7 @@ services: - superset - postgres - redis - command: "celery beat --app=superset.tasks.celery_app:app --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule" + command: "celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule" volumes: - ./config/:/app/pythonpath/ superset: diff --git a/docs/src/pages/docs/installation/async_queries_celery.mdx b/docs/src/pages/docs/installation/async_queries_celery.mdx index a95fcf0bdcfa..e97acc6a23cd 100644 --- a/docs/src/pages/docs/installation/async_queries_celery.mdx +++ b/docs/src/pages/docs/installation/async_queries_celery.mdx @@ -57,13 +57,13 @@ CELERY_CONFIG = CeleryConfig To start a Celery worker to leverage the configuration, run the following command: ``` -celery worker --app=superset.tasks.celery_app:app --pool=prefork -O fair -c 4 +celery --app=superset.tasks.celery_app:app worker --pool=prefork -O fair -c 4 ``` To start a job which schedules periodic background jobs, run the following command: ``` -celery beat --app=superset.tasks.celery_app:app +celery --app=superset.tasks.celery_app:app beat ``` To setup a result backend, you need to pass an instance of a derivative of from @@ -114,5 +114,5 @@ pip install flower You can run flower using: ``` -celery flower --app=superset.tasks.celery_app:app +celery --app=superset.tasks.celery_app:app flower ``` diff --git a/docs/src/pages/docs/installation/caching.mdx b/docs/src/pages/docs/installation/caching.mdx index 4c9a22dae20b..e599553dde74 100644 --- a/docs/src/pages/docs/installation/caching.mdx +++ b/docs/src/pages/docs/installation/caching.mdx @@ -8,15 +8,15 @@ version: 1 ## Caching -Superset uses [Flask-Cache](https://pythonhosted.org/Flask-Cache/) for caching purpose. For security reasons, +Superset uses [Flask-Caching](https://flask-caching.readthedocs.io/) for caching purpose. For security reasons, there are two separate cache configs for Superset's own metadata (`CACHE_CONFIG`) and charting data queried from connected datasources (`DATA_CACHE_CONFIG`). However, Query results from SQL Lab are stored in another backend called `RESULTS_BACKEND`, See [Async Queries via Celery](/docs/installation/async-queries-celery) for details. Configuring caching is as easy as providing `CACHE_CONFIG` and `DATA_CACHE_CONFIG` in your -`superset_config.py` that complies with [the Flask-Cache specifications](https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching). +`superset_config.py` that complies with [the Flask-Caching specifications](https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching). -Flask-Cache supports various caching backends, including Redis, Memcached, SimpleCache (in-memory), or the +Flask-Caching supports various caching backends, including Redis, Memcached, SimpleCache (in-memory), or the local filesystem. - Memcached: we recommend using [pylibmc](https://pypi.org/project/pylibmc/) client library as @@ -71,7 +71,7 @@ FEATURE_FLAGS = { } ``` -For this feature you will need a cache system and celery workers. All thumbnails are store on cache +For this feature you will need a cache system and celery workers. All thumbnails are stored on cache and are processed asynchronously by the workers. An example config where images are stored on S3 could be: diff --git a/docs/src/pages/docs/installation/configuring.mdx b/docs/src/pages/docs/installation/configuring.mdx index d4fc778f5889..707aaeef7209 100644 --- a/docs/src/pages/docs/installation/configuring.mdx +++ b/docs/src/pages/docs/installation/configuring.mdx @@ -47,7 +47,7 @@ can be altered in your local `superset_config.py`. Administrators will want to r to understand what can be configured locally as well as the default values in place. Since `superset_config.py` acts as a Flask configuration module, it can be used to alter the -settings Flask itself, as well as Flask extensions like `flask-wtf`, `flask-cache`, `flask-migrate`, +settings Flask itself, as well as Flask extensions like `flask-wtf`, `flask-caching`, `flask-migrate`, and `flask-appbuilder`. Flask App Builder, the web framework used by Superset, offers many configuration settings. Please consult the [Flask App Builder Documentation](https://flask-appbuilder.readthedocs.org/en/latest/config.html) @@ -154,6 +154,7 @@ Then, create a `CustomSsoSecurityManager` that extends `SupersetSecurityManager` `oauth_user_info`: ```python +import logging from superset.security import SupersetSecurityManager class CustomSsoSecurityManager(SupersetSecurityManager): @@ -177,9 +178,47 @@ from custom_sso_security_manager import CustomSsoSecurityManager CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager ``` -Notice that the redirect URL will be `https://<superset-webserver>/oauth-authorized/<provider-name>` -When configuring an OAuth2 authorization provider if needed. For instance, the redirect URL will -be `https://<superset-webserver>/oauth-authorized/egaSSO` for the above configuration. +**Notes** + +- The redirect URL will be `https://<superset-webserver>/oauth-authorized/<provider-name>` + When configuring an OAuth2 authorization provider if needed. For instance, the redirect URL will + be `https://<superset-webserver>/oauth-authorized/egaSSO` for the above configuration. + +- If an OAuth2 authorization server supports OpenID Connect 1.0, you could configure its configuration + document URL only without providing `api_base_url`, `access_token_url`, `authorize_url` and other + required options like user info endpoint, jwks uri etc. For instance: + ```python + OAUTH_PROVIDERS = [ + { 'name':'egaSSO', + 'token_key':'access_token', # Name of the token in the response of access_token_url + 'icon':'fa-address-card', # Icon for the provider + 'remote_app': { + 'client_id':'myClientId', # Client Id (Identify Superset application) + 'client_secret':'MySecret', # Secret for this Client Id (Identify Superset application) + 'server_metadata_url': 'https://myAuthorizationServer/.well-known/openid-configuration' + } + } + ] + ``` + +### Flask app Configuration Hook + +`FLASK_APP_MUTATOR` is a configuration function that can be provided in your environment, receives +the app object and can alter it in any way. For example, add `FLASK_APP_MUTATOR` into your +`superset_config.py` to setup session cookie expiration time to 24 hours: + +``` +def make_session_permanent(): + ''' + Enable maxAge for the cookie 'session' + ''' + session.permanent = True + +# Set up max age of session to 24 hours +PERMANENT_SESSION_LIFETIME = timedelta(hours=24) +def FLASK_APP_MUTATOR(app: Flask) -> None: + app.before_request_funcs.setdefault(None, []).append(make_session_permanent) +``` ### Feature Flags diff --git a/docs/src/pages/docs/installation/index.mdx b/docs/src/pages/docs/installation/index.mdx index 81bbf600a28a..508fa27d14fe 100644 --- a/docs/src/pages/docs/installation/index.mdx +++ b/docs/src/pages/docs/installation/index.mdx @@ -100,3 +100,13 @@ username: admin ```bash password: admin ``` + + +### 5. Connecting your local database instance to superset + +When running Superset using `docker` or `docker-compose` it runs in its own docker container, as if the Superset was running in a separate machine entirely. Therefore attempts to connect to your local database with hostname `localhost` won't work as `localhost` refers to the docker container Superset is running in, and not your actual host machine. Fortunately, docker provides an easy way to access network resources in the host machine from inside a container, and we will leverage this capability to connect to our local database instance. + +Here the instructions are for connecting to postgresql (which is running on your host machine) from Superset (which is running in its docker container). Other databases may have slightly different configurations but gist would be same and boils down to 2 steps - + +1. **(Mac users may skip this step)** Configuring the local postgresql/database instance to accept public incoming connections. By default postgresql only allows incoming connections from `localhost` only, but re-iterating once again, `localhosts` are different for host machine and docker container. For postgresql this involves make one-line changes to the files `postgresql.conf` and `pg_hba.conf`, you can find helpful links tailored to your OS / PG version on the web easily for this task. For docker it suffices to only whitelist IPs `172.0.0.0/8` instead of `*`, but in any case you are *warned* that doing this in a production database *may* have disastrous consequences as you are opening your database to the public internet. +2. Instead of `localhost`, try using `host.docker.internal` (Mac users) or `172.18.0.1` (Linux users) as the host name when attempting to connect to the database. This is docker internal detail, what is happening is that in Mac systems docker creates a dns entry for the host name `host.docker.internal` which resolves to the correct address for the host machine, whereas in linux this is not the case (at least by default). If neither of these 2 hostnames work then you may want to find the exact host name you want to use, for that you can do `ifconfig` or `ip addr show` and look at the IP address of `docker0` interface that must have been created by docker for you. Alternately if you don't even see the `docker0` interface try (if needed with sudo) `docker network inspect bridge` and see if there is an entry for `"Gateway"` and note the IP address. diff --git a/docs/src/pages/docs/installation/installing_scratch.mdx b/docs/src/pages/docs/installation/installing_scratch.mdx index 11126aecead7..2fb1b7a814ea 100644 --- a/docs/src/pages/docs/installation/installing_scratch.mdx +++ b/docs/src/pages/docs/installation/installing_scratch.mdx @@ -36,6 +36,18 @@ Install the following packages using the `yum` package manager: sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel ``` +In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`: + +``` +sudo dnf install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel +``` + +Also, on CentOS, you may need to upgrade pip for the install to work: + +``` +pip3 install --upgrade pip +``` + **Mac OS X** If you're not on the latest version of OS X, we recommend upgrading because we've found that many diff --git a/docs/src/pages/docs/installation/networking_settings.mdx b/docs/src/pages/docs/installation/networking_settings.mdx index 6d2781925a6b..dc0c49c09fd0 100644 --- a/docs/src/pages/docs/installation/networking_settings.mdx +++ b/docs/src/pages/docs/installation/networking_settings.mdx @@ -32,7 +32,7 @@ cross-domain request). Add the following setting in your `superset_config.py` file: -- `SUPERSET_WEBSERVER_DOMAINS`: list of allowed hostnames for domain shareding feature. +- `SUPERSET_WEBSERVER_DOMAINS`: list of allowed hostnames for domain sharding feature. ### Middleware diff --git a/docs/src/pages/docs/installation/sql_templating.mdx b/docs/src/pages/docs/installation/sql_templating.mdx index 76f6f94c857e..68d1ca682d12 100644 --- a/docs/src/pages/docs/installation/sql_templating.mdx +++ b/docs/src/pages/docs/installation/sql_templating.mdx @@ -87,3 +87,167 @@ FEATURE_FLAGS = { The available validators and names can be found in [sql_validators](https://github.com/apache/superset/tree/master/superset/sql_validators). + +### Available Macros + +In this section, we'll walkthrough the pre-defined Jinja macros in Superset. + +**Current Username** + +The `{{ current_username() }}` macro returns the username of the currently logged in user. + +If you have caching enabled in your Superset configuration, then by defaul the the `username` value will be used +by Superset when calculating the cache key. A cache key is a unique identifer that determines if there's a +cache hit in the future and Superset can retrieve cached data. + +You can disable the inclusion of the `username` value in the calculation of the +cache key by adding the following parameter to your Jinja code: + +``` +{{ current_username(add_to_cache_keys=False) }} +``` + +**Current User ID** + +The `{{ current_user_id()}}` macro returns the user_id of the currently logged in user. + +If you have caching enabled in your Superset configuration, then by defaul the the `user_id` value will be used +by Superset when calculating the cache key. A cache key is a unique identifer that determines if there's a +cache hit in the future and Superset can retrieve cached data. + +You can disable the inclusion of the `user_id` value in the calculation of the +cache key by adding the following parameter to your Jinja code: + +``` +{{ current_user_id(add_to_cache_keys=False) }} +``` + +**Custom URL Parameters** + +The `{{ url_param('custom_variable') }}` macro lets you define arbitrary URL +parameters and reference them in your SQL code. + +Here's a concrete example: + +- You write the following query in SQL Lab: + + ``` + SELECT count(*) + FROM ORDERS + WHERE country_code = '{{ url_param('countrycode') }}' + ``` + +- You're hosting Superset at the domain www.example.com and you send your +coworker in Spain the following SQL Lab URL `www.example.com/superset/sqllab?countrycode=ES` +and your coworker in the USA the following SQL Lab URL `www.example.com/superset/sqllab?countrycode=US` +- For your coworker in Spain, the SQL Lab query will be rendered as: + + ``` + SELECT count(*) + FROM ORDERS + WHERE country_code = 'ES' + ``` + +- For your coworker in the USA, the SQL Lab query will be rendered as: + + ``` + SELECT count(*) + FROM ORDERS + WHERE country_code = 'US' + ``` + +**Explicitly Including Values in Cache Key** + +The `{{ cache_key_wrapper() }}` function explicitly instructs Superset to add a value to the +accumulated list of values used in the the calculation of the cache key. + +This function is only needed when you want to wrap your own custom function return values +in the cache key. You can gain more context +[here](https://github.com/apache/superset/blob/efd70077014cbed62e493372d33a2af5237eaadf/superset/jinja_context.py#L133-L148). + +Note that this function powers the caching of the `user_id` and `username` values +in the `current_user_id()` and `current_username()` function calls (if you have caching enabled). + +**Filter Values** + +You can retrieve the value for a specific filter as a list using `{{ filter_values() }}`. + +This is useful if: +- you want to use a filter component to filter a query where the name of filter component column doesn't match the one in the select statement +- you want to have the ability for filter inside the main query for speed purposes + +Here's a concrete example: + +``` +SELECT action, count(*) as times +FROM logs +WHERE + action in ({{ "'" + "','".join(filter_values('action_type')) + "'" }}) +GROUP BY action +``` + +You can use thisfeature to reference the start & end datetimes from a time filter using: + +- `{{ from_dttm }}`: start datetime value +- `{{ to_dttm }}`: end datetime value + +**Filters for a Specific Column** + +The `{{ get_filters() }}` macro returns the filters applied to a given column. In addition to +returning the values (similar to how `filter_values()` does), the `get_filters()` macro +returns the operator specified in the Explore UI. + + This is useful if: +- you want to handle more than the IN operator in your SQL clause +- you want to handle generating custom SQL conditions for a filter +- you want to have the ability to filter inside the main query for speed purposes + +Here's a concrete example: + +``` + WITH RECURSIVE + superiors(employee_id, manager_id, full_name, level, lineage) AS ( + SELECT + employee_id, + manager_id, + full_name, + 1 as level, + employee_id as lineage + FROM + employees + WHERE + 1=1 + + {# Render a blank line #} + {%- for filter in get_filters('full_name', remove_filter=True) -%} + + {%- if filter.get('op') == 'IN' -%} + AND + full_name IN ( {{ "'" + "', '".join(filter.get('val')) + "'" }} ) + {%- endif -%} + + {%- if filter.get('op') == 'LIKE' -%} + AND + full_name LIKE {{ "'" + filter.get('val') + "'" }} + {%- endif -%} + + {%- endfor -%} + UNION ALL + SELECT + e.employee_id, + e.manager_id, + e.full_name, + s.level + 1 as level, + s.lineage + FROM + employees e, + superiors s + WHERE s.manager_id = e.employee_id + ) + + SELECT + employee_id, manager_id, full_name, level, lineage + FROM + superiors + order by lineage, level +``` diff --git a/docs/src/resources/data.js b/docs/src/resources/data.js index aae3b075b6bb..28b9e5e5db9b 100644 --- a/docs/src/resources/data.js +++ b/docs/src/resources/data.js @@ -235,4 +235,9 @@ export const Databases = [ href: 'https://www.alibabacloud.com/product/hologres', imgName: 'hologres.png', }, + { + title: 'IBM Netezza Performance Server', + href: 'https://www.ibm.com/products/netezza', + imgName: 'netezza.png', + }, ]; diff --git a/docs/src/resources/openapi.json b/docs/src/resources/openapi.json index a86724ead90d..c0486d597bc9 100644 --- a/docs/src/resources/openapi.json +++ b/docs/src/resources/openapi.json @@ -724,12 +724,15 @@ "description": "To what level of granularity should the temporal column be aggregated. Supports [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) durations.", "enum": [ "PT1S", + "PT5S", + "PT30S", "PT1M", "PT5M", "PT10M", "PT15M", "PT0.5H", "PT1H", + "PT6H", "P1D", "P1W", "P1M", @@ -745,15 +748,7 @@ "type": "string" }, "time_range_endpoints": { - "items": { - "description": "A list with two values, stating if start/end should be inclusive/exclusive.", - "enum": [ - "unknown", - "inclusive", - "exclusive" - ], - "type": "string" - }, + "items": {}, "type": "array" }, "where": { @@ -780,11 +775,14 @@ ">=", "<=", "LIKE", + "ILIKE", "IS NULL", "IS NOT NULL", "IN", "NOT IN", - "REGEX" + "REGEX", + "IS TRUE", + "IS FALSE" ], "example": "IN", "type": "string" @@ -935,7 +933,9 @@ "prophet", "rolling", "select", - "sort" + "sort", + "diff", + "compare" ], "example": "aggregate", "type": "string" @@ -992,12 +992,15 @@ "description": "Time grain used to specify time period increments in prediction. Supports [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) durations.", "enum": [ "PT1S", + "PT5S", + "PT30S", "PT1M", "PT5M", "PT10M", "PT15M", "PT0.5H", "PT1H", + "PT6H", "P1D", "P1W", "P1M", @@ -1042,24 +1045,8 @@ }, "type": "array" }, - "result_format": { - "description": "Format of result payload", - "enum": [ - "json", - "csv" - ], - "type": "string" - }, - "result_type": { - "description": "Type of results to return", - "enum": [ - "full", - "query", - "results", - "samples" - ], - "type": "string" - } + "result_format": {}, + "result_type": {} }, "type": "object" }, @@ -1078,8 +1065,14 @@ "example": { "__time_range": "1 year ago : now" }, + "nullable": true, "type": "object" }, + "apply_fetch_values_predicate": { + "description": "Add fetch values predicate (where clause) to query if defined in datasource", + "nullable": true, + "type": "boolean" + }, "columns": { "description": "Columns which to select in the query.", "items": { @@ -1088,6 +1081,14 @@ "nullable": true, "type": "array" }, + "datasource": { + "allOf": [ + { + "$ref": "#/components/schemas/ChartDataDatasource" + } + ], + "nullable": true + }, "druid_time_origin": { "description": "Starting point for time grain counting on legacy Druid datasources. Used to change e.g. Monday/Sunday first-day-of-week. This field is deprecated and should be passed to `extras` as `druid_time_origin`.", "nullable": true, @@ -1099,20 +1100,24 @@ "$ref": "#/components/schemas/ChartDataExtras" } ], - "description": "Extra parameters to add to the query." + "description": "Extra parameters to add to the query.", + "nullable": true }, "filters": { "items": { "$ref": "#/components/schemas/ChartDataFilter" }, + "nullable": true, "type": "array" }, "granularity": { "description": "Name of temporal column used for time filtering. For legacy Druid datasources this defines the time grain.", + "nullable": true, "type": "string" }, "granularity_sqla": { "description": "Name of temporal column used for time filtering for SQL datasources. This field is deprecated, use `granularity` instead.", + "nullable": true, "type": "string" }, "groupby": { @@ -1125,6 +1130,7 @@ }, "having": { "description": "HAVING clause to be added to aggregate queries using AND operator. This field is deprecated and should be passed to `extras`.", + "nullable": true, "type": "string" }, "having_filters": { @@ -1132,19 +1138,28 @@ "items": { "$ref": "#/components/schemas/ChartDataFilter" }, + "nullable": true, "type": "array" }, + "is_rowcount": { + "description": "Should the rowcount of the actual query be returned", + "nullable": true, + "type": "boolean" + }, "is_timeseries": { "description": "Is the `query_object` a timeseries.", + "nullable": true, "type": "boolean" }, "metrics": { "description": "Aggregate expressions. Metrics can be passed as both references to datasource metrics (strings), or ad-hoc metricswhich are defined only within the query object. See `ChartDataAdhocMetricSchema` for the structure of ad-hoc metrics.", "items": {}, + "nullable": true, "type": "array" }, "order_desc": { "description": "Reverse order. Default: `false`", + "nullable": true, "type": "boolean" }, "orderby": { @@ -1159,10 +1174,8 @@ true ] ], - "items": { - "items": {}, - "type": "array" - }, + "items": {}, + "nullable": true, "type": "array" }, "post_processing": { @@ -1175,32 +1188,48 @@ ], "nullable": true }, + "nullable": true, "type": "array" }, + "result_type": { + "nullable": true + }, "row_limit": { - "description": "Maximum row count. Default: `config[\"ROW_LIMIT\"]`", + "description": "Maximum row count (0=disabled). Default: `config[\"ROW_LIMIT\"]`", "format": "int32", - "minimum": 1, + "minimum": 0, + "nullable": true, "type": "integer" }, "row_offset": { "description": "Number of rows to skip. Default: `0`", "format": "int32", "minimum": 0, + "nullable": true, "type": "integer" }, + "time_offsets": { + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, "time_range": { "description": "A time rage, either expressed as a colon separated string `since : until` or human readable freeform. Valid formats for `since` and `until` are: \n- ISO 8601\n- X days/years/hours/day/year/weeks\n- X days/years/hours/day/year/weeks ago\n- X days/years/hours/day/year/weeks from now\n\nAdditionally, the following freeform can be used:\n\n- Last day\n- Last week\n- Last month\n- Last quarter\n- Last year\n- No filter\n- Last X seconds/minutes/hours/days/weeks/months/years\n- Next X seconds/minutes/hours/days/weeks/months/years\n", "example": "Last week", + "nullable": true, "type": "string" }, "time_shift": { "description": "A human-readable date/time string. Please refer to [parsdatetime](https://github.com/bear/parsedatetime) documentation for details on valid values.", + "nullable": true, "type": "string" }, "timeseries_limit": { "description": "Maximum row count for timeseries queries. Default: `0`", "format": "int32", + "nullable": true, "type": "integer" }, "timeseries_limit_metric": { @@ -1218,6 +1247,7 @@ }, "where": { "description": "WHERE clause to be added to queries using AND operator.This field is deprecated and should be passed to `extras`.", + "nullable": true, "type": "string" } }, @@ -1477,6 +1507,51 @@ ], "type": "object" }, + "ChartEntityResponseSchema": { + "properties": { + "cache_timeout": { + "description": "Duration (in seconds) of the caching timeout for this chart. Note this defaults to the datasource/table timeout if undefined.", + "format": "int32", + "type": "integer" + }, + "changed_on": { + "description": "The ISO date that the chart was last changed.", + "type": "string" + }, + "datasource": { + "description": "The datasource name.", + "type": "string" + }, + "description": { + "description": "A description of the chart propose.", + "type": "string" + }, + "description_markeddown": { + "description": "Sanitized HTML version of the chart description.", + "type": "string" + }, + "form_data": { + "description": "Form data from the Explore controls used to form the chart's data query.", + "type": "object" + }, + "modified": { + "type": "string" + }, + "slice_id": { + "format": "int32", + "type": "integer" + }, + "slice_name": { + "description": "The name of the chart.", + "type": "string" + }, + "slice_url": { + "description": "The URL of the chart.", + "type": "string" + } + }, + "type": "object" + }, "ChartFavStarResponseResult": { "properties": { "id": { @@ -1552,6 +1627,10 @@ "nullable": true, "type": "string" }, + "query_context": { + "nullable": true, + "type": "string" + }, "slice_name": { "maxLength": 250, "nullable": true, @@ -1573,7 +1652,7 @@ "type": "integer" }, "changed_by": { - "$ref": "#/components/schemas/Meta5" + "$ref": "#/components/schemas/Meta8" }, "changed_by_name": { "readOnly": true @@ -1588,7 +1667,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/Meta6" + "$ref": "#/components/schemas/Meta5" }, "datasource_id": { "format": "int32", @@ -1633,7 +1712,7 @@ "type": "string" }, "table": { - "$ref": "#/components/schemas/Meta8" + "$ref": "#/components/schemas/Meta6" }, "thumbnail_url": { "readOnly": true @@ -1702,6 +1781,11 @@ "nullable": true, "type": "string" }, + "query_context": { + "description": "The query context represents the queries that need to run in order to generate the data the visualization, and in what format the data should be returned.", + "nullable": true, + "type": "string" + }, "slice_name": { "description": "The name of the chart.", "maxLength": 250, @@ -1778,6 +1862,11 @@ "nullable": true, "type": "string" }, + "query_context": { + "description": "The query context represents the queries that need to run in order to generate the data the visualization, and in what format the data should be returned.", + "nullable": true, + "type": "string" + }, "slice_name": { "description": "The name of the chart.", "maxLength": 250, @@ -1882,86 +1971,245 @@ }, "type": "object" }, - "DashboardRestApi.get": { + "DashboardDatasetSchema": { "properties": { - "changed_by": { - "$ref": "#/components/schemas/Meta17" + "cache_timeout": { + "format": "int32", + "type": "integer" }, - "changed_by_name": { - "readOnly": true + "column_formats": { + "type": "object" }, - "changed_by_url": { - "readOnly": true + "column_types": { + "items": { + "format": "int32", + "type": "integer" + }, + "type": "array" }, - "changed_on": { - "format": "date-time", - "nullable": true, + "columns": { + "items": { + "type": "object" + }, + "type": "array" + }, + "database": { + "$ref": "#/components/schemas/Database" + }, + "datasource_name": { "type": "string" }, - "charts": { - "readOnly": true + "default_endpoint": { + "type": "string" }, - "css": { - "nullable": true, + "edit_url": { "type": "string" }, - "dashboard_title": { - "maxLength": 500, - "nullable": true, + "fetch_values_predicate": { + "type": "string" + }, + "filter_select": { + "type": "boolean" + }, + "filter_select_enabled": { + "type": "boolean" + }, + "granularity_sqla": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "health_check_message": { "type": "string" }, "id": { "format": "int32", "type": "integer" }, - "json_metadata": { - "nullable": true, + "is_sqllab_view": { + "type": "boolean" + }, + "main_dttm_col": { + "type": "string" + }, + "metrics": { + "items": { + "type": "object" + }, + "type": "array" + }, + "name": { "type": "string" }, + "offset": { + "format": "int32", + "type": "integer" + }, + "order_by_choices": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, "owners": { - "$ref": "#/components/schemas/Meta18" + "items": { + "format": "int32", + "type": "integer" + }, + "type": "array" }, - "position_json": { - "nullable": true, + "params": { "type": "string" }, - "published": { - "nullable": true, - "type": "boolean" + "perm": { + "type": "string" }, - "slug": { - "maxLength": 255, - "nullable": true, + "schema": { "type": "string" }, - "table_names": { - "readOnly": true + "select_star": { + "type": "string" }, - "thumbnail_url": { - "readOnly": true + "sql": { + "type": "string" }, - "url": { - "readOnly": true + "table_name": { + "type": "string" + }, + "template_params": { + "type": "string" + }, + "time_grain_sqla": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "verbose_map": { + "additionalProperties": { + "type": "string" + }, + "type": "object" } }, "type": "object" }, - "DashboardRestApi.get_list": { + "DashboardGetResponseSchema": { "properties": { "changed_by": { - "$ref": "#/components/schemas/Meta15" + "$ref": "#/components/schemas/User" }, "changed_by_name": { - "readOnly": true + "type": "string" }, "changed_by_url": { - "readOnly": true + "type": "string" + }, + "changed_on": { + "format": "date-time", + "type": "string" }, "changed_on_delta_humanized": { - "readOnly": true + "type": "string" }, - "changed_on_utc": { - "readOnly": true + "charts": { + "items": { + "description": "The names of the dashboard's charts. Names are used for legacy reasons.", + "type": "string" + }, + "type": "array" + }, + "css": { + "description": "Override CSS for the dashboard.", + "type": "string" + }, + "dashboard_title": { + "description": "A title for the dashboard.", + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + }, + "json_metadata": { + "description": "This JSON object is generated dynamically when clicking the save or overwrite button in the dashboard view. It is exposed here for reference and for power users who may want to alter specific parameters.", + "type": "string" + }, + "owners": { + "items": { + "$ref": "#/components/schemas/User" + }, + "type": "array" + }, + "position_json": { + "description": "This json object describes the positioning of the widgets in the dashboard. It is dynamically generated when adjusting the widgets size and positions by using drag & drop in the dashboard view", + "type": "string" + }, + "published": { + "type": "boolean" + }, + "roles": { + "items": { + "$ref": "#/components/schemas/Roles" + }, + "type": "array" + }, + "slug": { + "type": "string" + }, + "table_names": { + "type": "string" + }, + "thumbnail_url": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object" + }, + "DashboardRestApi.get": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DashboardRestApi.get_list": { + "properties": { + "changed_by": { + "$ref": "#/components/schemas/Meta15" + }, + "changed_by_name": { + "readOnly": true + }, + "changed_by_url": { + "readOnly": true + }, + "changed_on_delta_humanized": { + "readOnly": true + }, + "changed_on_utc": { + "readOnly": true }, "created_by": { "$ref": "#/components/schemas/Meta14" @@ -1984,7 +2232,7 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/Meta16" + "$ref": "#/components/schemas/Meta17" }, "position_json": { "nullable": true, @@ -1994,11 +2242,17 @@ "nullable": true, "type": "boolean" }, + "roles": { + "$ref": "#/components/schemas/Meta16" + }, "slug": { "maxLength": 255, "nullable": true, "type": "string" }, + "status": { + "readOnly": true + }, "thumbnail_url": { "readOnly": true }, @@ -2040,6 +2294,14 @@ "description": "Determines whether or not this dashboard is visible in the list of all dashboards.", "type": "boolean" }, + "roles": { + "items": { + "description": "Roles is a list which defines access to the dashboard. These roles are always applied in addition to restrictions on dataset level access. If no roles defined then the dashboard is available to all roles.", + "format": "int32", + "type": "integer" + }, + "type": "array" + }, "slug": { "description": "Unique identifying part for the web address of the dashboard.", "maxLength": 255, @@ -2088,6 +2350,15 @@ "nullable": true, "type": "boolean" }, + "roles": { + "items": { + "description": "Roles is a list which defines access to the dashboard. These roles are always applied in addition to restrictions on dataset level access. If no roles defined then the dashboard is available to all roles.", + "format": "int32", + "nullable": true, + "type": "integer" + }, + "type": "array" + }, "slug": { "description": "Unique identifying part for the web address of the dashboard.", "maxLength": 255, @@ -2098,6 +2369,48 @@ }, "type": "object" }, + "Database": { + "properties": { + "allow_multi_schema_metadata_fetch": { + "type": "boolean" + }, + "allows_cost_estimate": { + "type": "boolean" + }, + "allows_subquery": { + "type": "boolean" + }, + "allows_virtual_table_explore": { + "type": "boolean" + }, + "backend": { + "type": "string" + }, + "explore_database_id": { + "format": "int32", + "type": "integer" + }, + "id": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "DatabaseFunctionNamesResponse": { + "properties": { + "function_names": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, "DatabaseRelatedChart": { "properties": { "id": { @@ -2202,11 +2515,19 @@ "nullable": true, "type": "boolean" }, + "backend": { + "readOnly": true + }, "cache_timeout": { "format": "int32", "nullable": true, "type": "integer" }, + "configuration_method": { + "maxLength": 255, + "nullable": true, + "type": "string" + }, "database_name": { "maxLength": 250, "type": "string" @@ -2236,6 +2557,9 @@ "nullable": true, "type": "boolean" }, + "parameters": { + "readOnly": true + }, "server_cert": { "nullable": true, "type": "string" @@ -2298,7 +2622,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/Meta19" + "$ref": "#/components/schemas/Meta18" }, "database_name": { "maxLength": 250, @@ -2311,14 +2635,15 @@ "nullable": true, "type": "boolean" }, + "extra": { + "nullable": true, + "type": "string" + }, "force_ctas_schema": { "maxLength": 250, "nullable": true, "type": "string" }, - "function_names": { - "readOnly": true - }, "id": { "format": "int32", "type": "integer" @@ -2361,6 +2686,10 @@ "nullable": true, "type": "integer" }, + "configuration_method": { + "default": "sqlalchemy_form", + "description": "Configuration_method is used on the frontend to inform the backend whether to explode parameters or to provide only a sqlalchemy_uri." + }, "database_name": { "description": "A database name to identify this connection.", "maxLength": 250, @@ -2372,6 +2701,11 @@ "nullable": true, "type": "string" }, + "engine": { + "description": "SQLAlchemy engine to use", + "nullable": true, + "type": "string" + }, "expose_in_sqllab": { "description": "Expose this database to SQLLab", "type": "boolean" @@ -2391,6 +2725,11 @@ "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.<br/>If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", "type": "boolean" }, + "parameters": { + "additionalProperties": {}, + "description": "DB-specific parameters for configuration", + "type": "object" + }, "server_cert": { "description": "<p>Optional CA_BUNDLE contents to validate HTTPS requests. Only available on certain database engines.</p>", "nullable": true, @@ -2404,8 +2743,7 @@ } }, "required": [ - "database_name", - "sqlalchemy_uri" + "database_name" ], "type": "object" }, @@ -2441,6 +2779,10 @@ "nullable": true, "type": "integer" }, + "configuration_method": { + "default": "sqlalchemy_form", + "description": "Configuration_method is used on the frontend to inform the backend whether to explode parameters or to provide only a sqlalchemy_uri." + }, "database_name": { "description": "A database name to identify this connection.", "maxLength": 250, @@ -2453,6 +2795,11 @@ "nullable": true, "type": "string" }, + "engine": { + "description": "SQLAlchemy engine to use", + "nullable": true, + "type": "string" + }, "expose_in_sqllab": { "description": "Expose this database to SQLLab", "type": "boolean" @@ -2472,6 +2819,11 @@ "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.<br/>If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", "type": "boolean" }, + "parameters": { + "additionalProperties": {}, + "description": "DB-specific parameters for configuration", + "type": "object" + }, "server_cert": { "description": "<p>Optional CA_BUNDLE contents to validate HTTPS requests. Only available on certain database engines.</p>", "nullable": true, @@ -2481,7 +2833,6 @@ "description": "<p>Refer to the <a href=\"https://docs.sqlalchemy.org/en/rel_1_2/core/engines.html#database-urls\">SqlAlchemy docs</a> for more information on how to structure your URI.</p>", "maxLength": 1024, "minLength": 0, - "nullable": true, "type": "string" } }, @@ -2489,6 +2840,10 @@ }, "DatabaseTestConnectionSchema": { "properties": { + "configuration_method": { + "default": "sqlalchemy_form", + "description": "Configuration_method is used on the frontend to inform the backend whether to explode parameters or to provide only a sqlalchemy_uri." + }, "database_name": { "description": "A database name to identify this connection.", "maxLength": 250, @@ -2501,6 +2856,11 @@ "nullable": true, "type": "string" }, + "engine": { + "description": "SQLAlchemy engine to use", + "nullable": true, + "type": "string" + }, "extra": { "description": "<p>JSON string containing extra configuration elements.<br>1. The <code>engine_params</code> object gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine\">sqlalchemy.create_engine</a> call, while the <code>metadata_params</code> gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/rel_1_0/core/metadata.html#sqlalchemy.schema.MetaData\">sqlalchemy.MetaData</a> call.<br>2. The <code>metadata_cache_timeout</code> is a cache timeout setting in seconds for metadata fetch of this database. Specify it as <strong>\"metadata_cache_timeout\": {\"schema_cache_timeout\": 600, \"table_cache_timeout\": 600}</strong>. If unset, cache will not be enabled for the functionality. A timeout of 0 indicates that the cache never expires.<br>3. The <code>schemas_allowed_for_csv_upload</code> is a comma separated list of schemas that CSVs are allowed to upload to. Specify it as <strong>\"schemas_allowed_for_csv_upload\": [\"public\", \"csv_upload\"]</strong>. If database flavor does not support schema or any schema is allowed to be accessed, just leave the list empty<br>4. the <code>version</code> field is a string specifying the this db's version. This should be used with Presto DBs so that the syntax is correct<br>5. The <code>allows_virtual_table_explore</code> field is a boolean specifying whether or not the Explore button in SQL Lab results is shown.</p>", "type": "string" @@ -2509,6 +2869,11 @@ "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.<br/>If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", "type": "boolean" }, + "parameters": { + "additionalProperties": {}, + "description": "DB-specific parameters for configuration", + "type": "object" + }, "server_cert": { "description": "<p>Optional CA_BUNDLE contents to validate HTTPS requests. Only available on certain database engines.</p>", "nullable": true, @@ -2521,8 +2886,53 @@ "type": "string" } }, + "type": "object" + }, + "DatabaseValidateParametersSchema": { + "properties": { + "configuration_method": { + "description": "Configuration_method is used on the frontend to inform the backend whether to explode parameters or to provide only a sqlalchemy_uri." + }, + "database_name": { + "description": "A database name to identify this connection.", + "maxLength": 250, + "minLength": 1, + "nullable": true, + "type": "string" + }, + "encrypted_extra": { + "description": "<p>JSON string containing additional connection configuration.<br>This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.</p>", + "nullable": true, + "type": "string" + }, + "engine": { + "description": "SQLAlchemy engine to use", + "type": "string" + }, + "extra": { + "description": "<p>JSON string containing extra configuration elements.<br>1. The <code>engine_params</code> object gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine\">sqlalchemy.create_engine</a> call, while the <code>metadata_params</code> gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/rel_1_0/core/metadata.html#sqlalchemy.schema.MetaData\">sqlalchemy.MetaData</a> call.<br>2. The <code>metadata_cache_timeout</code> is a cache timeout setting in seconds for metadata fetch of this database. Specify it as <strong>\"metadata_cache_timeout\": {\"schema_cache_timeout\": 600, \"table_cache_timeout\": 600}</strong>. If unset, cache will not be enabled for the functionality. A timeout of 0 indicates that the cache never expires.<br>3. The <code>schemas_allowed_for_csv_upload</code> is a comma separated list of schemas that CSVs are allowed to upload to. Specify it as <strong>\"schemas_allowed_for_csv_upload\": [\"public\", \"csv_upload\"]</strong>. If database flavor does not support schema or any schema is allowed to be accessed, just leave the list empty<br>4. the <code>version</code> field is a string specifying the this db's version. This should be used with Presto DBs so that the syntax is correct<br>5. The <code>allows_virtual_table_explore</code> field is a boolean specifying whether or not the Explore button in SQL Lab results is shown.</p>", + "type": "string" + }, + "impersonate_user": { + "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.<br/>If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", + "type": "boolean" + }, + "parameters": { + "additionalProperties": { + "nullable": true + }, + "description": "DB-specific parameters for configuration", + "type": "object" + }, + "server_cert": { + "description": "<p>Optional CA_BUNDLE contents to validate HTTPS requests. Only available on certain database engines.</p>", + "nullable": true, + "type": "string" + } + }, "required": [ - "sqlalchemy_uri" + "configuration_method", + "engine" ], "type": "object" }, @@ -2582,6 +2992,78 @@ ], "type": "object" }, + "DatasetColumnsRestApi.get": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetColumnsRestApi.get_list": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetColumnsRestApi.post": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetColumnsRestApi.put": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetMetricRestApi.get": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetMetricRestApi.get_list": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetMetricRestApi.post": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "DatasetMetricRestApi.put": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, "DatasetMetricsPut": { "properties": { "d3format": { @@ -2709,10 +3191,10 @@ "type": "integer" }, "columns": { - "$ref": "#/components/schemas/Meta23" + "$ref": "#/components/schemas/Meta22" }, "database": { - "$ref": "#/components/schemas/Meta26" + "$ref": "#/components/schemas/Meta23" }, "datasource_type": { "readOnly": true @@ -2794,7 +3276,7 @@ "DatasetRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/Meta21" + "$ref": "#/components/schemas/Meta19" }, "changed_by_name": { "readOnly": true @@ -2809,7 +3291,7 @@ "readOnly": true }, "database": { - "$ref": "#/components/schemas/Meta22" + "$ref": "#/components/schemas/Meta21" }, "default_endpoint": { "nullable": true, @@ -3073,7 +3555,7 @@ "type": "integer" }, "user": { - "$ref": "#/components/schemas/Meta44" + "$ref": "#/components/schemas/Meta43" }, "user_id": { "format": "int32", @@ -3120,7 +3602,7 @@ "type": "integer" }, "user": { - "$ref": "#/components/schemas/Meta43" + "$ref": "#/components/schemas/Meta42" }, "user_id": { "format": "int32", @@ -3375,65 +3857,60 @@ }, "Meta16": { "properties": { - "first_name": { - "maxLength": 64, - "type": "string" - }, "id": { "format": "int32", "type": "integer" }, - "last_name": { - "maxLength": 64, - "type": "string" - }, - "username": { + "name": { "maxLength": 64, "type": "string" } }, "required": [ - "first_name", - "last_name", - "username" + "name" ], "type": "object" }, "Meta17": { "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, "username": { "maxLength": 64, "type": "string" } }, "required": [ + "first_name", + "last_name", "username" ], "type": "object" }, - "Meta18": { - "properties": { - "first_name": { - "maxLength": 64, - "type": "string" - }, - "id": { - "format": "int32", - "type": "integer" - }, - "last_name": { + "Meta18": { + "properties": { + "first_name": { "maxLength": 64, "type": "string" }, - "username": { + "last_name": { "maxLength": 64, "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -3443,14 +3920,14 @@ "maxLength": 64, "type": "string" }, - "last_name": { + "username": { "maxLength": 64, "type": "string" } }, "required": [ "first_name", - "last_name" + "username" ], "type": "object" }, @@ -3495,23 +3972,6 @@ "type": "object" }, "Meta21": { - "properties": { - "first_name": { - "maxLength": 64, - "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" - } - }, - "required": [ - "first_name", - "username" - ], - "type": "object" - }, - "Meta22": { "properties": { "database_name": { "maxLength": 250, @@ -3527,7 +3987,7 @@ ], "type": "object" }, - "Meta23": { + "Meta22": { "properties": { "changed_on": { "format": "date-time", @@ -3581,6 +4041,9 @@ "nullable": true, "type": "string" }, + "type_generic": { + "readOnly": true + }, "uuid": { "format": "uuid", "nullable": true, @@ -3597,6 +4060,22 @@ ], "type": "object" }, + "Meta23": { + "properties": { + "database_name": { + "maxLength": 250, + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "database_name" + ], + "type": "object" + }, "Meta24": { "properties": { "first_name": { @@ -3686,22 +4165,6 @@ "type": "object" }, "Meta26": { - "properties": { - "database_name": { - "maxLength": 250, - "type": "string" - }, - "id": { - "format": "int32", - "type": "integer" - } - }, - "required": [ - "database_name" - ], - "type": "object" - }, - "Meta27": { "properties": { "first_name": { "maxLength": 64, @@ -3727,7 +4190,7 @@ ], "type": "object" }, - "Meta28": { + "Meta27": { "properties": { "database_name": { "maxLength": 250, @@ -3739,7 +4202,7 @@ ], "type": "object" }, - "Meta29": { + "Meta28": { "properties": { "id": { "format": "int32", @@ -3748,12 +4211,16 @@ }, "type": "object" }, - "Meta3": { + "Meta29": { "properties": { "first_name": { "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -3765,16 +4232,12 @@ ], "type": "object" }, - "Meta30": { + "Meta3": { "properties": { "first_name": { "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -3786,7 +4249,7 @@ ], "type": "object" }, - "Meta31": { + "Meta30": { "properties": { "database_name": { "maxLength": 250, @@ -3802,7 +4265,7 @@ ], "type": "object" }, - "Meta32": { + "Meta31": { "properties": { "first_name": { "maxLength": 64, @@ -3823,7 +4286,7 @@ ], "type": "object" }, - "Meta33": { + "Meta32": { "properties": { "database_name": { "maxLength": 250, @@ -3839,23 +4302,24 @@ ], "type": "object" }, - "Meta34": { + "Meta33": { "properties": { - "id": { - "format": "int32", - "type": "integer" + "first_name": { + "maxLength": 64, + "type": "string" }, - "type": { - "maxLength": 50, + "last_name": { + "maxLength": 64, "type": "string" } }, "required": [ - "type" + "first_name", + "last_name" ], "type": "object" }, - "Meta35": { + "Meta34": { "properties": { "first_name": { "maxLength": 64, @@ -3872,12 +4336,32 @@ ], "type": "object" }, + "Meta35": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + }, + "type": { + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, "Meta36": { "properties": { "first_name": { "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -3891,22 +4375,17 @@ }, "Meta37": { "properties": { - "first_name": { - "maxLength": 64, + "database_name": { + "maxLength": 250, "type": "string" }, "id": { "format": "int32", "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" } }, "required": [ - "first_name", - "last_name" + "database_name" ], "type": "object" }, @@ -3915,17 +4394,40 @@ "id": { "format": "int32", "type": "integer" + }, + "recipient_config_json": { + "nullable": true, + "type": "string" + }, + "type": { + "maxLength": 50, + "type": "string" } }, + "required": [ + "type" + ], "type": "object" }, "Meta39": { "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, "id": { "format": "int32", "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" } }, + "required": [ + "first_name", + "last_name" + ], "type": "object" }, "Meta4": { @@ -3951,51 +4453,34 @@ "format": "int32", "type": "integer" }, - "recipient_config_json": { + "slice_name": { + "maxLength": 250, "nullable": true, "type": "string" }, - "type": { - "maxLength": 50, + "viz_type": { + "maxLength": 250, + "nullable": true, "type": "string" } }, - "required": [ - "type" - ], "type": "object" }, "Meta41": { "properties": { - "first_name": { - "maxLength": 64, + "dashboard_title": { + "maxLength": 500, + "nullable": true, "type": "string" }, "id": { "format": "int32", "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" } }, - "required": [ - "first_name", - "last_name" - ], "type": "object" }, "Meta42": { - "properties": { - "id": { - "format": "int32", - "type": "integer" - } - }, - "type": "object" - }, - "Meta43": { "properties": { "username": { "maxLength": 64, @@ -4007,7 +4492,7 @@ ], "type": "object" }, - "Meta44": { + "Meta43": { "properties": { "username": { "maxLength": 64, @@ -4025,6 +4510,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -4038,22 +4527,17 @@ }, "Meta6": { "properties": { - "first_name": { - "maxLength": 64, + "default_endpoint": { + "nullable": true, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, - "last_name": { - "maxLength": 64, + "table_name": { + "maxLength": 250, "type": "string" } }, "required": [ - "first_name", - "last_name" + "table_name" ], "type": "object" }, @@ -4085,17 +4569,18 @@ }, "Meta8": { "properties": { - "default_endpoint": { - "nullable": true, + "first_name": { + "maxLength": 64, "type": "string" }, - "table_name": { - "maxLength": 250, + "last_name": { + "maxLength": 64, "type": "string" } }, "required": [ - "table_name" + "first_name", + "last_name" ], "type": "object" }, @@ -4125,7 +4610,7 @@ "type": "string" }, "database": { - "$ref": "#/components/schemas/Meta29" + "$ref": "#/components/schemas/Meta28" }, "end_result_backend_time": { "nullable": true, @@ -4240,7 +4725,7 @@ "type": "string" }, "database": { - "$ref": "#/components/schemas/Meta28" + "$ref": "#/components/schemas/Meta27" }, "end_time": { "nullable": true, @@ -4295,7 +4780,7 @@ "type": "string" }, "user": { - "$ref": "#/components/schemas/Meta27" + "$ref": "#/components/schemas/Meta26" } }, "required": [ @@ -4379,6 +4864,11 @@ "maxLength": 50, "type": "string" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "value": { "format": "float", "nullable": true, @@ -4423,6 +4913,11 @@ "maxLength": 50, "type": "string" }, + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" + }, "value": { "format": "float", "nullable": true, @@ -4491,21 +4986,26 @@ "type": "boolean" }, "chart": { - "$ref": "#/components/schemas/Meta39" + "$ref": "#/components/schemas/Meta40" }, "context_markdown": { "nullable": true, "type": "string" }, + "creation_method": { + "maxLength": 255, + "nullable": true, + "type": "string" + }, "crontab": { "maxLength": 1000, "type": "string" }, "dashboard": { - "$ref": "#/components/schemas/Meta38" + "$ref": "#/components/schemas/Meta41" }, "database": { - "$ref": "#/components/schemas/Meta42" + "$ref": "#/components/schemas/Meta37" }, "description": { "nullable": true, @@ -4549,15 +5049,24 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/Meta41" + "$ref": "#/components/schemas/Meta39" }, "recipients": { - "$ref": "#/components/schemas/Meta40" + "$ref": "#/components/schemas/Meta38" + }, + "report_format": { + "maxLength": 50, + "nullable": true, + "type": "string" }, "sql": { "nullable": true, "type": "string" }, + "timezone": { + "maxLength": 100, + "type": "string" + }, "type": { "maxLength": 50, "type": "string" @@ -4592,7 +5101,7 @@ "type": "boolean" }, "changed_by": { - "$ref": "#/components/schemas/Meta36" + "$ref": "#/components/schemas/Meta34" }, "changed_on": { "format": "date-time", @@ -4603,13 +5112,18 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/Meta35" + "$ref": "#/components/schemas/Meta33" }, "created_on": { "format": "date-time", "nullable": true, "type": "string" }, + "creation_method": { + "maxLength": 255, + "nullable": true, + "type": "string" + }, "crontab": { "maxLength": 1000, "type": "string" @@ -4617,6 +5131,10 @@ "crontab_humanized": { "readOnly": true }, + "description": { + "nullable": true, + "type": "string" + }, "id": { "format": "int32", "type": "integer" @@ -4636,10 +5154,14 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/Meta37" + "$ref": "#/components/schemas/Meta36" }, "recipients": { - "$ref": "#/components/schemas/Meta34" + "$ref": "#/components/schemas/Meta35" + }, + "timezone": { + "maxLength": 100, + "type": "string" }, "type": { "maxLength": 50, @@ -4661,6 +5183,7 @@ }, "chart": { "format": "int32", + "nullable": true, "type": "integer" }, "context_markdown": { @@ -4668,6 +5191,9 @@ "nullable": true, "type": "string" }, + "creation_method": { + "description": "Creation method is used to inform the frontend whether the report/alert was created in the dashboard, chart, or alerts and reports UI." + }, "crontab": { "description": "A CRON expression.[Crontab Guru](https://crontab.guru/) is a helpful resource that can help you craft a CRON expression.", "example": "*/5 * * * *", @@ -4677,6 +5203,7 @@ }, "dashboard": { "format": "int32", + "nullable": true, "type": "integer" }, "database": { @@ -4693,12 +5220,14 @@ "description": "Once an alert is triggered, how long, in seconds, before Superset nags you again. (in seconds)", "example": 14400, "format": "int32", + "minimum": 1, "type": "integer" }, "log_retention": { "description": "How long to keep the logs around for this report (in days)", "example": 90, "format": "int32", + "minimum": 1, "type": "integer" }, "name": { @@ -4722,11 +5251,23 @@ }, "type": "array" }, + "report_format": { + "enum": [ + "PNG", + "CSV", + "TEXT" + ], + "type": "string" + }, "sql": { "description": "A SQL statement that defines whether the alert should get triggered or not. The query is expected to return either NULL or a number value.", "example": "SELECT value FROM time_series_table", "type": "string" }, + "timezone": { + "description": "A timezone string that represents the location of the timezone.", + "type": "string" + }, "type": { "description": "The report schedule type", "enum": [ @@ -4750,6 +5291,7 @@ "description": "If an alert is staled at a working state, how long until it's state is reseted to error", "example": 3600, "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -4767,6 +5309,7 @@ }, "chart": { "format": "int32", + "nullable": true, "type": "integer" }, "context_markdown": { @@ -4774,6 +5317,10 @@ "nullable": true, "type": "string" }, + "creation_method": { + "description": "Creation method is used to inform the frontend whether the report/alert was created in the dashboard, chart, or alerts and reports UI.", + "nullable": true + }, "crontab": { "description": "A CRON expression.[Crontab Guru](https://crontab.guru/) is a helpful resource that can help you craft a CRON expression.", "maxLength": 1000, @@ -4782,6 +5329,7 @@ }, "dashboard": { "format": "int32", + "nullable": true, "type": "integer" }, "database": { @@ -4798,12 +5346,14 @@ "description": "Once an alert is triggered, how long, in seconds, before Superset nags you again. (in seconds)", "example": 14400, "format": "int32", + "minimum": 1, "type": "integer" }, "log_retention": { "description": "How long to keep the logs around for this report (in days)", "example": 90, "format": "int32", + "minimum": 1, "type": "integer" }, "name": { @@ -4826,12 +5376,24 @@ }, "type": "array" }, + "report_format": { + "enum": [ + "PNG", + "CSV", + "TEXT" + ], + "type": "string" + }, "sql": { "description": "A SQL statement that defines whether the alert should get triggered or not. The query is expected to return either NULL or a number value.", "example": "SELECT value FROM time_series_table", "nullable": true, "type": "string" }, + "timezone": { + "description": "A timezone string that represents the location of the timezone.", + "type": "string" + }, "type": { "description": "The report schedule type", "enum": [ @@ -4856,19 +5418,32 @@ "description": "If an alert is staled at a working state, how long until it's state is reseted to error", "example": 3600, "format": "int32", + "minimum": 1, "nullable": true, "type": "integer" } }, "type": "object" }, + "Roles": { + "properties": { + "id": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, "SavedQueryRestApi.get": { "properties": { "created_by": { - "$ref": "#/components/schemas/Meta32" + "$ref": "#/components/schemas/Meta31" }, "database": { - "$ref": "#/components/schemas/Meta33" + "$ref": "#/components/schemas/Meta32" }, "description": { "nullable": true, @@ -4904,7 +5479,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/Meta30" + "$ref": "#/components/schemas/Meta29" }, "created_on": { "format": "date-time", @@ -4912,7 +5487,7 @@ "type": "string" }, "database": { - "$ref": "#/components/schemas/Meta31" + "$ref": "#/components/schemas/Meta30" }, "db_id": { "format": "int32", @@ -4923,6 +5498,9 @@ "nullable": true, "type": "string" }, + "extra": { + "readOnly": true + }, "id": { "format": "int32", "type": "integer" @@ -5174,6 +5752,24 @@ }, "type": "object" }, + "User": { + "properties": { + "first_name": { + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + }, + "last_name": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, "ValidatorConfigJSON": { "properties": { "op": { @@ -5374,6 +5970,12 @@ "filter": { "type": "string" }, + "include_ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, "page": { "type": "integer" }, @@ -7052,13 +7654,16 @@ "schema": { "properties": { "formData": { + "description": "upload file (ZIP)", "format": "binary", "type": "string" }, "overwrite": { - "type": "bool" + "description": "overwrite existing databases?", + "type": "boolean" }, "passwords": { + "description": "JSON map of passwords for each file", "type": "string" } }, @@ -7109,7 +7714,7 @@ }, "/chart/related/{column_name}": { "get": { - "description": "Get a list of all possible owners for a chart.", + "description": "Get a list of all possible owners for a chart. Use `owners` has the `column_name` parameter", "parameters": [ { "in": "path", @@ -7451,6 +8056,77 @@ ] } }, + "/chart/{pk}/data/": { + "get": { + "description": "Takes a chart ID and uses the query context stored when the chart was saved to return payload data response.", + "parameters": [ + { + "description": "The chart ID", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "The format in which the data should be returned", + "in": "query", + "name": "format", + "schema": { + "type": "string" + } + }, + { + "description": "The type in which the data should be returned", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChartDataResponseSchema" + } + } + }, + "description": "Query result" + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChartDataAsyncResponseSchema" + } + } + }, + "description": "Async job details" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Charts" + ] + } + }, "/chart/{pk}/screenshot/{digest}/": { "get": { "description": "Get a computed screenshot from cache.", @@ -8560,13 +9236,16 @@ "schema": { "properties": { "formData": { + "description": "upload file (ZIP or JSON)", "format": "binary", "type": "string" }, "overwrite": { - "type": "bool" + "description": "overwrite existing databases?", + "type": "boolean" }, "passwords": { + "description": "JSON map of passwords for each file", "type": "string" } }, @@ -8648,7 +9327,118 @@ } } }, - "description": "Related column data" + "description": "Related column data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Dashboards" + ] + } + }, + "/dashboard/{id_or_slug}": { + "get": { + "description": "Get a dashboard detail information.", + "parameters": [ + { + "description": "Either the id of the dashboard, or its slug", + "in": "path", + "name": "id_or_slug", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/DashboardGetResponseSchema" + } + }, + "type": "object" + } + } + }, + "description": "Dashboard" + }, + "302": { + "description": "Redirects to the current digest" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Dashboards" + ] + } + }, + "/dashboard/{id_or_slug}/charts": { + "get": { + "description": "Get the chart definitions for a given dashboard", + "parameters": [ + { + "in": "path", + "name": "id_or_slug", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "items": { + "$ref": "#/components/schemas/ChartEntityResponseSchema" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "Dashboard chart definitions" + }, + "302": { + "description": "Redirects to the current digest" }, "400": { "$ref": "#/components/responses/400" @@ -8658,9 +9448,6 @@ }, "404": { "$ref": "#/components/responses/404" - }, - "500": { - "$ref": "#/components/responses/500" } }, "security": [ @@ -8673,16 +9460,17 @@ ] } }, - "/dashboard/{pk}": { - "delete": { - "description": "Deletes a Dashboard.", + "/dashboard/{id_or_slug}/datasets": { + "get": { + "description": "Returns a list of a dashboard's datasets. Each dataset includes only the information necessary to render the dashboard's charts.", "parameters": [ { + "description": "Either the id of the dashboard, or its slug", "in": "path", - "name": "pk", + "name": "id_or_slug", "required": true, "schema": { - "type": "integer" + "type": "string" } } ], @@ -8692,30 +9480,30 @@ "application/json": { "schema": { "properties": { - "message": { - "type": "string" + "result": { + "items": { + "$ref": "#/components/schemas/DashboardDatasetSchema" + }, + "type": "array" } }, "type": "object" } } }, - "description": "Dashboard deleted" + "description": "Dashboard dataset definitions" + }, + "302": { + "description": "Redirects to the current digest" + }, + "400": { + "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" }, - "403": { - "$ref": "#/components/responses/403" - }, "404": { "$ref": "#/components/responses/404" - }, - "422": { - "$ref": "#/components/responses/422" - }, - "500": { - "$ref": "#/components/responses/500" } }, "security": [ @@ -8726,9 +9514,11 @@ "tags": [ "Dashboards" ] - }, - "get": { - "description": "Get a dashboard detail information.", + } + }, + "/dashboard/{pk}": { + "delete": { + "description": "Deletes a Dashboard.", "parameters": [ { "in": "path", @@ -8737,17 +9527,6 @@ "schema": { "type": "integer" } - }, - { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/get_item_schema" - } - } - }, - "in": "query", - "name": "q" } ], "responses": { @@ -8756,43 +9535,7 @@ "application/json": { "schema": { "properties": { - "description_columns": { - "properties": { - "column_name": { - "description": "The description for the column name. Will be translated by babel", - "example": "A Nice description for the column", - "type": "string" - } - }, - "type": "object" - }, - "id": { - "description": "The item id", - "type": "string" - }, - "label_columns": { - "properties": { - "column_name": { - "description": "The label for the column name. Will be translated by babel", - "example": "A Nice label for the column", - "type": "string" - } - }, - "type": "object" - }, - "result": { - "$ref": "#/components/schemas/DashboardRestApi.get" - }, - "show_columns": { - "description": "A list of columns", - "items": { - "type": "string" - }, - "type": "array" - }, - "show_title": { - "description": "A title to render. Will be translated by babel", - "example": "Show Item Details", + "message": { "type": "string" } }, @@ -8800,14 +9543,14 @@ } } }, - "description": "Item from Model" - }, - "400": { - "$ref": "#/components/responses/400" + "description": "Dashboard deleted" }, "401": { "$ref": "#/components/responses/401" }, + "403": { + "$ref": "#/components/responses/403" + }, "404": { "$ref": "#/components/responses/404" }, @@ -9233,6 +9976,73 @@ ] } }, + "/database/available/": { + "get": { + "description": "Get names of databases currently available", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "properties": { + "available_drivers": { + "description": "Installed drivers for the engine", + "items": { + "type": "string" + }, + "type": "array" + }, + "default_driver": { + "description": "Default driver for the engine", + "type": "string" + }, + "engine": { + "description": "Name of the SQLAlchemy engine", + "type": "string" + }, + "name": { + "description": "Name of the database", + "type": "string" + }, + "parameters": { + "description": "JSON schema defining the needed parameters", + "type": "object" + }, + "preferred": { + "description": "Is the database preferred?", + "type": "boolean" + }, + "sqlalchemy_uri_placeholder": { + "description": "Example placeholder for the SQLAlchemy URI", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + } + }, + "description": "Database names" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Database" + ] + } + }, "/database/export/": { "get": { "description": "Download database(s) and associated dataset(s) as a zip file", @@ -9289,13 +10099,16 @@ "schema": { "properties": { "formData": { + "description": "upload file (ZIP)", "format": "binary", "type": "string" }, "overwrite": { - "type": "bool" + "description": "overwrite existing databases?", + "type": "boolean" }, "passwords": { + "description": "JSON map of passwords for each file", "type": "string" } }, @@ -9394,6 +10207,56 @@ ] } }, + "/database/validate_parameters": { + "post": { + "description": "Validates parameters used to connect to a database", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseValidateParametersSchema" + } + } + }, + "description": "DB-specific parameters", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Database Test Connection" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Database" + ] + } + }, "/database/{pk}": { "delete": { "description": "Deletes a Database.", @@ -9547,9 +10410,81 @@ "tags": [ "Database" ] - }, - "put": { - "description": "Changes a Database.", + }, + "put": { + "description": "Changes a Database.", + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatabaseRestApi.put" + } + } + }, + "description": "Database schema", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/DatabaseRestApi.put" + } + }, + "type": "object" + } + } + }, + "description": "Database changed" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Database" + ] + } + }, + "/database/{pk}/function_names/": { + "get": { + "description": "Get function names supported by a database", "parameters": [ { "in": "path", @@ -9560,51 +10495,23 @@ } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatabaseRestApi.put" - } - } - }, - "description": "Database schema", - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "properties": { - "id": { - "type": "number" - }, - "result": { - "$ref": "#/components/schemas/DatabaseRestApi.put" - } - }, - "type": "object" + "$ref": "#/components/schemas/DatabaseFunctionNamesResponse" } } }, - "description": "Database changed" - }, - "400": { - "$ref": "#/components/responses/400" + "description": "Query result" }, "401": { "$ref": "#/components/responses/401" }, - "403": { - "$ref": "#/components/responses/403" - }, "404": { "$ref": "#/components/responses/404" }, - "422": { - "$ref": "#/components/responses/422" - }, "500": { "$ref": "#/components/responses/500" } @@ -10352,13 +11259,16 @@ "schema": { "properties": { "formData": { + "description": "upload file (ZIP or YAML)", "format": "binary", "type": "string" }, "overwrite": { - "type": "bool" + "description": "overwrite existing datasets?", + "type": "boolean" }, "passwords": { + "description": "JSON map of passwords for each file", "type": "string" } }, @@ -10630,11 +11540,10 @@ } }, { - "in": "path", + "in": "query", "name": "override_columns", - "required": true, "schema": { - "type": "bool" + "type": "boolean" } } ], @@ -10697,6 +11606,136 @@ ] } }, + "/dataset/{pk}/column/{column_id}": { + "delete": { + "description": "Delete a Dataset column", + "parameters": [ + { + "description": "The dataset pk for this column", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "The column id for this dataset", + "in": "path", + "name": "column_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Column deleted" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Datasets" + ] + } + }, + "/dataset/{pk}/metric/{metric_id}": { + "delete": { + "description": "Delete a Dataset metric", + "parameters": [ + { + "description": "The dataset pk for this column", + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "The metric id for this dataset", + "in": "path", + "name": "metric_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Metric deleted" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Datasets" + ] + } + }, "/dataset/{pk}/refresh": { "put": { "description": "Refreshes and updates columns of a dataset", @@ -12639,6 +13678,72 @@ ] } }, + "/saved_query/import/": { + "post": { + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "properties": { + "formData": { + "description": "upload file (ZIP)", + "format": "binary", + "type": "string" + }, + "overwrite": { + "description": "overwrite existing saved queries?", + "type": "boolean" + }, + "passwords": { + "description": "JSON map of passwords for each file", + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Saved Query import result" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Queries" + ] + } + }, "/saved_query/related/{column_name}": { "get": { "parameters": [ @@ -12909,6 +14014,42 @@ ] } }, + "/security/csrf_token/": { + "get": { + "description": "Fetch the CSRF token", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Result contains the CSRF token" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Security" + ] + } + }, "/security/login": { "post": { "description": "Authenticate and get a JWT access and refresh token", diff --git a/docs/static/images/tutorial_dashboard_access.png b/docs/static/images/tutorial_dashboard_access.png new file mode 100644 index 000000000000..f1ce5d6273a1 Binary files /dev/null and b/docs/static/images/tutorial_dashboard_access.png differ diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml index d06eff1859fd..30a409e083d8 100644 --- a/helm/superset/Chart.yaml +++ b/helm/superset/Chart.yaml @@ -22,13 +22,13 @@ maintainers: - name: craig-rueda email: craig@craigrueda.com url: https://github.com/craig-rueda -version: 0.1.3 +version: 0.3.6 dependencies: - name: postgresql version: 10.2.0 repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled - name: redis - version: 12.3.0 + version: 12.3.3 repository: https://charts.bitnami.com/bitnami condition: redis.enabled diff --git a/helm/superset/templates/deployment-beat.yaml b/helm/superset/templates/deployment-beat.yaml index 0ec76da15e88..2775d287498c 100644 --- a/helm/superset/templates/deployment-beat.yaml +++ b/helm/superset/templates/deployment-beat.yaml @@ -69,7 +69,7 @@ spec: env: - name: "SUPERSET_PORT" value: {{ .Values.service.port | quote}} - {{ if .Values.extraEnv }} + {{- if .Values.extraEnv }} {{- range $key, $value := .Values.extraEnv }} - name: {{ $key | quote}} value: {{ $value | quote }} @@ -78,10 +78,17 @@ spec: envFrom: - secretRef: name: {{ tpl .Values.envFromSecret . | quote }} + {{- range .Values.envFromSecrets }} + - secretRef: + name: {{ tpl . $ | quote }} + {{- end }} volumeMounts: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} resources: {{ toYaml .Values.resources | indent 12 }} {{- with .Values.nodeSelector }} @@ -104,4 +111,7 @@ spec: - name: superset-config secret: secretName: {{ tpl .Values.configFromSecret . }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} {{- end -}} diff --git a/helm/superset/templates/deployment-worker.yaml b/helm/superset/templates/deployment-worker.yaml index fe0ce20d8580..8bb2cc81c15d 100644 --- a/helm/superset/templates/deployment-worker.yaml +++ b/helm/superset/templates/deployment-worker.yaml @@ -53,6 +53,9 @@ spec: app: {{ template "superset.name" . }}-worker release: {{ .Release.Name }} spec: + {{- if .Values.serviceAccountName }} + serviceAccountName: {{ .Values.serviceAccountName }} + {{- end }} securityContext: runAsUser: {{ .Values.runAsUser }} {{- if .Values.supersetWorker.initContainers }} @@ -67,7 +70,7 @@ spec: env: - name: "SUPERSET_PORT" value: {{ .Values.service.port | quote}} - {{ if .Values.extraEnv }} + {{- if .Values.extraEnv }} {{- range $key, $value := .Values.extraEnv }} - name: {{ $key | quote}} value: {{ $value | quote }} @@ -76,10 +79,17 @@ spec: envFrom: - secretRef: name: {{ tpl .Values.envFromSecret . | quote }} + {{- range .Values.envFromSecrets }} + - secretRef: + name: {{ tpl . $ | quote }} + {{- end }} volumeMounts: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} resources: {{ toYaml .Values.resources | indent 12 }} {{- with .Values.nodeSelector }} @@ -102,3 +112,6 @@ spec: - name: superset-config secret: secretName: {{ tpl .Values.configFromSecret . }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} diff --git a/helm/superset/templates/deployment.yaml b/helm/superset/templates/deployment.yaml index 8e807daf15e8..ec6b4f453ea5 100644 --- a/helm/superset/templates/deployment.yaml +++ b/helm/superset/templates/deployment.yaml @@ -56,6 +56,9 @@ spec: app: {{ template "superset.name" . }} release: {{ .Release.Name }} spec: + {{- if .Values.serviceAccountName }} + serviceAccountName: {{ .Values.serviceAccountName }} + {{- end }} securityContext: runAsUser: {{ .Values.runAsUser }} {{- if .Values.supersetNode.initContainers }} @@ -70,7 +73,7 @@ spec: env: - name: "SUPERSET_PORT" value: {{ .Values.service.port | quote}} - {{ if .Values.extraEnv }} + {{- if .Values.extraEnv }} {{- range $key, $value := .Values.extraEnv }} - name: {{ $key | quote}} value: {{ $value | quote }} @@ -79,6 +82,10 @@ spec: envFrom: - secretRef: name: {{ tpl .Values.envFromSecret . | quote }} + {{- range .Values.envFromSecrets }} + - secretRef: + name: {{ tpl . $ | quote }} + {{- end }} volumeMounts: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} @@ -88,6 +95,9 @@ spec: mountPath: {{ .Values.extraConfigMountPath | quote }} readOnly: true {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 12 -}} + {{- end }} ports: - name: http containerPort: {{ .Values.service.port }} @@ -120,3 +130,6 @@ spec: configMap: name: {{ template "superset.fullname" . }}-extra-config {{- end }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} diff --git a/helm/superset/templates/ingress.yaml b/helm/superset/templates/ingress.yaml index b0888e4ede04..7c1dd72f0ebe 100644 --- a/helm/superset/templates/ingress.yaml +++ b/helm/superset/templates/ingress.yaml @@ -16,8 +16,7 @@ # {{ if .Values.ingress.enabled -}} {{- $fullName := include "superset.fullname" . -}} -{{- $ingressPath := .Values.ingress.path -}} -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} @@ -46,9 +45,12 @@ spec: - host: {{ . }} http: paths: - - path: {{ $ingressPath }} + - path: {{ $.Values.ingress.path }} + pathType: {{ $.Values.ingress.pathType }} backend: - serviceName: {{ $fullName }} - servicePort: http + service: + name: {{ $fullName }} + port: + name: http {{- end }} {{- end }} diff --git a/helm/superset/templates/init-job.yaml b/helm/superset/templates/init-job.yaml index b3c4fd42f67a..fb30abc8cef2 100644 --- a/helm/superset/templates/init-job.yaml +++ b/helm/superset/templates/init-job.yaml @@ -46,16 +46,23 @@ spec: envFrom: - secretRef: name: {{ tpl .Values.envFromSecret . }} + {{- range .Values.envFromSecrets }} + - secretRef: + name: {{ tpl . $ }} + {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: - name: superset-config mountPath: {{ .Values.configMountPath | quote }} readOnly: true - {{ if .Values.extraConfigs }} + {{- if .Values.extraConfigs }} - name: superset-extra-config mountPath: {{ .Values.extraConfigMountPath | quote }} readOnly: true {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- tpl (toYaml .) $ | nindent 10 -}} + {{- end }} command: {{ tpl (toJson .Values.init.command) . }} resources: {{ toYaml .Values.init.resources | indent 10 }} @@ -72,5 +79,8 @@ spec: configMap: name: {{ template "superset.fullname" . }}-extra-config {{- end }} + {{- with .Values.extraVolumes }} + {{- tpl (toYaml .) $ | nindent 8 -}} + {{- end }} restartPolicy: Never {{- end }} diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml index 58e9faaef5fa..c8577774afb8 100644 --- a/helm/superset/values.yaml +++ b/helm/superset/values.yaml @@ -29,10 +29,10 @@ runAsUser: 0 # For production clusters it's recommended to build own image with this step done in CI bootstrapScript: | #!/bin/bash - apt-get update -y &&\ - apt-get install -y --no-install-recommends nano &&\ - rm -rf /var/lib/apt/lists/* - pip install psycopg2==2.8.5 redis==3.2.1 + rm -rf /var/lib/apt/lists/* && \ + pip install \ + psycopg2==2.8.5 \ + redis==3.2.1 && \ if [ ! -f ~/bootstrap ]; then echo "Running Superset with uid {{ .Values.runAsUser }}" > ~/bootstrap; fi ## The name of the secret which we will use to generate a superset_config.py file @@ -44,15 +44,26 @@ configFromSecret: '{{ template "superset.fullname" . }}-config' ## This can be useful for secret keys, etc. ## envFromSecret: '{{ template "superset.fullname" . }}-env' +## This can be a list of template strings +envFromSecrets: [] ## Extra environment variables that will be passed into pods ## extraEnv: {} + # Extend timeout to allow long running queries. + # GUNICORN_TIMEOUT: 300 + + + # OAUTH_HOME_DOMAIN: .. + # # If a whitelist is not set, any address that can use your OAuth2 endpoint will be able to login. + # # this includes any random Gmail address if your OAuth2 Web App is set to External. + # OAUTH_WHITELIST_REGEX: ... ## Extra environment variables to pass as secrets ## extraSecretEnv: {} # MAPBOX_API_KEY: ... + # # Google API Keys: https://console.cloud.google.com/apis/credentials # GOOGLE_KEY: ... # GOOGLE_SECRET: ... @@ -69,20 +80,38 @@ extraConfigs: {} # sqlalchemy_uri: example://example-db.local # tables: [] - extraSecrets: {} +extraVolumes: [] + # - name: customConfig + # configMap: + # name: '{{ template "superset.fullname" . }}-custom-config' + # - name: additionalSecret + # secret: + # secretName: my-secret + # defaultMode: 0600 + +extraVolumeMounts: [] + # - name: customConfig + # mountPath: /mnt/config + # readOnly: true + # - name: additionalSecret: + # mountPath: /mnt/secret # A dictionary of overrides to append at the end of superset_config.py - the name does not matter # WARNING: the order is not guaranteed configOverrides: {} + # extend_timeout: | + # # Extend timeout to allow long running queries. + # SUPERSET_WEBSERVER_TIMEOUT = ... # enable_oauth: | - # from flask_appbuilder.security.manager import AUTH_DB + # from flask_appbuilder.security.manager import (AUTH_DB, AUTH_OAUTH) # AUTH_TYPE = AUTH_OAUTH # OAUTH_PROVIDERS = [ # { # "name": "google", + # "whitelist": [ os.getenv("OAUTH_WHITELIST_REGEX", "") ], # "icon": "fa-google", # "token_key": "access_token", # "remote_app": { @@ -93,9 +122,17 @@ configOverrides: {} # "request_token_url": None, # "access_token_url": "https://accounts.google.com/o/oauth2/token", # "authorize_url": "https://accounts.google.com/o/oauth2/auth", - # }, + # "authorize_params": {"hd": os.getenv("OAUTH_HOME_DOMAIN", "")} + # } # } # ] + # # Map Authlib roles to superset roles + # AUTH_ROLE_ADMIN = 'Admin' + # AUTH_ROLE_PUBLIC = 'Public' + # # Will allow user self registration, allowing to create Flask users from Authorized User + # AUTH_USER_REGISTRATION = True + # # The default user self registration role + # AUTH_USER_REGISTRATION_ROLE = "Admin" configMountPath: "/app/pythonpath" @@ -121,7 +158,12 @@ ingress: annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" + ## Extend timeout to allow long running queries. + # nginx.ingress.kubernetes.io/proxy-connect-timeout: "300" + # nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + # nginx.ingress.kubernetes.io/proxy-send-timeout: "300" path: / + pathType: ImplementationSpecific hosts: - chart-example.local tls: [] @@ -165,10 +207,8 @@ supersetNode: - secretRef: name: '{{ tpl .Values.envFromSecret . }}' command: [ "/bin/sh", "-c", "until nc -zv $DB_HOST $DB_PORT -w1; do echo 'waiting for db'; sleep 1; done" ] - ## Annotations to be added to supersetNode deployment deploymentAnnotations: {} - ## Annotations to be added to supersetNode pods podAnnotations: {} @@ -188,10 +228,8 @@ supersetWorker: - secretRef: name: '{{ tpl .Values.envFromSecret . }}' command: [ "/bin/sh", "-c", "until nc -zv $DB_HOST $DB_PORT -w1; do echo 'waiting for db'; sleep 1; done" ] - ## Annotations to be added to supersetWorker deployment deploymentAnnotations: {} - ## Annotations to be added to supersetWorker pods podAnnotations: {} @@ -203,7 +241,7 @@ supersetCeleryBeat: command: - "/bin/sh" - "-c" - - ". {{ .Values.configMountPath }}/superset_bootstrap.sh; celery beat --app=superset.tasks.celery_app:app --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule" + - ". {{ .Values.configMountPath }}/superset_bootstrap.sh; celery --app=superset.tasks.celery_app:app beat --pidfile /tmp/celerybeat.pid --schedule /tmp/celerybeat-schedule" forceReload: false # If true, forces deployment to reload on each upgrade initContainers: - name: wait-for-postgres @@ -213,10 +251,8 @@ supersetCeleryBeat: - secretRef: name: '{{ tpl .Values.envFromSecret . }}' command: [ "/bin/sh", "-c", "until nc -zv $DB_HOST $DB_PORT -w1; do echo 'waiting for db'; sleep 1; done" ] - ## Annotations to be added to supersetCeleryBeat deployment deploymentAnnotations: {} - ## Annotations to be added to supersetCeleryBeat pods podAnnotations: {} @@ -224,7 +260,7 @@ supersetCeleryBeat: ## Init job configuration init: # Configure resources - # Warning: fab commant consumes a lot of ram and can + # Warning: fab command consumes a lot of ram and can # cause the process to be killed due to OOM if it exceeds limit resources: {} # limits: @@ -239,6 +275,7 @@ init: - ". {{ .Values.configMountPath }}/superset_bootstrap.sh; . {{ .Values.configMountPath }}/superset_init.sh" enabled: true loadExamples: false + createAdmin: true adminUser: username: admin firstname: Superset @@ -259,6 +296,7 @@ init: superset db upgrade echo "Initializing roles..." superset init + {{ if .Values.init.createAdmin }} echo "Creating admin user..." superset fab create-admin \ --username {{ .Values.init.adminUser.username }} \ @@ -267,10 +305,15 @@ init: --email {{ .Values.init.adminUser.email }} \ --password {{ .Values.init.adminUser.password }} \ || true + {{- end }} {{ if .Values.init.loadExamples }} echo "Loading examples..." superset load_examples {{- end }} + if [ -f "{{ .Values.extraConfigMountPath }}/import_datasources.yaml" ]; then + echo "Importing database connections.... " + superset import_datasources -p {{ .Values.extraConfigMountPath }}/import_datasources.yaml + fi ## ## Configuration values for the postgresql dependency. ## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md @@ -279,14 +322,11 @@ postgresql: ## Use the PostgreSQL chart dependency. ## Set to false if bringing your own PostgreSQL. enabled: true - ## ## The name of an existing secret that contains the postgres password. existingSecret: - ## Name of the key containing the secret. existingSecretKey: postgresql-password - ## ## If you are bringing your own PostgreSQL, you should set postgresHost and ## also probably service.port, postgresqlUsername, postgresqlPassword, and postgresqlDatabase @@ -326,16 +366,12 @@ redis: ## Use the redis chart dependency. ## Set to false if bringing your own redis. enabled: true - usePassword: false - ## ## The name of an existing secret that contains the redis password. existingSecret: - ## Name of the key containing the secret. existingSecretKey: redis-password - ## ## If you are bringing your own redis, you can set the host in redisHost. ## redisHost: diff --git a/requirements/base.in b/requirements/base.in index 294583801824..00071c4ac8f1 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -18,3 +18,4 @@ -e file:. pyrsistent>=0.16.1,<0.17 zipp==3.4.1 +sasl==0.2.1 diff --git a/requirements/base.txt b/requirements/base.txt index 0f433e0d6647..6c903a098df4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -# SHA1:0862095245a068ae2fc00217da78331e1e7ae505 +# SHA1:57a754a4cf09b58d8e02c45bfb1058d2ce4286a6 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -7,9 +7,9 @@ # -e file:. # via -r requirements/base.in -aiohttp==3.7.2 +aiohttp==3.7.4.post0 # via slackclient -alembic==1.4.3 +alembic==1.6.5 # via flask-migrate amqp==2.6.1 # via kombu @@ -17,17 +17,17 @@ apispec[yaml]==3.3.2 # via flask-appbuilder async-timeout==3.0.1 # via aiohttp -attrs==20.2.0 +attrs==21.2.0 # via # aiohttp # jsonschema -babel==2.8.0 +babel==2.9.1 # via flask-babel -backoff==1.10.0 +backoff==1.11.1 # via apache-superset -billiard==3.6.3.0 +billiard==3.6.4.0 # via celery -bleach==3.3.0 +bleach==3.3.1 # via apache-superset brotli==1.0.9 # via flask-compress @@ -35,9 +35,9 @@ cachelib==0.1.1 # via apache-superset celery==4.4.7 # via apache-superset -cffi==1.14.3 +cffi==1.14.6 # via cryptography -chardet==3.0.4 +chardet==4.0.0 # via aiohttp click==7.1.2 # via @@ -48,66 +48,64 @@ colorama==0.4.4 # via # apache-superset # flask-appbuilder -contextlib2==0.6.0.post1 - # via apache-superset -convertdate==2.3.0 +convertdate==2.3.2 # via holidays cron-descriptor==1.2.24 # via apache-superset -croniter==0.3.36 +croniter==1.0.15 # via apache-superset -cryptography==3.3.2 +cryptography==3.4.7 # via apache-superset -decorator==4.4.2 - # via retry -defusedxml==0.6.0 +defusedxml==0.7.1 # via python3-openid -dnspython==2.0.0 +deprecation==2.1.0 + # via apache-superset +dnspython==2.1.0 # via email-validator -email-validator==1.1.1 +email-validator==1.1.3 # via flask-appbuilder -flask-appbuilder==3.3.0 +flask==1.1.4 + # via + # apache-superset + # flask-appbuilder + # flask-babel + # flask-caching + # flask-compress + # flask-jwt-extended + # flask-login + # flask-migrate + # flask-openid + # flask-sqlalchemy + # flask-wtf +flask-appbuilder==3.3.2 # via apache-superset flask-babel==1.0.0 # via flask-appbuilder -flask-caching==1.9.0 +flask-caching==1.10.1 # via apache-superset -flask-compress==1.8.0 +flask-compress==1.10.1 # via apache-superset -flask-jwt-extended==3.24.1 +flask-jwt-extended==3.25.1 # via flask-appbuilder flask-login==0.4.1 # via flask-appbuilder -flask-migrate==2.5.3 +flask-migrate==3.1.0 # via apache-superset flask-openid==1.2.5 # via flask-appbuilder -flask-sqlalchemy==2.4.4 +flask-sqlalchemy==2.5.1 # via # flask-appbuilder # flask-migrate -flask-talisman==0.7.0 +flask-talisman==0.8.1 # via apache-superset flask-wtf==0.14.3 # via # apache-superset # flask-appbuilder -flask==1.1.2 - # via - # apache-superset - # flask-appbuilder - # flask-babel - # flask-caching - # flask-compress - # flask-jwt-extended - # flask-login - # flask-migrate - # flask-openid - # flask-sqlalchemy - # flask-wtf -geographiclib==1.50 +geographiclib==1.52 # via geopy -geopy==2.0.0 +geopy==2.2.0 # via apache-superset graphlib-backport==1.0.3 # via apache-superset @@ -115,17 +113,12 @@ gunicorn==20.0.4 # via apache-superset holidays==0.10.3 # via apache-superset -humanize==3.1.0 +humanize==3.11.0 # via apache-superset -idna==2.10 +idna==3.2 # via # email-validator # yarl -importlib-metadata==2.1.1 - # via - # jsonschema - # kombu - # markdown isodate==0.6.0 # via apache-superset itsdangerous==1.1.0 @@ -143,53 +136,49 @@ kombu==4.6.11 # via celery korean-lunar-calendar==0.2.1 # via holidays -mako==1.1.3 +mako==1.1.4 # via alembic -markdown==3.3.3 +markdown==3.3.4 # via apache-superset -markupsafe==1.1.1 +markupsafe==2.0.1 # via # jinja2 # mako # wtforms -marshmallow-enum==1.5.1 - # via flask-appbuilder -marshmallow-sqlalchemy==0.23.1 - # via flask-appbuilder -marshmallow==3.9.0 +marshmallow==3.13.0 # via # flask-appbuilder # marshmallow-enum # marshmallow-sqlalchemy -msgpack==1.0.0 +marshmallow-enum==1.5.1 + # via flask-appbuilder +marshmallow-sqlalchemy==0.23.1 + # via flask-appbuilder +msgpack==1.0.2 # via apache-superset -multidict==5.0.0 +multidict==5.1.0 # via # aiohttp # yarl -natsort==7.0.1 - # via croniter -numpy==1.19.4 +numpy==1.21.1 # via # pandas # pyarrow -packaging==20.4 - # via bleach -pandas==1.2.2 +packaging==21.0 + # via + # bleach + # deprecation +pandas==1.2.5 # via apache-superset parsedatetime==2.6 # via apache-superset -pathlib2==2.3.5 - # via apache-superset pgsanity==0.2.9 # via apache-superset polyline==1.4.0 # via apache-superset prison==0.1.3 # via flask-appbuilder -py==1.9.0 - # via retry -pyarrow==3.0.0 +pyarrow==4.0.1 # via apache-superset pycparser==2.20 # via cffi @@ -198,7 +187,7 @@ pyjwt==1.7.1 # apache-superset # flask-appbuilder # flask-jwt-extended -pymeeus==0.3.7 +pymeeus==0.5.11 # via convertdate pyparsing==2.4.7 # via @@ -208,7 +197,7 @@ pyrsistent==0.16.1 # via # -r requirements/base.in # jsonschema -python-dateutil==2.8.1 +python-dateutil==2.8.2 # via # alembic # apache-superset @@ -216,7 +205,7 @@ python-dateutil==2.8.1 # flask-appbuilder # holidays # pandas -python-dotenv==0.15.0 +python-dotenv==0.19.0 # via apache-superset python-editor==1.0.4 # via alembic @@ -224,7 +213,7 @@ python-geohash==0.8.5 # via apache-superset python3-openid==3.2.0 # via flask-openid -pytz==2020.4 +pytz==2021.1 # via # babel # celery @@ -237,36 +226,30 @@ pyyaml==5.4.1 # apispec redis==3.5.3 # via apache-superset -retry==0.9.2 - # via apache-superset +sasl==0.2.1 + # via -r requirements/base.in selenium==3.141.0 # via apache-superset -simplejson==3.17.2 +simplejson==3.17.3 # via apache-superset -six==1.15.0 +six==1.16.0 # via # bleach - # cryptography # flask-jwt-extended # flask-talisman # holidays # isodate # jsonschema - # packaging - # pathlib2 # polyline # prison # pyrsistent # python-dateutil + # sasl # sqlalchemy-utils # wtforms-json slackclient==2.5.0 # via apache-superset -sqlalchemy-utils==0.36.8 - # via - # apache-superset - # flask-appbuilder -sqlalchemy==1.3.20 +sqlalchemy==1.3.24 # via # alembic # apache-superset @@ -274,14 +257,19 @@ sqlalchemy==1.3.20 # flask-sqlalchemy # marshmallow-sqlalchemy # sqlalchemy-utils +sqlalchemy-utils==0.36.8 + # via + # apache-superset + # flask-appbuilder sqlparse==0.3.0 # via apache-superset -typing-extensions==3.7.4.3 +tabulate==0.8.9 + # via apache-superset +typing-extensions==3.10.0.0 # via # aiohttp # apache-superset - # yarl -urllib3==1.25.11 +urllib3==1.26.6 # via selenium vine==1.3.0 # via @@ -293,18 +281,16 @@ werkzeug==1.0.1 # via # flask # flask-jwt-extended -wtforms-json==0.3.3 - # via apache-superset wtforms==2.3.3 # via # flask-wtf # wtforms-json -yarl==1.6.2 +wtforms-json==0.3.3 + # via apache-superset +yarl==1.6.3 # via aiohttp zipp==3.4.1 - # via - # -r requirements/base.in - # importlib-metadata + # via -r requirements/base.in # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/development.in b/requirements/development.in index 5ac06dc54339..db2162341dac 100644 --- a/requirements/development.in +++ b/requirements/development.in @@ -24,4 +24,5 @@ pyhive[hive]>=0.6.1 psycopg2-binary==2.8.5 tableschema thrift>=0.11.0,<1.0.0 -pygithub>=1.54.1,<2.0.0 +progress>=1.5,<2 +pyinstrument>=4.0.2,<5 diff --git a/requirements/development.txt b/requirements/development.txt index 7b6ca3ffb7a5..82133b43a320 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -1,4 +1,4 @@ -# SHA1:1b4d15a41f3498d2eb930ac3d3d4ce5d1f218a2f +# SHA1:e4f3ea65026a8aec3735d6d9977f89fef4a1a4f9 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -8,82 +8,77 @@ -r base.txt -e file:. # via -r requirements/base.in -boto3==1.16.10 +boto3==1.18.19 # via tabulator -botocore==1.19.10 +botocore==1.21.19 # via # boto3 # s3transfer cached-property==1.5.2 # via tableschema -certifi==2020.6.20 +certifi==2021.5.30 # via requests -deprecated==1.2.11 - # via pygithub -et-xmlfile==1.0.1 +charset-normalizer==2.0.4 + # via requests +et-xmlfile==1.1.0 # via openpyxl -flask-cors==3.0.9 +flask-cors==3.0.10 # via -r requirements/development.in future==0.18.2 # via pyhive -ijson==3.1.2.post0 +ijson==3.1.4 # via tabulator -jdcal==1.4.1 - # via openpyxl jmespath==0.10.0 # via # boto3 # botocore -jsonlines==1.2.0 +jsonlines==2.0.0 # via tabulator linear-tsv==1.1.0 # via tabulator mysqlclient==1.4.2.post1 # via -r requirements/development.in -openpyxl==3.0.5 +openpyxl==3.0.7 # via tabulator pillow==7.2.0 # via -r requirements/development.in +progress==1.6 + # via -r requirements/development.in psycopg2-binary==2.8.5 # via -r requirements/development.in -pydruid==0.6.1 +pure-sasl==0.6.2 + # via thrift-sasl +pydruid==0.6.2 # via -r requirements/development.in -pygithub==1.54.1 +pyhive[hive]==0.6.4 # via -r requirements/development.in -pyhive[hive]==0.6.3 +pyinstrument==4.0.2 # via -r requirements/development.in -requests==2.24.0 +requests==2.26.0 # via # pydruid - # pygithub # tableschema # tabulator -rfc3986==1.4.0 +rfc3986==1.5.0 # via tableschema -s3transfer==0.3.3 +s3transfer==0.5.0 # via boto3 -sasl==0.2.1 - # via - # pyhive - # thrift-sasl -tableschema==1.20.0 +tableschema==1.20.2 # via -r requirements/development.in -tabulator==1.52.5 +tabulator==1.53.5 # via tableschema -thrift-sasl==0.4.2 - # via pyhive thrift==0.13.0 # via # -r requirements/development.in # pyhive # thrift-sasl +thrift-sasl==0.4.3 + # via pyhive unicodecsv==0.14.1 # via # tableschema # tabulator -wrapt==1.12.1 - # via deprecated -xlrd==1.2.0 +xlrd==2.0.1 # via tabulator # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/docker.txt b/requirements/docker.txt index e1d3f07003ab..545e919791bd 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -8,15 +8,15 @@ -r base.txt -e file:. # via -r requirements/base.in -gevent==20.9.0 +gevent==21.8.0 # via -r requirements/docker.in -greenlet==0.4.17 +greenlet==1.1.1 # via gevent -psycopg2-binary==2.8.6 +psycopg2-binary==2.9.1 # via -r requirements/docker.in zope.event==4.5.0 # via gevent -zope.interface==5.1.2 +zope.interface==5.4.0 # via gevent # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/integration.in b/requirements/integration.in index 1aaee9255f97..1bc94c7fb71d 100644 --- a/requirements/integration.in +++ b/requirements/integration.in @@ -17,3 +17,6 @@ pip-compile-multi!=1.5.9 pre-commit tox +py>=1.10.0 +click==7.1.2 +packaging==21.0 diff --git a/requirements/integration.txt b/requirements/integration.txt index e1cb0edb3e2a..b7da99e3da36 100644 --- a/requirements/integration.txt +++ b/requirements/integration.txt @@ -1,70 +1,74 @@ -# SHA1:f95c1152ed0bcc554f3668440d63eec2a7d1567c +# SHA1:17ab2346746deadfc557e1df96014e77c8337f4b # # This file is autogenerated by pip-compile-multi # To update, run: # # pip-compile-multi # -appdirs==1.4.4 +backports.entry-points-selectable==1.1.0 # via virtualenv -cfgv==3.2.0 +cfgv==3.3.0 # via pre-commit click==7.1.2 # via + # -r requirements/integration.in # pip-compile-multi # pip-tools -distlib==0.3.1 +distlib==0.3.2 # via virtualenv filelock==3.0.12 # via # tox # virtualenv -identify==1.5.9 +identify==2.2.13 # via pre-commit -importlib-metadata==2.1.1 +nodeenv==1.6.0 + # via pre-commit +packaging==21.0 # via - # pluggy - # pre-commit + # -r requirements/integration.in # tox - # virtualenv -nodeenv==1.5.0 - # via pre-commit -packaging==20.4 - # via tox +pep517==0.11.0 + # via pip-tools pip-compile-multi==2.4.1 # via -r requirements/integration.in -pip-tools==5.3.1 +pip-tools==6.2.0 # via pip-compile-multi +platformdirs==2.2.0 + # via virtualenv pluggy==0.13.1 # via tox -pre-commit==2.8.2 +pre-commit==2.14.0 # via -r requirements/integration.in -py==1.9.0 - # via tox +py==1.10.0 + # via + # -r requirements/integration.in + # tox pyparsing==2.4.7 # via packaging pyyaml==5.4.1 # via pre-commit -six==1.15.0 +six==1.16.0 # via - # packaging - # pip-tools # tox # virtualenv toml==0.10.2 # via # pre-commit # tox -toposort==1.5 +tomli==1.2.1 + # via pep517 +toposort==1.6 # via pip-compile-multi -tox==3.20.1 +tox==3.24.1 # via -r requirements/integration.in -virtualenv==20.1.0 +virtualenv==20.7.2 # via # pre-commit # tox -zipp==3.4.1 - # via importlib-metadata +wheel==0.37.0 + # via pip-tools # The following packages are considered to be unsafe in a requirements file: # pip +# setuptools diff --git a/requirements/testing.in b/requirements/testing.in index e95cab68f04c..3355d71fa433 100644 --- a/requirements/testing.in +++ b/requirements/testing.in @@ -22,13 +22,15 @@ freezegun ipdb # pinning ipython as pip-compile-multi was bringing higher version # of the ipython that was not found in CI -ipython==7.16.1 +ipython openapi-spec-validator openpyxl parameterized pyfakefs pyhive[presto]>=0.6.3 -pylint +pylint==2.6.0 pytest pytest-cov statsd +pytest-mock +packaging==21.0 diff --git a/requirements/testing.txt b/requirements/testing.txt index 87b87060e8ca..ccbd88989e6a 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,4 +1,4 @@ -# SHA1:dba8c39bbdcb85b86e83af855d023b55a2674e87 +# SHA1:5bfcfb5d0ab31dd532ce58caa2aab91d6807b123 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -9,78 +9,91 @@ -r integration.txt -e file:. # via -r requirements/base.in -appnope==0.1.0 +appnope==0.1.2 # via ipython -astroid==2.4.2 +astroid==2.6.6 # via pylint backcall==0.2.0 # via ipython -coverage==5.3 +coverage==5.5 # via pytest-cov -docker==4.3.1 +decorator==5.0.9 + # via + # ipdb + # ipython +docker==5.0.0 # via -r requirements/testing.in -flask-testing==0.8.0 +flask-testing==0.8.1 # via -r requirements/testing.in -freezegun==1.0.0 +freezegun==1.1.0 # via -r requirements/testing.in iniconfig==1.1.1 # via pytest -ipdb==0.13.4 +ipdb==0.13.9 # via -r requirements/testing.in -ipython-genutils==0.2.0 - # via traitlets -ipython==7.16.1 +ipython==7.26.0 # via # -r requirements/testing.in # ipdb -isort==5.6.4 +ipython-genutils==0.2.0 + # via traitlets +isort==5.9.3 # via pylint -jedi==0.17.2 +jedi==0.18.0 # via ipython -lazy-object-proxy==1.4.3 +lazy-object-proxy==1.6.0 # via astroid +matplotlib-inline==0.1.2 + # via ipython mccabe==0.6.1 # via pylint -openapi-spec-validator==0.2.9 +openapi-schema-validator==0.1.5 + # via openapi-spec-validator +openapi-spec-validator==0.3.1 # via -r requirements/testing.in -parameterized==0.7.4 +parameterized==0.8.1 # via -r requirements/testing.in -parso==0.7.1 +parso==0.8.2 # via jedi pexpect==4.8.0 # via ipython pickleshare==0.7.5 # via ipython -prompt-toolkit==3.0.8 +prompt-toolkit==3.0.19 # via ipython -ptyprocess==0.6.0 +ptyprocess==0.7.0 # via pexpect -pyfakefs==4.4.0 +pyfakefs==4.5.0 # via -r requirements/testing.in -pygments==2.7.2 +pygments==2.9.0 # via ipython -pyhive[hive,presto]==0.6.3 +pyhive[hive,presto]==0.6.4 # via # -r requirements/development.in # -r requirements/testing.in -pylint==2.6.0 +pylint==2.9.6 # via -r requirements/testing.in -pytest-cov==2.10.1 - # via -r requirements/testing.in -pytest==6.1.2 +pytest==6.2.4 # via # -r requirements/testing.in # pytest-cov + # pytest-mock +pytest-cov==2.12.1 + # via -r requirements/testing.in +pytest-mock==3.6.1 + # via -r requirements/testing.in statsd==3.3.0 # via -r requirements/testing.in traitlets==5.0.5 - # via ipython -typed-ast==1.4.3 - # via astroid + # via + # ipython + # matplotlib-inline wcwidth==0.2.5 # via prompt-toolkit -websocket-client==0.57.0 +websocket-client==1.2.0 # via docker +wrapt==1.12.1 + # via astroid # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/scripts/benchmark_migration.py b/scripts/benchmark_migration.py index 0faa92a88552..27670b5d4d72 100644 --- a/scripts/benchmark_migration.py +++ b/scripts/benchmark_migration.py @@ -25,10 +25,13 @@ from typing import Dict, List, Set, Type import click +from flask import current_app from flask_appbuilder import Model from flask_migrate import downgrade, upgrade from graphlib import TopologicalSorter # pylint: disable=wrong-import-order -from sqlalchemy import inspect +from progress.bar import ChargingBar +from sqlalchemy import create_engine, inspect +from sqlalchemy.ext.automap import automap_base from superset import db from superset.utils.mock_data import add_sample_rows @@ -41,9 +44,13 @@ def import_migration_script(filepath: Path) -> ModuleType: Import migration script as if it were a module. """ spec = importlib.util.spec_from_file_location(filepath.stem, filepath) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore - return module + if spec: + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore + return module + raise Exception( + "No module spec found in location: `{path}`".format(path=str(filepath)) + ) def extract_modified_tables(module: ModuleType) -> Set[str]: @@ -83,11 +90,29 @@ def find_models(module: ModuleType) -> List[Type[Model]]: elif isinstance(obj, dict): queue.extend(obj.values()) - # add implicit models - # pylint: disable=no-member, protected-access - for obj in Model._decl_class_registry.values(): - if hasattr(obj, "__table__") and obj.__table__.fullname in tables: - models.append(obj) + # build models by automapping the existing tables, instead of using current + # code; this is needed for migrations that modify schemas (eg, add a column), + # where the current model is out-of-sync with the existing table after a + # downgrade + sqlalchemy_uri = current_app.config["SQLALCHEMY_DATABASE_URI"] + engine = create_engine(sqlalchemy_uri) + Base = automap_base() + Base.prepare(engine, reflect=True) + seen = set() + while tables: + table = tables.pop() + seen.add(table) + model = getattr(Base.classes, table) + model.__tablename__ = table + models.append(model) + + # add other models referenced in foreign keys + inspector = inspect(model) + for column in inspector.columns.values(): + for foreign_key in column.foreign_keys: + table = foreign_key.column.table.name + if table not in seen: + tables.add(table) # sort topologically so we can create entities in order and # maintain relationships (eg, create a database before creating @@ -98,7 +123,8 @@ def find_models(module: ModuleType) -> List[Type[Model]]: dependent_tables: List[str] = [] for column in inspector.columns.values(): for foreign_key in column.foreign_keys: - dependent_tables.append(foreign_key.target_fullname.split(".")[0]) + if foreign_key.column.table.name != model.__tablename__: + dependent_tables.append(foreign_key.column.table.name) sorter.add(model.__tablename__, *dependent_tables) order = list(sorter.static_order()) models.sort(key=lambda model: order.index(model.__tablename__)) @@ -133,15 +159,6 @@ def main( ).scalar() print(f"Current version of the DB is {current_revision}") - print("\nIdentifying models used in the migration:") - models = find_models(module) - model_rows: Dict[Type[Model], int] = {} - for model in models: - rows = session.query(model).count() - print(f"- {model.__name__} ({rows} rows in table {model.__tablename__})") - model_rows[model] = rows - session.close() - if current_revision != down_revision: if not force: click.confirm( @@ -152,6 +169,15 @@ def main( ) downgrade(revision=down_revision) + print("\nIdentifying models used in the migration:") + models = find_models(module) + model_rows: Dict[Type[Model], int] = {} + for model in models: + rows = session.query(model).count() + print(f"- {model.__name__} ({rows} rows in table {model.__tablename__})") + model_rows[model] = rows + session.close() + print("Benchmarking migration") results: Dict[str, float] = {} start = time.time() @@ -168,18 +194,23 @@ def main( for model in models: missing = min_entities - model_rows[model] if missing > 0: + entities: List[Model] = [] print(f"- Adding {missing} entities to the {model.__name__} model") + bar = ChargingBar("Processing", max=missing) try: - added_models = add_sample_rows(session, model, missing) + for entity in add_sample_rows(session, model, missing): + entities.append(entity) + bar.next() except Exception: session.rollback() raise + bar.finish() model_rows[model] = min_entities + session.add_all(entities) session.commit() if auto_cleanup: - new_models[model].extend(added_models) - + new_models[model].extend(entities) start = time.time() upgrade(revision=revision) duration = time.time() - start @@ -187,6 +218,10 @@ def main( results[f"{min_entities}+"] = duration min_entities *= 10 + print("\nResults:\n") + for label, duration in results.items(): + print(f"{label}: {duration:.2f} s") + if auto_cleanup: print("Cleaning up DB") # delete in reverse order of creation to handle relationships @@ -201,10 +236,6 @@ def main( upgrade(revision=revision) print("Reverted") - print("\nResults:\n") - for label, duration in results.items(): - print(f"{label}: {duration:.2f} s") - if __name__ == "__main__": from superset.app import create_app diff --git a/scripts/cancel_github_workflows.py b/scripts/cancel_github_workflows.py index 84acda0979d2..90087fa4f736 100755 --- a/scripts/cancel_github_workflows.py +++ b/scripts/cancel_github_workflows.py @@ -33,7 +33,7 @@ ./cancel_github_workflows.py 1024 --include-last """ import os -from typing import Iterable, List, Optional, Union +from typing import Any, Dict, Iterable, Iterator, List, Optional, Union import click import requests @@ -45,7 +45,9 @@ github_repo = os.environ.get("GITHUB_REPOSITORY", "apache/superset") -def request(method: Literal["GET", "POST", "DELETE", "PUT"], endpoint: str, **kwargs): +def request( + method: Literal["GET", "POST", "DELETE", "PUT"], endpoint: str, **kwargs: Any +) -> Dict[str, Any]: resp = requests.request( method, f"https://api.github.com/{endpoint.lstrip('/')}", @@ -57,10 +59,14 @@ def request(method: Literal["GET", "POST", "DELETE", "PUT"], endpoint: str, **kw return resp -def list_runs(repo: str, params=None): +def list_runs( + repo: str, params: Optional[Dict[str, str]] = None, +) -> Iterator[Dict[str, Any]]: """List all github workflow runs. Returns: An iterator that will iterate through all pages of matching runs.""" + if params is None: + params = {} page = 1 total_count = 10000 while page * 100 < total_count: @@ -75,11 +81,11 @@ def list_runs(repo: str, params=None): page += 1 -def cancel_run(repo: str, run_id: Union[str, int]): +def cancel_run(repo: str, run_id: Union[str, int]) -> Dict[str, Any]: return request("POST", f"/repos/{repo}/actions/runs/{run_id}/cancel") -def get_pull_request(repo: str, pull_number: Union[str, int]): +def get_pull_request(repo: str, pull_number: Union[str, int]) -> Dict[str, Any]: return request("GET", f"/repos/{repo}/pulls/{pull_number}") @@ -89,7 +95,7 @@ def get_runs( user: Optional[str] = None, statuses: Iterable[str] = ("queued", "in_progress"), events: Iterable[str] = ("pull_request", "push"), -): +) -> List[Dict[str, Any]]: """Get workflow runs associated with the given branch""" return [ item @@ -101,7 +107,7 @@ def get_runs( ] -def print_commit(commit, branch): +def print_commit(commit: Dict[str, Any], branch: str) -> None: """Print out commit message for verification""" indented_message = " \n".join(commit["message"].split("\n")) date_str = ( @@ -151,7 +157,7 @@ def cancel_github_workflows( event: List[str], include_last: bool, include_running: bool, -): +) -> None: """Cancel running or queued GitHub workflows by branch or pull request ID""" if not github_token: raise ClickException("Please provide GITHUB_TOKEN as an env variable") @@ -231,7 +237,7 @@ def cancel_github_workflows( try: print(f"[{entry['status']}] {entry['name']}", end="\r") cancel_run(repo, entry["id"]) - print(f"[Cancled] {entry['name']} ") + print(f"[Canceled] {entry['name']} ") except ClickException as error: print(f"[Error: {error.message}] {entry['name']} ") print("") diff --git a/scripts/ci_check_no_file_changes.sh b/scripts/ci_check_no_file_changes.sh index e03a6aca54c6..592faa54628b 100755 --- a/scripts/ci_check_no_file_changes.sh +++ b/scripts/ci_check_no_file_changes.sh @@ -34,7 +34,7 @@ REGEXES=() for CHECK in "$@" do if [[ ${CHECK} == "python" ]]; then - REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^setup\.py|^requirements\/.+\.txt)" + REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^setup\.py|^requirements\/.+\.txt|^\.pylintrc)" echo "Searching for changes in python files" elif [[ ${CHECK} == "frontend" ]]; then REGEX="(^\.github\/workflows\/.*(frontend|e2e)|^superset-frontend\/)" diff --git a/scripts/permissions_cleanup.py b/scripts/permissions_cleanup.py index 3656aaa43dcc..99d192919c6c 100644 --- a/scripts/permissions_cleanup.py +++ b/scripts/permissions_cleanup.py @@ -19,7 +19,7 @@ from superset import security_manager -def cleanup_permissions(): +def cleanup_permissions() -> None: # 1. Clean up duplicates. pvms = security_manager.get_session.query( security_manager.permissionview_model diff --git a/scripts/python_tests.sh b/scripts/python_tests.sh index d34e605a21ca..b9ef2cee2104 100755 --- a/scripts/python_tests.sh +++ b/scripts/python_tests.sh @@ -18,7 +18,7 @@ # set -e -export SUPERSET_CONFIG=${SUPERSET_CONFIG:-tests.superset_test_config} +export SUPERSET_CONFIG=${SUPERSET_CONFIG:-tests.integration_tests.superset_test_config} export SUPERSET_TESTENV=true echo "Superset config module: $SUPERSET_CONFIG" diff --git a/scripts/tests/run.sh b/scripts/tests/run.sh index be1a11988e97..9f78318b72b5 100755 --- a/scripts/tests/run.sh +++ b/scripts/tests/run.sh @@ -62,7 +62,7 @@ DB_NAME="test" DB_USER="superset" DB_PASSWORD="superset" export SUPERSET__SQLALCHEMY_DATABASE_URI=${SUPERSET__SQLALCHEMY_DATABASE_URI:-postgresql+psycopg2://"${DB_USER}":"${DB_PASSWORD}"@localhost/"${DB_NAME}"} -export SUPERSET_CONFIG=${SUPERSET_CONFIG:-tests.superset_test_config} +export SUPERSET_CONFIG=${SUPERSET_CONFIG:-tests.integration_tests.superset_test_config} RUN_INIT=1 RUN_RESET_DB=1 RUN_TESTS=1 diff --git a/setup.cfg b/setup.cfg index 8fd75a0bd6c5..9a108f76a480 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,21 +30,23 @@ combine_as_imports = true include_trailing_comma = true line_length = 88 known_first_party = superset -known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,contextlib2,cron_descriptor,croniter,cryptography,dateutil,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pathlib2,pgsanity,pkg_resources,polyline,prison,pyarrow,pyhive,pyparsing,pytest,pytz,redis,requests,retry,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,werkzeug,wtforms,wtforms_json,yaml +known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml multi_line_output = 3 order_by_type = false [mypy] +check_untyped_defs = true disallow_any_generics = true +disallow_untyped_calls = true +disallow_untyped_defs = true ignore_missing_imports = true no_implicit_optional = true warn_unused_ignores = true -[mypy-superset.*] -check_untyped_defs = true -disallow_untyped_calls = true -disallow_untyped_defs = true -warn_unused_ignores = false - [mypy-superset.migrations.versions.*] ignore_errors = true + +[mypy-tests.*] +check_untyped_defs = false +disallow_untyped_calls = false +disallow_untyped_defs = false diff --git a/setup.py b/setup.py index 30e4c5479f7e..19a08951df0b 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ long_description = f.read() -def get_git_sha(): +def get_git_sha() -> str: try: s = subprocess.check_output(["git", "rev-parse", "HEAD"]) return s.decode().strip() @@ -70,13 +70,13 @@ def get_git_sha(): "celery>=4.3.0, <5.0.0, !=4.4.1", "click<8", "colorama", - "contextlib2", "croniter>=0.3.28", "cron-descriptor", "cryptography>=3.3.2", + "deprecation>=2.1.0, <2.2.0", "flask>=1.1.0, <2.0.0", "flask-appbuilder>=3.3.0, <4.0.0", - "flask-caching", + "flask-caching>=1.10.0", "flask-compress", "flask-talisman", "flask-migrate", @@ -84,37 +84,36 @@ def get_git_sha(): "geopy", "graphlib-backport", "gunicorn>=20.0.2, <20.1", + "holidays==0.10.3", # PINNED! https://github.com/dr-prodigy/python-holidays/issues/406 "humanize", - "itsdangerous>=1.0.0, <2.0.0", + "itsdangerous>=1.0.0, <2.0.0", # https://github.com/apache/superset/pull/14627 "isodate", "markdown>=3.0", "msgpack>=1.0.0, <1.1", "pandas>=1.2.2, <1.3", "parsedatetime", - "pathlib2", "pgsanity", "polyline", + "pyparsing>=2.4.7, <3.0.0", "python-dateutil", "python-dotenv", "python-geohash", - "pyarrow>=3.0.0, <3.1", + "pyarrow>=4.0.1, <4.1", "pyyaml>=5.4", "PyJWT>=1.7.1, <2", "redis", - "retry>=0.9.2", "selenium>=3.141.0", "simplejson>=3.15.0", "slackclient==2.5.0", # PINNED! slack changes file upload api in the future versions "sqlalchemy>=1.3.16, <1.4, !=1.3.21", - "sqlalchemy-utils>=0.36.6,<0.37", + "sqlalchemy-utils>=0.36.6, <0.37", "sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562 - "typing-extensions>=3.7.4.3,<4", # needed to support typing.Literal on py37 + "tabulate==0.8.9", + "typing-extensions>=3.10, <4", # needed to support Literal (3.8) and TypeGuard (3.10) "wtforms-json", - "pyparsing>=2.4.7, <3.0.0", - "holidays==0.10.3", # PINNED! https://github.com/dr-prodigy/python-holidays/issues/406 ], extras_require={ - "athena": ["pyathena>=1.10.8,<1.11"], + "athena": ["pyathena>=1.10.8, <1.11"], "bigquery": [ "pandas_gbq>=0.10.0", "pybigquery>=0.4.10", @@ -134,7 +133,7 @@ def get_git_sha(): "exasol": ["sqlalchemy-exasol>=2.1.0, <2.2"], "excel": ["xlrd>=1.2.0, <1.3"], "firebird": ["sqlalchemy-firebird>=0.7.0, <0.8"], - "gsheets": ["shillelagh[gsheetsapi]>=0.5, <0.6"], + "gsheets": ["shillelagh[gsheetsapi]>=0.7.1, <0.8"], "hana": ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"], "hive": ["pyhive[hive]>=0.6.1", "tableschema", "thrift>=0.11.0, <1.0.0"], "impala": ["impyla>0.16.2, <0.17"], @@ -148,10 +147,12 @@ def get_git_sha(): "trino": ["sqlalchemy-trino>=0.2"], "prophet": ["prophet>=1.0.1, <1.1", "pystan<3.0"], "redshift": ["sqlalchemy-redshift>=0.8.1, < 0.9"], + "rockset": ["rockset>=0.7.68, <0.8"], "snowflake": ["snowflake-sqlalchemy>=1.2.3, <1.3"], "teradata": ["sqlalchemy-teradata==0.9.0.dev0"], "thumbnails": ["Pillow>=7.0.0, <8.0.0"], "vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"], + "netezza": ["nzalchemy>=11.0.2"], }, python_requires="~=3.7", author="Apache Software Foundation", diff --git a/superset-frontend/.storybook/main.js b/superset-frontend/.storybook/main.js index 7d31e0f4f620..7b15d57dae7e 100644 --- a/superset-frontend/.storybook/main.js +++ b/superset-frontend/.storybook/main.js @@ -18,11 +18,11 @@ */ const path = require('path'); -// Suerset's webpack.config.js +// Superset's webpack.config.js const customConfig = require('../webpack.config.js'); module.exports = { -stories: ['../src/@(components|common|filters)/**/*.stories.@(t|j)sx'], + stories: ['../src/@(components|common|filters)/**/*.stories.@(t|j)sx'], addons: [ '@storybook/addon-essentials', '@storybook/addon-links', @@ -43,4 +43,7 @@ stories: ['../src/@(components|common|filters)/**/*.stories.@(t|j)sx'], }, plugins: [...config.plugins, ...customConfig.plugins], }), + typescript: { + reactDocgen: 'none', + }, }; diff --git a/superset-frontend/babel.config.js b/superset-frontend/babel.config.js index 5aa3420cf1ac..1d0cae25843d 100644 --- a/superset-frontend/babel.config.js +++ b/superset-frontend/babel.config.js @@ -45,6 +45,7 @@ module.exports = { ['@babel/plugin-proposal-class-properties', { loose: true }], ['@babel/plugin-proposal-optional-chaining', { loose: true }], ['@babel/plugin-proposal-private-methods', { loose: true }], + ['@babel/plugin-proposal-nullish-coalescing-operator', { loose: true }], ['@babel/plugin-transform-runtime', { corejs: 3 }], 'react-hot-loader/babel', ], @@ -81,5 +82,8 @@ module.exports = { ], ], }, + testableProduction: { + plugins: [], + }, }, }; diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts index 22da71912af1..1335fcb42220 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts @@ -22,7 +22,7 @@ describe('chart card view', () => { beforeEach(() => { cy.login(); cy.visit(CHART_LIST); - cy.get('[data-test="card-view"]').click(); + cy.get('[aria-label="card-view"]').click(); }); it('should load cards', () => { @@ -34,36 +34,36 @@ describe('chart card view', () => { it('should allow to favorite/unfavorite chart card', () => { cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('not.exist'); cy.get("[data-test='card-actions']") - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .first() .click(); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('be.visible'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('not.exist'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('not.exist'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .click(); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('be.visible'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('not.exist'); }); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts index dd9b573ebaf9..6892651e00ef 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts @@ -22,7 +22,7 @@ describe('chart card view filters', () => { beforeEach(() => { cy.login(); cy.visit(CHART_LIST); - cy.get('[data-test="card-view"]').click(); + cy.get('[aria-label="card-view"]').click(); }); it('should filter by owners correctly', () => { @@ -89,7 +89,7 @@ describe('chart list view filters', () => { beforeEach(() => { cy.login(); cy.visit(CHART_LIST); - cy.get('[data-test="list-view"]').click(); + cy.get('[aria-label="list-view"]').click(); }); it('should filter by owners correctly', () => { diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts index 915bf15f0ae8..6da5d90106d1 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts @@ -22,7 +22,7 @@ describe('chart list view', () => { beforeEach(() => { cy.login(); cy.visit(CHART_LIST); - cy.get('[data-test="list-view"]').click(); + cy.get('[aria-label="list-view"]').click(); }); it('should load rows', () => { @@ -51,11 +51,11 @@ describe('chart list view', () => { it('should bulk delete correctly', () => { cy.get('[data-test="listview-table"]').should('be.visible'); cy.get('[data-test="bulk-select"]').eq(0).click(); - cy.get('[data-test="checkbox-off"]').eq(1).siblings('input').click(); - cy.get('[data-test="checkbox-off"]').eq(2).siblings('input').click(); + cy.get('[aria-label="checkbox-off"]').eq(1).siblings('input').click(); + cy.get('[aria-label="checkbox-off"]').eq(2).siblings('input').click(); cy.get('[data-test="bulk-select-action"]').eq(0).click(); cy.get('[data-test="delete-modal-input"]').eq(0).type('DELETE'); cy.get('[data-test="modal-confirm-button"]').eq(0).click(); - cy.get('[data-test="checkbox-on"]').should('not.exist'); + cy.get('[aria-label="checkbox-on"]').should('not.exist'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts index 73587ee3acd9..5ba40db499e3 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts @@ -63,7 +63,7 @@ describe('Dashboard top-level controls', () => { // should allow force refresh WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); getChartAliasesBySpec(WORLD_HEALTH_CHARTS).then(aliases => { - cy.get('[data-test="more-horiz"]').click(); + cy.get('[aria-label="more-horiz"]').click(); cy.get('[data-test="refresh-dashboard-menu-item"]').should( 'not.have.class', 'ant-dropdown-menu-item-disabled', @@ -92,7 +92,7 @@ describe('Dashboard top-level controls', () => { }); }); }); - cy.get('[data-test="more-horiz"]').click(); + cy.get('[aria-label="more-horiz"]').click(); cy.get('[data-test="refresh-dashboard-menu-item"]').and( 'not.have.class', 'ant-dropdown-menu-item-disabled', diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js index 62f40c6162f4..d799dccd3bb6 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js @@ -23,7 +23,7 @@ describe('Dashboard edit mode', () => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); cy.get('[data-test="dashboard-header"]') - .find('[data-test=edit-alt]') + .find('[aria-label=edit-alt]') .click(); }); @@ -96,7 +96,7 @@ describe('Dashboard edit mode', () => { .click(); cy.get('[data-test="dashboard-header"]').within(() => { cy.get('[data-test="dashboard-edit-actions"]').should('not.be.visible'); - cy.get('[data-test="edit-alt"]').should('be.visible'); + cy.get('[aria-label="edit-alt"]').should('be.visible'); }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts index ad20010a824d..c5cbfb7bba66 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts @@ -77,7 +77,7 @@ describe('Dashboard edit action', () => { cy.get('.dashboard-grid', { timeout: 50000 }) .should('be.visible') // wait for 50 secs to load dashboard .then(() => { - cy.get('.dashboard-header [data-test=edit-alt]') + cy.get('.dashboard-header [aria-label=edit-alt]') .should('be.visible') .click(); openDashboardEditProperties(); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js index 812a3c9e7851..a20b1eb3f597 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js @@ -38,23 +38,23 @@ describe('Dashboard add to favorite', () => { it('should allow favor/unfavor', () => { if (!isFavoriteDashboard) { cy.get('[data-test="fave-unfave-icon"]') - .find('svg') - .should('have.attr', 'data-test', 'favorite-unselected'); + .find('span') + .should('have.attr', 'aria-label', 'favorite-unselected'); cy.get('[data-test="fave-unfave-icon"]').trigger('click'); cy.get('[data-test="fave-unfave-icon"]') - .find('svg') - .should('have.attr', 'data-test', 'favorite-selected') - .and('not.have.attr', 'data-test', 'favorite-unselected'); + .find('span') + .should('have.attr', 'aria-label', 'favorite-selected') + .and('not.have.attr', 'aria-label', 'favorite-unselected'); } else { cy.get('[data-test="fave-unfave-icon"]') - .find('svg') - .should('have.attr', 'data-test', 'favorite-unselected') - .and('not.have.attr', 'data-test', 'favorite-selected'); + .find('span') + .should('have.attr', 'aria-label', 'favorite-unselected') + .and('not.have.attr', 'aria-label', 'favorite-selected'); cy.get('[data-test="fave-unfave-icon"]').trigger('click'); cy.get('[data-test="fave-unfave-icon"]') - .find('svg') - .should('have.attr', 'data-test', 'favorite-unselected') - .and('not.have.attr', 'data-test', 'favorite-selected'); + .find('span') + .should('have.attr', 'aria-label', 'favorite-unselected') + .and('not.have.attr', 'aria-label', 'favorite-selected'); } // reset to original fav state diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts index 375f14dacdd8..9b30639c24b6 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts @@ -38,7 +38,7 @@ describe('Dashboard filter', () => { cy.get('.Select__placeholder:first').click(); // should show the filter indicator - cy.get('svg[data-test="filter"]:visible').should(nodes => { + cy.get('span[aria-label="filter"]:visible').should(nodes => { expect(nodes.length).to.least(9); }); @@ -50,7 +50,7 @@ describe('Dashboard filter', () => { cy.get('.Select__menu').first().contains('South Asia').click(); // should still have all filter indicators - cy.get('svg[data-test="filter"]:visible').should(nodes => { + cy.get('span[aria-label="filter"]:visible').should(nodes => { expect(nodes.length).to.least(9); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts index a13a1c91460a..3f69c9f804f8 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts @@ -30,12 +30,12 @@ describe('Dashboard edit markdown', () => { numScripts = nodes.length; }); cy.get('[data-test="dashboard-header"]') - .find('[data-test="edit-alt"]') + .find('[aria-label="edit-alt"]') .click(); // lazy load - need to open dropdown for the scripts to load cy.get('[data-test="dashboard-header"]') - .find('[data-test="more-horiz"]') + .find('[aria-label="more-horiz"]') .click(); cy.get('script').then(nodes => { // load 5 new script chunks for css editor @@ -70,7 +70,7 @@ describe('Dashboard edit markdown', () => { .type('Test resize'); resize( - '[data-test="dashboard-markdown-editor"] .resizable-container span div', + '[data-test="dashboard-markdown-editor"] .resizable-container span div:last-child', ).to(500, 600); cy.get('[data-test="dashboard-markdown-editor"]').contains('Test resize'); @@ -78,7 +78,7 @@ describe('Dashboard edit markdown', () => { // entering edit mode does not add new scripts // (though scripts may still be removed by others) cy.get('script').then(nodes => { - expect(nodes.length).to.most(numScripts); + expect(nodes.length).to.greaterThan(numScripts); }); cy.get('@component-background-first').click('right'); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js index b56da45b89e8..2555730c38bd 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js @@ -25,7 +25,7 @@ import { } from './dashboard.helper'; function openDashboardEditProperties() { - cy.get('.dashboard-header [data-test=edit-alt]').click(); + cy.get('.dashboard-header [aria-label=edit-alt]').click(); cy.get('#save-dash-split-button').trigger('click', { force: true }); cy.get('.dropdown-menu').contains('Edit dashboard properties').click(); } @@ -42,7 +42,7 @@ describe('Dashboard save action', () => { 'copyRequest', ); - cy.get('[data-test="more-horiz"]').trigger('click', { force: true }); + cy.get('[aria-label="more-horiz"]').trigger('click', { force: true }); cy.get('[data-test="save-as-menu-item"]').trigger('click', { force: true, }); @@ -68,7 +68,7 @@ describe('Dashboard save action', () => { WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); // remove box_plot chart from dashboard - cy.get('[data-test="edit-alt"]').click({ timeout: 5000 }); + cy.get('[aria-label="edit-alt"]').click({ timeout: 5000 }); cy.get('[data-test="dashboard-delete-component-button"]') .last() .trigger('moustenter') @@ -87,7 +87,7 @@ describe('Dashboard save action', () => { // go back to view mode cy.wait('@saveRequest'); cy.get('[data-test="dashboard-header"]') - .find('[data-test="edit-alt"]') + .find('[aria-label="edit-alt"]') .click(); // deleted boxplot should still not exist diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts index 841fb560b127..8bfc35d71c84 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts @@ -22,7 +22,7 @@ describe('Dashboard card view', () => { beforeEach(() => { cy.login(); cy.visit(DASHBOARD_LIST); - cy.get('[data-test="card-view"]').click(); + cy.get('[aria-label="card-view"]').click(); }); xit('should load cards', () => { @@ -34,36 +34,36 @@ describe('Dashboard card view', () => { it('should allow to favorite/unfavorite dashboard card', () => { cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('not.exist'); cy.get("[data-test='card-actions']") - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .first() .click(); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('be.visible'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('not.exist'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('not.exist'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .click(); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-unselected']") + .find("[aria-label='favorite-unselected']") .should('be.visible'); cy.get("[data-test='card-actions']") .first() - .find("[data-test='favorite-selected']") + .find("[aria-label='favorite-selected']") .should('not.exist'); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts index caff4c03f675..aede8e7d5466 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts @@ -22,7 +22,7 @@ describe('dashboard filters card view', () => { beforeEach(() => { cy.login(); cy.visit(DASHBOARD_LIST); - cy.get('[data-test="card-view"]').click(); + cy.get('[aria-label="card-view"]').click(); }); it('should filter by owners correctly', () => { @@ -59,13 +59,13 @@ describe('dashboard filters card view', () => { // filter by published cy.get('.Select__control').eq(2).click(); cy.get('.Select__menu').contains('Published').click({ timeout: 5000 }); - cy.get('[data-test="styled-card"]').should('have.length', 2); + cy.get('[data-test="styled-card"]').should('have.length', 3); cy.get('[data-test="styled-card"]') .contains('USA Births Names') .should('be.visible'); cy.get('.Select__control').eq(1).click(); cy.get('.Select__control').eq(1).type('unpub{enter}'); - cy.get('[data-test="styled-card"]').should('have.length', 2); + cy.get('[data-test="styled-card"]').should('have.length', 3); }); }); @@ -73,7 +73,7 @@ describe('dashboard filters list view', () => { beforeEach(() => { cy.login(); cy.visit(DASHBOARD_LIST); - cy.get('[data-test="list-view"]').click(); + cy.get('[aria-label="list-view"]').click(); }); it('should filter by owners correctly', () => { @@ -110,12 +110,12 @@ describe('dashboard filters list view', () => { // filter by published cy.get('.Select__control').eq(2).click(); cy.get('.Select__menu').contains('Published').click(); - cy.get('[data-test="table-row"]').should('have.length', 2); + cy.get('[data-test="table-row"]').should('have.length', 3); cy.get('[data-test="table-row"]') .contains('USA Births Names') .should('be.visible'); cy.get('.Select__control').eq(2).click(); cy.get('.Select__control').eq(2).type('unpub{enter}'); - cy.get('[data-test="table-row"]').should('have.length', 2); + cy.get('[data-test="table-row"]').should('have.length', 3); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts index 924cf84b6e46..a758552481f9 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts @@ -22,7 +22,7 @@ describe('dashboard list view', () => { beforeEach(() => { cy.login(); cy.visit(DASHBOARD_LIST); - cy.get('[data-test="list-view"]').click(); + cy.get('[aria-label="list-view"]').click(); }); xit('should load rows', () => { @@ -51,11 +51,11 @@ describe('dashboard list view', () => { it('should bulk delete correctly', () => { cy.get('[data-test="listview-table"]').should('be.visible'); cy.get('[data-test="bulk-select"]').eq(0).click(); - cy.get('[data-test="checkbox-off"]').eq(1).siblings('input').click(); - cy.get('[data-test="checkbox-off"]').eq(2).siblings('input').click(); + cy.get('[aria-label="checkbox-off"]').eq(1).siblings('input').click(); + cy.get('[aria-label="checkbox-off"]').eq(2).siblings('input').click(); cy.get('[data-test="bulk-select-action"]').eq(0).click(); cy.get('[data-test="delete-modal-input"]').eq(0).type('DELETE'); cy.get('[data-test="modal-confirm-button"]').eq(0).click(); - cy.get('[data-test="checkbox-on"]').should('not.exist'); + cy.get('[aria-label="checkbox-on"]').should('not.exist'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts index e29ec48a9479..225502167656 100644 --- a/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts @@ -21,71 +21,50 @@ import { DATABASE_LIST } from './helper'; describe('Add database', () => { beforeEach(() => { cy.login(); - }); - - it('should keep create modal open when error', () => { cy.visit(DATABASE_LIST); - - // open modal + cy.wait(3000); cy.get('[data-test="btn-create-database"]').click(); + }); - // values should be blank - cy.get('[data-test="database-modal"] input[name="database_name"]').should( - 'have.value', - '', - ); - cy.get('[data-test="database-modal"] input[name="sqlalchemy_uri"]').should( - 'have.value', - '', - ); - - // type values - cy.get('[data-test="database-modal"] input[name="database_name"]') - .focus() - .type('cypress'); - cy.get('[data-test="database-modal"] input[name="sqlalchemy_uri"]') - .focus() - .type('bad_db_uri'); - - // click save - cy.get('[data-test="modal-confirm-button"]:not(:disabled)').click(); - - // should show error alerts and keep modal open - cy.get('.toast').contains('error'); - cy.wait(1000); // wait for potential (incorrect) closing annimation - cy.get('[data-test="database-modal"]').should('be.visible'); + it('should open dynamic form', () => { + // click postgres dynamic form + cy.get('.preferred > :nth-child(1)').click(); - // should be able to close modal - cy.get('[data-test="modal-cancel-button"]').click(); - cy.get('[data-test="database-modal"]').should('not.be.visible'); + // make sure all the fields are rendering + cy.get('input[name="host"]').should('have.value', ''); + cy.get('input[name="port"]').should('have.value', ''); + cy.get('input[name="database"]').should('have.value', ''); + cy.get('input[name="password"]').should('have.value', ''); + cy.get('input[name="database_name"]').should('have.value', ''); }); - it('should keep update modal open when error', () => { - // open modal - cy.get('[data-test="database-edit"]:last').click(); - - // it should show saved values - cy.get('[data-test="database-modal"]:last input[name="sqlalchemy_uri"]') - .invoke('val') - .should('not.be.empty'); - cy.get('[data-test="database-modal"] input[name="database_name"]') - .invoke('val') - .should('not.be.empty'); + it('should open sqlalchemy form', () => { + // click postgres dynamic form + cy.get('.preferred > :nth-child(1)').click(); - cy.get('[data-test="database-modal"]:last input[name="sqlalchemy_uri"]') - .focus() - .dblclick() - .type('{selectall}{backspace}bad_uri'); + cy.get('[data-test="sqla-connect-btn"]').click(); - // click save - cy.get('[data-test="modal-confirm-button"]:not(:disabled)').click(); + // check if the sqlalchemy form is showing up + cy.get('[data-test=database-name-input]').should('be.visible'); + cy.get('[data-test="sqlalchemy-uri-input"]').should('be.visible'); + }); - // should show error alerts - // TODO(hugh): Update this test - // cy.get('.toast').contains('error').should('be.visible'); + it('show error alerts on dynamic form for bad host', () => { + // click postgres dynamic form + cy.get('.preferred > :nth-child(1)').click(); + cy.get('input[name="host"]').focus().type('badhost'); + cy.get('input[name="port"]').focus().type('5432'); + cy.get('.ant-form-item-explain-error').contains( + "The hostname provided can't be resolved", + ); + }); - // modal should still be open - // cy.wait(1000); // wait for potential (incorrect) closing annimation - // cy.get('[data-test="database-modal"]').should('be.visible'); + it('show error alerts on dynamic form for bad port', () => { + // click postgres dynamic form + cy.get('.preferred > :nth-child(1)').click(); + cy.get('input[name="host"]').focus().type('localhost'); + cy.get('input[name="port"]').focus().type('123'); + cy.get('input[name="database"]').focus(); + cy.get('.ant-form-item-explain-error').contains('The port is closed'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts index e6d8b5676ebd..fc81c92dde00 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/control.test.ts @@ -99,20 +99,14 @@ describe('VizType control', () => { cy.visitChartByName('Daily Totals'); cy.verifySliceSuccess({ waitAlias: '@tableChartData' }); - let numScripts = 0; - cy.get('script').then(nodes => { - numScripts = nodes.length; - }); - cy.get('[data-test="visualization-type"]').contains('Table').click(); + cy.get('button').contains('Evolution').click(); // change categories cy.get('[role="button"]').contains('Line Chart').click(); + cy.get('button').contains('Select').click(); // should load mathjs for line chart cy.get('script[src*="mathjs"]').should('have.length', 1); - cy.get('script').then(nodes => { - expect(nodes.length).to.greaterThan(numScripts); - }); cy.get('button[data-test="run-query-button"]').click(); cy.verifySliceSuccess({ diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts index 26660bc5fa67..f75fe77cceed 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts @@ -73,13 +73,12 @@ describe('Visualization > Line', () => { it('should allow type to search color schemes', () => { cy.get('#controlSections-tab-display').click(); cy.get('.Control[data-test="color_scheme"]').scrollIntoView(); - cy.get('.Control[data-test="color_scheme"] input[type="text"]') + cy.get('.Control[data-test="color_scheme"] input[type="search"]') .focus() - .type('air{enter}'); - cy.get('input[name="select-color_scheme"]').should( - 'have.value', - 'bnbColors', - ); + .type('bnbColors{enter}'); + cy.get( + '.Control[data-test="color_scheme"] .ant-select-selection-item > ul[data-test="bnbColors"]', + ).should('exist'); }); it('should work with adhoc metric', () => { diff --git a/superset-frontend/images/icons/alert_solid_small.svg b/superset-frontend/images/icons/alert_solid_small.svg index 092c37f1ce9a..b590be2a0e60 100644 --- a/superset-frontend/images/icons/alert_solid_small.svg +++ b/superset-frontend/images/icons/alert_solid_small.svg @@ -17,6 +17,6 @@ specific language governing permissions and limitations under the License. --> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path fill-rule="evenodd" clip-rule="evenodd" d="M11.335 8.735L7.30998 1.735C7.04507 1.26004 6.54382 0.965683 5.99998 0.965683C5.45614 0.965683 4.9549 1.26004 4.68998 1.735L0.689984 8.735C0.416009 9.19706 0.410037 9.77036 0.674327 10.238C0.938617 10.7057 1.43282 10.9963 1.96998 11H10.03C10.5716 11.0053 11.0741 10.7182 11.3445 10.2489C11.6149 9.77957 11.6113 9.20091 11.335 8.735Z" fill="currentColor"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 4.5C5.5 4.22386 5.72386 4 6 4C6.27614 4 6.5 4.22386 6.5 4.5V6.5C6.5 6.77614 6.27614 7 6 7C5.72386 7 5.5 6.77614 5.5 6.5V4.5ZM5.5 8.5C5.5 8.22386 5.72386 8 6 8C6.27614 8 6.5 8.22386 6.5 8.5C6.5 8.77614 6.27614 9 6 9C5.72386 9 5.5 8.77614 5.5 8.5Z" fill="white"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M17.345929226875306,14.745929226875305 L13.320909226875305,7.7459292268753055 C13.055999226875306,7.270969226875306 12.554749226875305,6.9766122268753055 12.010909226875306,6.9766122268753055 C11.467069226875305,6.9766122268753055 10.965829226875307,7.270969226875306 10.700909226875304,7.7459292268753055 L6.700913226875306,14.745929226875305 C6.426938226875307,15.207989226875306 6.420966226875306,15.781289226875305 6.685256226875307,16.248929226875305 C6.949546226875305,16.716629226875305 7.443749226875305,17.007229226875303 7.980909226875305,17.010929226875305 H16.040929226875306 C16.582529226875305,17.016229226875307 17.085029226875307,16.729129226875305 17.355429226875305,16.259829226875304 C17.625829226875304,15.790499226875305 17.622229226875305,15.211839226875306 17.345929226875306,14.745929226875305 z" fill="currentColor" /> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11.510929226875305,10.510929226875305 C11.510929226875305,10.234789226875305 11.734789226875307,10.010929226875307 12.010929226875305,10.010929226875307 C12.287069226875303,10.010929226875307 12.510929226875305,10.234789226875305 12.510929226875305,10.510929226875305 V12.510929226875305 C12.510929226875305,12.787069226875305 12.287069226875303,13.010929226875307 12.010929226875305,13.010929226875307 C11.734789226875307,13.010929226875307 11.510929226875305,12.787069226875305 11.510929226875305,12.510929226875305 V10.510929226875305 zM11.510929226875305,14.510929226875305 C11.510929226875305,14.234789226875305 11.734789226875307,14.010929226875307 12.010929226875305,14.010929226875307 C12.287069226875303,14.010929226875307 12.510929226875305,14.234789226875305 12.510929226875305,14.510929226875305 C12.510929226875305,14.787069226875305 12.287069226875303,15.010929226875307 12.010929226875305,15.010929226875307 C11.734789226875307,15.010929226875307 11.510929226875305,14.787069226875305 11.510929226875305,14.510929226875305 z" fill="white" /> </svg> diff --git a/superset-frontend/images/icons/ballot.svg b/superset-frontend/images/icons/ballot.svg new file mode 100644 index 000000000000..00d1a235ce11 --- /dev/null +++ b/superset-frontend/images/icons/ballot.svg @@ -0,0 +1,22 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M8.66667 5H12V6.33333H8.66667V5ZM8.66667 9.66667H12V11H8.66667V9.66667ZM12.6667 2H3.33333C2.6 2 2 2.6 2 3.33333V12.6667C2 13.4 2.6 14 3.33333 14H12.6667C13.4 14 14 13.4 14 12.6667V3.33333C14 2.6 13.4 2 12.6667 2ZM12.6667 12.6667H3.33333V3.33333H12.6667V12.6667ZM7.33333 4H4V7.33333H7.33333V4ZM6.66667 6.66667H4.66667V4.66667H6.66667V6.66667ZM7.33333 8.66667H4V12H7.33333V8.66667ZM6.66667 11.3333H4.66667V9.33333H6.66667V11.3333Z" fill="currentColor"/> +</svg> diff --git a/superset-frontend/images/icons/category.svg b/superset-frontend/images/icons/category.svg new file mode 100644 index 000000000000..471cf254d022 --- /dev/null +++ b/superset-frontend/images/icons/category.svg @@ -0,0 +1,22 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M7.73967 1.7002L4.04888 7.79629H11.4305L7.73967 1.7002ZM7.73967 4.3012L9.0348 6.4416H6.43783L7.73967 4.3012ZM11.4305 9.15098C9.75954 9.15098 8.41072 10.5124 8.41072 12.199C8.41072 13.8856 9.75954 15.2471 11.4305 15.2471C13.1014 15.2471 14.4502 13.8856 14.4502 12.199C14.4502 10.5124 13.1014 9.15098 11.4305 9.15098ZM11.4305 13.8924C10.5044 13.8924 9.75283 13.1338 9.75283 12.199C9.75283 11.2643 10.5044 10.5057 11.4305 10.5057C12.3565 10.5057 13.1081 11.2643 13.1081 12.199C13.1081 13.1338 12.3565 13.8924 11.4305 13.8924ZM1.7002 14.9084H7.06862V9.48965H1.7002V14.9084ZM3.0423 10.8443H5.72651V13.5537H3.0423V10.8443Z" fill="currentColor" fill-opacity="0.85"/> +</svg> diff --git a/superset-frontend/images/icons/default_db_image.svg b/superset-frontend/images/icons/default_db_image.svg new file mode 100644 index 000000000000..f8892bb31241 --- /dev/null +++ b/superset-frontend/images/icons/default_db_image.svg @@ -0,0 +1,21 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M15.9942 14.9661C16.0473 14.9256 16.0843 14.893 16.1094 14.8686V12.3207C15.7158 12.5563 15.2568 12.7608 14.7565 12.9359C13.2501 13.4632 11.2149 13.7763 8.99998 13.7763C6.78508 13.7763 4.74988 13.4632 3.24347 12.9359C2.74318 12.7608 2.28418 12.5563 1.89058 12.3207V14.8686C1.91568 14.893 1.95268 14.9256 2.00578 14.9661C2.26078 15.1603 2.71837 15.3956 3.40987 15.6184C4.77707 16.059 6.75368 16.3544 8.99998 16.3544C11.2463 16.3544 13.2229 16.059 14.5901 15.6184C15.2816 15.3956 15.7392 15.1603 15.9942 14.9661ZM15.7503 10.8614C16.0622 10.6033 16.1094 10.4232 16.1094 10.3388V8.41454C15.7158 8.65004 15.2568 8.85464 14.7565 9.02974C13.2501 9.55694 11.2149 9.87004 8.99998 9.87004C6.78508 9.87004 4.74988 9.55694 3.24347 9.02974C2.74318 8.85464 2.28418 8.65004 1.89058 8.41454V10.3388C1.89058 10.4232 1.93777 10.6033 2.24967 10.8614C2.55707 11.1158 3.04418 11.3763 3.70798 11.6086C5.02918 12.071 6.90007 12.37 8.99998 12.37C11.0999 12.37 12.9708 12.071 14.292 11.6086C14.9558 11.3763 15.4429 11.1158 15.7503 10.8614ZM0.484375 6.43254V10.3388V15.0165C0.484375 16.5321 4.29698 17.7607 8.99998 17.7607C13.703 17.7607 17.5156 16.5321 17.5156 15.0165V10.3388V6.43254V3.31734C17.5156 1.80174 13.703 0.573242 8.99998 0.573242H8.99508H8.99018C4.29258 0.573242 0.484375 1.80174 0.484375 3.31734V6.43254ZM16.1094 6.43254V4.81964C14.59 5.56754 11.9689 6.06144 8.99018 6.06144C6.02428 6.06144 3.41288 5.57174 1.89058 4.82924V6.43254C1.89058 6.51694 1.93777 6.69714 2.24967 6.95524C2.55707 7.20954 3.04418 7.47004 3.70798 7.70244C5.02918 8.16484 6.90007 8.46384 8.99998 8.46384C11.0999 8.46384 12.9708 8.16484 14.292 7.70244C14.9558 7.47004 15.4429 7.20954 15.7503 6.95524C16.0622 6.69714 16.1094 6.51694 16.1094 6.43254ZM2.07487 3.31764C2.33937 3.50124 2.77558 3.71554 3.40748 3.91944C4.77268 4.35984 6.74668 4.65524 8.99018 4.65524C11.2337 4.65524 13.2078 4.35984 14.573 3.91944C15.2053 3.71544 15.6416 3.50104 15.9061 3.31734C15.6416 3.13364 15.2053 2.91924 14.573 2.71524C13.2087 2.27514 11.2366 1.97984 8.99508 1.97944C6.75078 1.97984 4.77607 2.27514 3.40987 2.71544C2.77677 2.91944 2.33977 3.13384 2.07487 3.31764ZM16.1561 14.8151C16.1565 14.8152 16.1546 14.8186 16.1493 14.8253C16.1532 14.8185 16.1558 14.8151 16.1561 14.8151ZM1.84998 3.50934C1.84668 3.51524 1.84438 3.51814 1.84408 3.51804H1.84398C1.84458 3.51684 1.84648 3.51394 1.84998 3.50934ZM1.84387 14.8151C1.84417 14.8151 1.84678 14.8185 1.85068 14.8253C1.84538 14.8186 1.84347 14.8152 1.84387 14.8151Z" fill="#444E7C"/> +</svg> diff --git a/superset-frontend/images/icons/more_vert.svg b/superset-frontend/images/icons/more_vert.svg new file mode 100644 index 000000000000..2fbe6287d86e --- /dev/null +++ b/superset-frontend/images/icons/more_vert.svg @@ -0,0 +1,21 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10 12C10 13.1046 10.8954 14 12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12ZM10 19C10 20.1046 10.8954 21 12 21C13.1046 21 14 20.1046 14 19C14 17.8954 13.1046 17 12 17C10.8954 17 10 17.8954 10 19ZM10 5C10 6.10457 10.8954 7 12 7C13.1046 7 14 6.10457 14 5C14 3.89543 13.1046 3 12 3C10.8954 3 10 3.89543 10 5Z" fill="currentColor"/> +</svg> diff --git a/superset-frontend/images/icons/tags.svg b/superset-frontend/images/icons/tags.svg new file mode 100644 index 000000000000..36a061ecf33b --- /dev/null +++ b/superset-frontend/images/icons/tags.svg @@ -0,0 +1,22 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M14.1668 7.0835V5.66683H11.3335V2.8335H9.91683V5.66683H7.0835V2.8335H5.66683V5.66683H2.8335V7.0835H5.66683V9.91683H2.8335V11.3335H5.66683V14.1668H7.0835V11.3335H9.91683V14.1668H11.3335V11.3335H14.1668V9.91683H11.3335V7.0835H14.1668ZM9.91683 9.91683H7.0835V7.0835H9.91683V9.91683Z" fill="currentColor" fill-opacity="0.85"/> +</svg> diff --git a/superset-frontend/images/netezza.png b/superset-frontend/images/netezza.png new file mode 100644 index 000000000000..2658d8629bf1 Binary files /dev/null and b/superset-frontend/images/netezza.png differ diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js index 99a121561bf3..191f7a5e1df1 100644 --- a/superset-frontend/jest.config.js +++ b/superset-frontend/jest.config.js @@ -20,7 +20,7 @@ module.exports = { testRegex: '(\\/spec|\\/src)\\/.*(_spec|\\.test)\\.(j|t)sx?$', moduleNameMapper: { '\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js', - '\\.(gif|ttf|eot|png)$': '<rootDir>/spec/__mocks__/fileMock.js', + '\\.(gif|ttf|eot|png|jpg)$': '<rootDir>/spec/__mocks__/fileMock.js', '\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.tsx', '^src/(.*)$': '<rootDir>/src/$1', '^spec/(.*)$': '<rootDir>/spec/$1', diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 2649abee8eb2..1ef93bce0e86 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "superset", - "version": "0.999.0dev", + "version": "0.0.0dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "superset", - "version": "0.999.0dev", + "version": "0.0.0dev", "license": "Apache-2.0", "dependencies": { "@ant-design/icons": "^4.2.2", @@ -15,35 +15,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.53", - "@superset-ui/core": "^0.17.53", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.53", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.53", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.53", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.53", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.53", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.53", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.53", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.53", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.53", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.53", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.53", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.53", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.53", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.53", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.53", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.53", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.53", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.7", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.53", - "@superset-ui/plugin-chart-echarts": "^0.17.53", - "@superset-ui/plugin-chart-pivot-table": "^0.17.53", - "@superset-ui/plugin-chart-table": "^0.17.53", - "@superset-ui/plugin-chart-word-cloud": "^0.17.53", - "@superset-ui/preset-chart-xy": "^0.17.53", + "@superset-ui/chart-controls": "^0.17.85", + "@superset-ui/core": "^0.17.81", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.85", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.85", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.85", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.85", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.85", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.85", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.85", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.85", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.85", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.85", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.85", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", + "@superset-ui/plugin-chart-echarts": "^0.17.85", + "@superset-ui/plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/plugin-chart-table": "^0.17.85", + "@superset-ui/plugin-chart-word-cloud": "^0.17.85", + "@superset-ui/preset-chart-xy": "^0.17.85", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", @@ -58,8 +58,10 @@ "d3-color": "^1.2.0", "d3-scale": "^2.1.2", "dom-to-image": "^2.6.0", + "emotion-rgba": "0.0.9", "fontsource-fira-code": "^3.0.5", "fontsource-inter": "^3.0.5", + "fuse.js": "^6.4.6", "geolib": "^2.0.24", "global-box": "^1.2.0", "html-webpack-plugin": "^4.5.1", @@ -77,6 +79,7 @@ "mathjs": "^8.0.1", "memoize-one": "^5.1.1", "moment": "^2.26.0", + "moment-timezone": "^0.5.33", "mousetrap": "^1.6.1", "mustache": "^2.2.1", "omnibar": "^2.1.1", @@ -94,10 +97,10 @@ "react-dom": "^16.13.0", "react-gravatar": "^2.6.1", "react-hot-loader": "^4.12.20", - "react-icons": "^4.2.0", "react-js-cron": "^1.2.0", "react-json-tree": "^0.11.2", "react-jsonschema-form": "^1.2.0", + "react-lines-ellipsis": "^0.15.0", "react-loadable": "^5.5.0", "react-markdown": "^4.3.1", "react-redux": "^7.2.0", @@ -124,6 +127,7 @@ "redux-undo": "^1.0.0-beta9-9-7", "regenerator-runtime": "^0.13.5", "rison": "^0.1.1", + "scroll-into-view-if-needed": "^2.2.28", "shortid": "^2.2.6", "urijs": "^1.19.6", "use-immer": "^0.4.2", @@ -243,12 +247,11 @@ "storybook-addon-jsx": "^7.3.3", "storybook-addon-paddings": "^3.2.0", "style-loader": "^1.0.0", - "terser-webpack-plugin": "^1.1.0", "thread-loader": "^1.2.0", "transform-loader": "^0.2.3", "ts-jest": "^26.4.2", "ts-loader": "^8.0.7", - "typescript": "^4.0.3", + "typescript": "^4.1.6", "url-loader": "^1.0.1", "webpack": "^4.42.0", "webpack-bundle-analyzer": "^3.6.1", @@ -480,11 +483,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dependencies": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { @@ -497,6 +503,7 @@ "version": "7.12.10", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/generator": "^7.12.10", @@ -518,125 +525,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/core/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/core/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/core/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dependencies": { - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/core/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/core/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/core/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/core/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/core/node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, "dependencies": { "safe-buffer": "~5.1.1" } @@ -645,6 +538,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -656,6 +550,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, "dependencies": { "minimist": "^1.2.5" }, @@ -669,59 +564,32 @@ "node_modules/@babel/core/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/@babel/core/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@babel/generator": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.3.tgz", - "integrity": "sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "dependencies": { - "@babel/types": "^7.10.3", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.1" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", - "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "node_modules/@babel/helper-builder-react-jsx-experimental": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.1.tgz", - "integrity": "sha512-irQJ8kpQUV3JasXPSFQ+LCCtJSc5ceZrPFVj6TElR6XCHssi3jV8ch3odIrNtjJFRZZVbrOEfJMI79TPU/h1pQ==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-module-imports": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { @@ -783,202 +651,20 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", + "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { @@ -1036,63 +722,54 @@ "regjsparser": "bin/parser" } }, - "node_modules/@babel/helper-define-map": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", - "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", - "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, "node_modules/@babel/helper-function-name": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz", - "integrity": "sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.10.3", - "@babel/template": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-get-function-arity": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz", - "integrity": "sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "dependencies": { - "@babel/types": "^7.10.3" + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", "dev": true, "dependencies": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", - "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "dev": true, "dependencies": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { @@ -1107,6 +784,7 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.12.1", "@babel/helper-replace-supers": "^7.12.1", @@ -1119,189 +797,26 @@ "lodash": "^4.17.19" } }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dependencies": { - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dependencies": { - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, "dependencies": { - "ms": "2.1.2" + "@babel/types": "^7.14.5" }, "engines": { - "node": ">=6.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/@babel/helper-module-transforms/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.1" + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" - }, "node_modules/@babel/helper-regex": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", @@ -1322,31 +837,26 @@ "@babel/types": "^7.12.1" } }, - "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, "node_modules/@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, "dependencies": { "@babel/types": "^7.12.1" } @@ -1361,18 +871,24 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "dependencies": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-validator-option": { "version": "7.12.1", @@ -1392,111 +908,34 @@ "@babel/types": "^7.10.4" } }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "node_modules/@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "dependencies": { - "@babel/types": "^7.12.11" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function/node_modules/ansi-styles": { + "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1504,11 +943,10 @@ "node": ">=4" } }, - "node_modules/@babel/helper-wrap-function/node_modules/chalk": { + "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1518,29 +956,10 @@ "node": ">=4" } }, - "node_modules/@babel/helper-wrap-function/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/helper-wrap-function/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/helper-wrap-function/node_modules/supports-color": { + "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1548,219 +967,22 @@ "node": ">=4" } }, - "node_modules/@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", - "dependencies": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "node_modules/@babel/node": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.12.10.tgz", + "integrity": "sha512-lJT1sXp1bEfAZ7B2ChEOOiUxaGbIWkcAixqZDpbHnJWUqIjoofOGo5ON1bJ9HOmtMdF7rqKiOoM7zZSI87El3g==", + "dev": true, "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dependencies": { - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helpers/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helpers/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helpers/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helpers/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/helpers/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/@babel/helpers/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dependencies": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/node": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.12.10.tgz", - "integrity": "sha512-lJT1sXp1bEfAZ7B2ChEOOiUxaGbIWkcAixqZDpbHnJWUqIjoofOGo5ON1bJ9HOmtMdF7rqKiOoM7zZSI87El3g==", - "dev": true, - "dependencies": { - "@babel/register": "^7.12.10", - "commander": "^4.0.1", - "core-js": "^3.2.1", - "lodash": "^4.17.19", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.13.4", - "v8flags": "^3.1.1" - }, - "bin": { - "babel-node": "bin/babel-node.js" + "@babel/register": "^7.12.10", + "commander": "^4.0.1", + "core-js": "^3.2.1", + "lodash": "^4.17.19", + "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.4", + "v8flags": "^3.1.1" + }, + "bin": { + "babel-node": "bin/babel-node.js" } }, "node_modules/@babel/node/node_modules/commander": { @@ -1773,9 +995,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.3.tgz", - "integrity": "sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1816,205 +1038,6 @@ "@babel/plugin-syntax-decorators": "^7.12.13" } }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.8.tgz", - "integrity": "sha512-qioaRrKHQbn4hkRKDHbnuQ6kAxmmOF+kzKGnIfxPK4j2rckSJCpKzr/SSTlohSCiE3uAQpNDJ9FIh4baeE8W+w==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz", - "integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-replace-supers": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz", - "integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/plugin-proposal-decorators/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/plugin-proposal-dynamic-import": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", @@ -2084,37 +1107,6 @@ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", - "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-numeric-separator": "^7.10.1" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", - "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "node_modules/@babel/plugin-proposal-optional-catch-binding": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", @@ -2137,27 +1129,16 @@ } }, "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", - "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@babel/plugin-proposal-private-methods/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/plugin-proposal-unicode-property-regex": { @@ -2269,9 +1250,6 @@ "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { @@ -2384,22 +1362,6 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", - "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-define-map": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "globals": "^11.1.0" - } - }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", @@ -2437,16 +1399,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "node_modules/@babel/plugin-transform-flow-strip-types": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.13.0.tgz", @@ -2476,107 +1428,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-function-name/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/plugin-transform-literals": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", @@ -2586,15 +1437,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", @@ -2641,15 +1483,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, "node_modules/@babel/plugin-transform-new-target": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", @@ -2669,349 +1502,118 @@ "@babel/helper-replace-supers": "^7.12.1" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.10.4" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.14.5.tgz", + "integrity": "sha512-NBqLEx1GxllIOXJInJAQbrnwwYJsV3WaMHIcOwD8rhYS0AabTWn7kHdHgPgu5RmHLU0q4DMxhAMu8ue/KampgQ==", "dev": true, "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", + "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.17.tgz", + "integrity": "sha512-mwaVNcXV+l6qJOuRhpdTEj8sT/Z0owAVWf9QujTZ0d2ye9X/K+MTOTSizcgKOj18PGnTc/7g1I4+cIUjsKhBcw==", "dependencies": { - "@babel/types": "^7.12.10" + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/types": "^7.12.17" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "dev": true, "dependencies": { - "@babel/types": "^7.12.7" + "regenerator-transform": "^0.14.2" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "dev": true, "dependencies": { - "@babel/types": "^7.12.10" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", + "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "semver": "^5.5.1" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", + "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", "dev": true, "dependencies": { - "@babel/types": "^7.12.11" + "@babel/helper-plugin-utils": "^7.12.13" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", + "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/template": { + "node_modules/@babel/plugin-transform-sticky-regex": { "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", + "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-object-super/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-object-super/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-object-super/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/plugin-transform-object-super/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/plugin-transform-object-super/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.1.tgz", - "integrity": "sha512-V4os6bkWt/jbrzfyVcZn2ZpuHZkvj3vyBU0U/dtS8SZuMS7Rfx5oknTrtfyXJ2/QZk8gX7Yls5Z921ItNpE30Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", - "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.17.tgz", - "integrity": "sha512-mwaVNcXV+l6qJOuRhpdTEj8sT/Z0owAVWf9QujTZ0d2ye9X/K+MTOTSizcgKOj18PGnTc/7g1I4+cIUjsKhBcw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/types": "^7.12.17" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.1.tgz", - "integrity": "sha512-XwDy/FFoCfw9wGFtdn5Z+dHh6HXKHkC6DwKNWpN74VWinUagZfDcEJc3Y8Dn5B3WMVnAllX8Kviaw7MtC5Epwg==", - "dev": true, - "dependencies": { - "@babel/helper-builder-react-jsx-experimental": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.1.tgz", - "integrity": "sha512-mfhoiai083AkeewsBHUpaS/FM1dmUENHBMpS/tugSJ7VXqXO5dCN1Gkint2YvM1Cdv1uhmAKt1ZOuAjceKmlLA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", - "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", - "semver": "^5.5.1" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", - "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", - "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", - "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.13.tgz", - "integrity": "sha512-z1VWskPJxK9tfxoYvePWvzSJC+4pxXr8ArmRm5ofqgi+mwpKg6lvtomkIngBYMJVnKhsFYVysCQLDn//v2RHcg==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.13.tgz", + "integrity": "sha512-z1VWskPJxK9tfxoYvePWvzSJC+4pxXr8ArmRm5ofqgi+mwpKg6lvtomkIngBYMJVnKhsFYVysCQLDn//v2RHcg==", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.12.13", @@ -3019,214 +1621,6 @@ "@babel/plugin-syntax-typescript": "^7.12.13" } }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", - "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", - "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", @@ -3237,15 +1631,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-transform-unicode-regex/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, "node_modules/@babel/plugin-transform-unicode-regex/node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", @@ -3388,47 +1773,6 @@ "semver": "^5.5.0" } }, - "node_modules/@babel/preset-env/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/generator/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, "node_modules/@babel/preset-env/node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", @@ -3469,94 +1813,12 @@ "@babel/types": "^7.12.1" } }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.11" - } - }, "node_modules/@babel/preset-env/node_modules/@babel/helper-validator-option": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", "dev": true }, - "node_modules/@babel/preset-env/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-numeric-separator": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", @@ -3578,16 +1840,6 @@ "@babel/plugin-transform-parameters": "^7.12.1" } }, - "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-unicode-property-regex": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", @@ -3755,46 +2007,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/preset-env/node_modules/@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/preset-env/node_modules/@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/preset-env/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/preset-env/node_modules/browserslist": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", @@ -3820,20 +2032,6 @@ "integrity": "sha512-R3aqmjrICdGCTAnSXtNyvWYMK3YtV5jwudbq0T7nN9k4kmE4CBuwPqyJ+KBzepSTh0huivV2gLbSMEzTTmfeYw==", "dev": true }, - "node_modules/@babel/preset-env/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/preset-env/node_modules/core-js-compat": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", @@ -3853,18 +2051,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/preset-env/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, "node_modules/@babel/preset-env/node_modules/electron-to-chromium": { "version": "1.3.635", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.635.tgz", @@ -3889,12 +2075,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/@babel/preset-env/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@babel/preset-env/node_modules/node-releases": { "version": "1.1.69", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", @@ -3936,18 +2116,6 @@ "regjsparser": "bin/parser" } }, - "node_modules/@babel/preset-env/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/preset-flow": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.12.13.tgz", @@ -3984,15 +2152,6 @@ "@babel/plugin-transform-react-pure-annotations": "^7.12.1" } }, - "node_modules/@babel/preset-react/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, "node_modules/@babel/preset-react/node_modules/@babel/plugin-transform-react-jsx": { "version": "7.12.12", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.12.tgz", @@ -4087,171 +2246,64 @@ } }, "node_modules/@babel/template": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.3.tgz", - "integrity": "sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.10.3", - "@babel/parser": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz", - "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.3" + "node_modules/@babel/traverse": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/template/node_modules/@babel/highlight": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", - "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.10.3", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "ms": "^2.1.1" } }, - "node_modules/@babel/template/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dependencies": { - "color-convert": "^1.9.0" + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/template/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/template/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/traverse": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.3.tgz", - "integrity": "sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.3", - "@babel/generator": "^7.10.3", - "@babel/helper-function-name": "^7.10.3", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.3", - "@babel/types": "^7.10.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz", - "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.3" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/highlight": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", - "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.3", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/traverse/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/traverse/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/traverse/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" + "node": ">=6.9.0" } }, "node_modules/@base2/pretty-print-object": { @@ -4969,81 +3021,186 @@ "integrity": "sha512-M2AelyJDVR/oLnToJLtuDJRBBWUGUvvGigj1411hXhAdyFWqMaqHp7TixW3FpiLuVaikIcR1QL+zqoJoZlOgpg==" }, "node_modules/@deck.gl/aggregation-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-7.1.11.tgz", - "integrity": "sha512-CHsr+UJhf06Mqb/q60iP7ftHQv3ftHUhJbVO4550PRo+QMFFhHfhxo53gQDDgrQ3stxpAcLT3lXRSNghMoU34g==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.5.2.tgz", + "integrity": "sha512-oiqXPmyn2v0lX9tWCvgmWs29stHSLS3tje71Ff2FVXDNmvP5FoZItFa8y7O7KSTkej2/rSwZeSte/a9pri6Njg==", "dependencies": { + "@luma.gl/shadertools": "^8.5.4", + "@math.gl/web-mercator": "^3.5.3", "d3-hexbin": "^0.2.1" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "@deck.gl/layers": "^8.0.0" } }, - "node_modules/@deck.gl/core": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-7.1.11.tgz", - "integrity": "sha512-jUi1CcsnF5KPL2sv7Z0H3x+8amee5csqliZXGbXEBYox1l8naC4PhHg5jTgLaB0ZOHfVDsldPwGdPC+Mi4jP/Q==", + "node_modules/@deck.gl/carto": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/carto/-/carto-8.5.2.tgz", + "integrity": "sha512-Kw/3NUM+2NcHjxH6b7IOUYXEwmJ4SNQujFzAVFW5amG4Lut8074NGSF5XHi+4M/zgk7vXDFsGRxLqspsA/dg8w==", + "dependencies": { + "@loaders.gl/loader-utils": "^3.0.6", + "@loaders.gl/mvt": "^3.0.6", + "@loaders.gl/tiles": "^3.0.6", + "@math.gl/web-mercator": "^3.5.3", + "cartocolor": "^4.0.2", + "d3-scale": "^3.2.3" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "@deck.gl/geo-layers": "^8.0.0", + "@deck.gl/layers": "^8.0.0", + "@loaders.gl/core": "^3.0.0" + } + }, + "node_modules/@deck.gl/carto/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/@deck.gl/carto/node_modules/d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "dependencies": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "node_modules/@deck.gl/carto/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", "dependencies": { - "@luma.gl/core": "^7.1.0", + "d3-array": "2" + } + }, + "node_modules/@deck.gl/core": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.5.2.tgz", + "integrity": "sha512-SAFv7fKx6k1Rj8R4qTMQO2wEhEfixROzbcoSS6RivxrfES00KYYj6jJ7iNEnq3dFn6qc37LPpxqtYYHO4BcvYA==", + "dependencies": { + "@loaders.gl/core": "^3.0.6", + "@loaders.gl/images": "^3.0.6", + "@luma.gl/core": "^8.5.4", + "@math.gl/web-mercator": "^3.5.3", "gl-matrix": "^3.0.0", - "math.gl": "^2.3.0", - "mjolnir.js": "^2.1.2", - "probe.gl": "^3.0.1", - "seer": "^0.2.4", - "viewport-mercator-project": "^6.1.0" + "math.gl": "^3.5.3", + "mjolnir.js": "^2.5.0", + "probe.gl": "^3.4.0" } }, - "node_modules/@deck.gl/geo-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-7.1.11.tgz", - "integrity": "sha512-gIJ1K98IFSZ12hB+zHyQC+9pMncb9BKVhJTA7pjUpxwcmEkkroqet9zkYQQMeSInK0a67BR9GXjebb/N0U04qA==", + "node_modules/@deck.gl/extensions": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-8.5.2.tgz", + "integrity": "sha512-VhbQsMNPM7RCR/ERwb1u1x0rEWAxgXfcCWttW+gYvbiagW/LrAJ22jhOghlRW/wilEmupHYbXQlWkW2V/mYfsg==", "dependencies": { - "h3-js": "^3.4.3", + "@luma.gl/shadertools": "^8.5.4" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "gl-matrix": "^3.0.0" + } + }, + "node_modules/@deck.gl/geo-layers": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-8.5.2.tgz", + "integrity": "sha512-t6+TgAdbKWDw8g9UX1y6D+5twcdJuKaXw4qSib/0yVurWi/Mil5Plihybt1l9uBZuwkr+UcpxPR73zzo+qd9MA==", + "dependencies": { + "@loaders.gl/3d-tiles": "^3.0.6", + "@loaders.gl/gis": "^3.0.6", + "@loaders.gl/loader-utils": "^3.0.6", + "@loaders.gl/mvt": "^3.0.6", + "@loaders.gl/terrain": "^3.0.6", + "@loaders.gl/tiles": "^3.0.6", + "@luma.gl/experimental": "^8.5.4", + "@math.gl/culling": "^3.5.3", + "@math.gl/web-mercator": "^3.5.3", + "h3-js": "^3.6.0", "long": "^3.2.0", - "s2-geometry": "^1.2.10" + "math.gl": "^3.5.3" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "@deck.gl/extensions": "^8.0.0", + "@deck.gl/layers": "^8.0.0", + "@deck.gl/mesh-layers": "^8.0.0", + "@loaders.gl/core": "^3.0.0" } }, "node_modules/@deck.gl/google-maps": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-7.1.11.tgz", - "integrity": "sha512-gYp3NFIsyT5p65HgKjXFWTDzFf7K8+6ce9d9MIqaNgVWFZdsjUy9JL5TttDMQXshaQ1aZpxtLL6ZO3BiI1w8fw==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-8.5.2.tgz", + "integrity": "sha512-Dk3ozenBWgt9nFSYOT4N82urNW/JhiMszfFq6zLt3jUp0N7EJ9d2XO81hclM59BhjIdGWb6drTe96NvtbabVLQ==", + "peerDependencies": { + "@deck.gl/core": "^8.0.0" + } }, "node_modules/@deck.gl/json": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-7.1.11.tgz", - "integrity": "sha512-obYAXq5VZ0qCTVS8hopS64aXGicUeBNg0/03AAfo+Q5z62cNqagAktGKVZMUsJ13bV8CPohJ2zRWMXO+mAJtew==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-8.5.2.tgz", + "integrity": "sha512-lVS16bvPfLUSidgBURZvGbWEjgK8GjLWlp1iGuLvua2W6TnWIyiKa6a3XoebgeXd8kqwSbQxhNnuSVPX+Di6Rg==", "dependencies": { - "d3-dsv": "^1.0.8" + "d3-dsv": "^1.0.8", + "expression-eval": "^2.0.0" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0" } }, "node_modules/@deck.gl/layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-7.1.11.tgz", - "integrity": "sha512-hOylm7Pf3CSvqpDoiCJLnqLAU3PAePISskJ5jjhpXtgHBrm1/Gk4boP4/7t7kFZdbSvVIXin13pPRbT0SWCRPw==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.5.2.tgz", + "integrity": "sha512-HmpE3qf9CI7sU/xa2DMCNg31pzpzK5XuUHyC70dsLq8AV7Sm3vZQz17KMU/CWSZpVr7yQ8uxTeSQARiv/zeOFQ==", "dependencies": { - "@loaders.gl/core": "^1.0.3", - "@loaders.gl/images": "^1.0.3", + "@loaders.gl/images": "^3.0.6", "@mapbox/tiny-sdf": "^1.1.0", + "@math.gl/polygon": "^3.5.3", "earcut": "^2.0.6" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "@loaders.gl/core": "^3.0.0" } }, "node_modules/@deck.gl/mapbox": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-7.1.11.tgz", - "integrity": "sha512-V4cc9vwXzAOBtWV8x+WtvPVXElGChogkvQketeR2uhz6wIHuH+3sBBRg/Ma476w/II+DKjeHg2AzAZeX3SK7yQ==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-8.5.2.tgz", + "integrity": "sha512-nMpzfdPFBVthT+EMgIcKo4YO6bZCqADQtqnxIFtfofZIiKS6R5OSuJ3sXPSNZ9ReCJGzdmndEz7/Qtm9Sia/bA==", + "peerDependencies": { + "@deck.gl/core": "^8.0.0" + } }, "node_modules/@deck.gl/mesh-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-7.1.11.tgz", - "integrity": "sha512-rI8ffUNh7ac2GpMcGLEiKyRarOPeLfVRlMRKjl9LXU61Wgx6DaHqsMmeqxzjoXEzgiRlY/XgCjepVg0dY6btlQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-8.5.2.tgz", + "integrity": "sha512-dUfQyGjm5CYQg9AQdRsGtEEXGSGHxifPlws0zWWoj1r757wjqM0aZ663TUJEsJQDTLNOvbBLGTiuFeCBUoKO4Q==", "dependencies": { - "@loaders.gl/core": "^1.0.3", - "@loaders.gl/images": "^1.0.3" + "@loaders.gl/gltf": "^3.0.6", + "@luma.gl/experimental": "^8.5.4", + "@luma.gl/shadertools": "^8.5.4" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0" } }, "node_modules/@deck.gl/react": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-7.1.11.tgz", - "integrity": "sha512-WUzxhvM3jZIZkBAQgdQR+tFBAVDm5opLCKMWI9YkJUsdJzdv9uwiWCsk3Se1pCTFIa5Asb8U6YAi1CHl+OOFyA==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-8.5.2.tgz", + "integrity": "sha512-h7AJ9nPY1PTjrAVP7T1fvWDChWZrVOsEfYIoEP4W6ILSjvDqEQfVL0+9RhjUwQV2nKrg0QmpqCmbfOrgKQQbYw==", "dependencies": { "prop-types": "^15.6.0" + }, + "peerDependencies": { + "@deck.gl/core": "^8.0.0", + "react": ">=16.3", + "react-dom": ">=16.3" } }, "node_modules/@emotion/babel-plugin": { @@ -5063,9 +3220,6 @@ "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "^4.0.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@emotion/babel-plugin-jsx-pragmatic": { @@ -5109,9 +3263,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@emotion/babel-preset-css-prop": { @@ -5123,9 +3274,6 @@ "@babel/runtime": "^7.7.2", "@emotion/babel-plugin": "^11.2.0", "@emotion/babel-plugin-jsx-pragmatic": "^0.1.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@emotion/cache": { @@ -5161,9 +3309,6 @@ "@emotion/serialize": "^0.11.15", "@emotion/sheet": "0.9.4", "@emotion/utils": "0.11.3" - }, - "peerDependencies": { - "react": ">=16.3.0" } }, "node_modules/@emotion/core/node_modules/@emotion/cache": { @@ -5239,18 +3384,6 @@ "chalk": "^4.1.0", "specificity": "^0.4.1", "stylis": "^4.0.3" - }, - "peerDependencies": { - "@types/jest": "^26.0.14", - "enzyme-to-json": "^3.2.1" - }, - "peerDependenciesMeta": { - "@types/jest": { - "optional": true - }, - "enzyme-to-json": { - "optional": true - } } }, "node_modules/@emotion/jest/node_modules/ansi-styles": { @@ -5263,9 +3396,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@emotion/jest/node_modules/chalk": { @@ -5279,9 +3409,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@emotion/jest/node_modules/color-convert": { @@ -7272,9 +5399,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@jest/types/node_modules/chalk": { @@ -7288,9 +5412,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@jest/types/node_modules/color-convert": { @@ -7332,93 +5453,227 @@ "node": ">=8" } }, + "node_modules/@loaders.gl/3d-tiles": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-3.0.8.tgz", + "integrity": "sha512-jZeOyDPGD2wEkTLW4Do9A4UUQ+OGjhhNXztB0AsttZ69OpkmsxJXb76xxwevf+eThrsTgSTjZ06eC5DHX0kyXA==", + "dependencies": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/draco": "3.0.8", + "@loaders.gl/gltf": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/math": "3.0.8", + "@loaders.gl/tiles": "3.0.8", + "@math.gl/core": "^3.5.1", + "@math.gl/geospatial": "^3.5.1" + } + }, "node_modules/@loaders.gl/core": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-1.3.7.tgz", - "integrity": "sha512-dFZkJQc+i2PoqlBMz/aO8Gnn0y6ICafQp8u6cTpCm96h/HHulE8qDBodQlHGHn9EMJDSgVl/zjni+QhqIK31dg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.0.8.tgz", + "integrity": "sha512-FIfbhMkoRX2JonEHXHgClC7jwOSsEwvvmjlaTMRAY+gFKvJPGmegkp4VgUZquLFf6GedJt/1TuMMvAX6gdq1pg==", "dependencies": { - "@babel/runtime": "^7.3.1" + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/worker-utils": "3.0.8", + "probe.gl": "^3.4.0" + } + }, + "node_modules/@loaders.gl/draco": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-3.0.8.tgz", + "integrity": "sha512-ZCXzXNHWQ7H0qk/kC+rWzjMWjLzZGzQcDbdpIuy8xJdp4rTpmMkLUseFPby8vhkmIaqxWPwPB6mx/vM7L6JENg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@loaders.gl/worker-utils": "3.0.8", + "draco3d": "1.4.1" + } + }, + "node_modules/@loaders.gl/gis": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-3.0.8.tgz", + "integrity": "sha512-7NL+lIb7NezlMupYskVil6M3RZunXJl+TyaVAW82GLbzPSOq+m/G7h3+z0GBa8iv/U/I+cB5BhSN+GZmvFwqEA==", + "dependencies": { + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "node_modules/@loaders.gl/gltf": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-3.0.8.tgz", + "integrity": "sha512-4PXWTlqyvlbZE2Vp4iQ+Y87ZO1WuRvSlbImDhygd0hoINfmJ9ObxrFS3yJcpJTu007nWxXorNVEOKyuoo+4Iyw==", + "dependencies": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/draco": "3.0.8", + "@loaders.gl/images": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8" } }, "node_modules/@loaders.gl/images": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-1.3.7.tgz", - "integrity": "sha512-TKqW94vjvWc4RIChhr0Yx6HaVTe8K6h6GFeXcahsKeCxq9/k2qpcigRkXfmb6/37dkp2Qy5COHp73ECgN/q+NA==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.0.8.tgz", + "integrity": "sha512-rO2cIYJYlMs/uO9YSoF4/BEA4p/9xQ3gHZ1sIJkPYVnDqzpbu8nvUjWTQqIdL/MkQBTW8tz3twCdM+B6G9Fa2w==", + "dependencies": { + "@loaders.gl/loader-utils": "3.0.8" + } }, - "node_modules/@luma.gl/constants": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-7.3.2.tgz", - "integrity": "sha512-hr6JOOwsGPjjoHnil4sQ6AWsc8P6XXYtRL10TwNYfFTcNxrhSrjQvutYoCzXHH5U0vfHBfPMMUyLASK9FqiHOA==" + "node_modules/@loaders.gl/loader-utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.0.8.tgz", + "integrity": "sha512-PW1WyyQ+LXkqoGHBZHsmfNQkKiLAYf1gok+kHnHvY9fCzhJeA1iTNEUKPXGXKgS00m/k5cBTkOWAaOG9KRvBCQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/worker-utils": "3.0.8", + "@probe.gl/stats": "^3.4.0" + } }, - "node_modules/@luma.gl/core": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-7.3.2.tgz", - "integrity": "sha512-XyQPSUJRkZcc//gVX0AgjLLNTkCOO68NRnm7RkIhikRBEUdovb4IOcpmWMCB1/Gyj4hzg/Z1FOAVT4pG1E+agw==", + "node_modules/@loaders.gl/math": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-3.0.8.tgz", + "integrity": "sha512-jfFpxxr4Bq5JfOPqLVJc4JJGoGGvVTOCWiJhnTtSAKhaNSwldmNWaZ0w8E2nlgPKPMAHiTRKOQnd9sSY5m66Cw==", "dependencies": { - "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2", - "@luma.gl/shadertools": "7.3.2", - "@luma.gl/webgl": "7.3.2", - "@luma.gl/webgl-state-tracker": "7.3.2", - "@luma.gl/webgl2-polyfill": "7.3.2", - "math.gl": "^3.0.0", - "probe.gl": "^3.1.1", - "seer": "^0.2.4" + "@loaders.gl/images": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@math.gl/core": "^3.5.1" } }, - "node_modules/@luma.gl/core/node_modules/math.gl": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.4.1.tgz", - "integrity": "sha512-D33ZXryVFcHu1YJ+fgcNp2MkyK+mEfHesHMdQUZBz2hFqIsAwXovM1sJ+0rTcs8IyTFmuRJ2ayHf1igEJEOM2g==", + "node_modules/@loaders.gl/mvt": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-3.0.8.tgz", + "integrity": "sha512-Jk1QTHgpxMsUT01w5IJJ2en9qq0yOZcL2wGXVc7CFp2h6inB22rC3drUwq1mUNGe6iy3EWIo7EeJVd9B+5JyTQ==", "dependencies": { - "@math.gl/core": "3.4.1" + "@loaders.gl/gis": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@math.gl/polygon": "^3.5.1", + "pbf": "^3.2.1" } }, - "node_modules/@luma.gl/shadertools": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-7.3.2.tgz", - "integrity": "sha512-GiOZTvdEr164zYFy1DNRc7mzduSWLNJ34s+YbkJ/0i07E6tK7gHgM29QNCZ/gROvUDDJ5CHxngZqGkb+XquOMQ==", + "node_modules/@loaders.gl/schema": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.0.8.tgz", + "integrity": "sha512-yne5WE7fZZWFl2zF8fzDlYhPVJua6h6mTCSmlQ5pryaMXTZS9mfzXXIFWRL3kswqnQTu/QNFdyFj1mP0haF24w==", + "dependencies": { + "@types/geojson": "^7946.0.7", + "apache-arrow": "^4.0.0", + "d3-dsv": "^1.2.0" + } + }, + "node_modules/@loaders.gl/terrain": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-3.0.8.tgz", + "integrity": "sha512-MtOAYEB/xJB4CN4B0YNPkO4v1ZY332joxiOHQI1x37x4sWVAqOrKLr9jB42sZCB8aINi2WMWGiErtf9wh9L5Pg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@mapbox/martini": "^0.2.0" + } + }, + "node_modules/@loaders.gl/tiles": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-3.0.8.tgz", + "integrity": "sha512-Rc+yHFdQg2sYmcYkwvszukFWdm9EW354F9HUR7y/oauos6tsdo4YTj31zgytaYR63/EqWQ7kwI29/eePEcutzg==", + "dependencies": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/math": "3.0.8", + "@math.gl/core": "^3.5.1", + "@math.gl/culling": "^3.5.1", + "@math.gl/geospatial": "^3.5.1", + "@math.gl/web-mercator": "^3.5.1", + "@probe.gl/stats": "^3.4.0" + } + }, + "node_modules/@loaders.gl/worker-utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.0.8.tgz", + "integrity": "sha512-Pg72HuXPcL725TrOlOr83xloVUHj6OMWmno1dI8ccuqfOBsgoRjxNZrcSvwBzfK8tFCzuN2X30I+mHl3BkuYLw==", + "dependencies": { + "@babel/runtime": "^7.3.1" + } + }, + "node_modules/@luma.gl/constants": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.4.tgz", + "integrity": "sha512-lrA4ja92om/gDHYOvM9itL5S7FVzjKulyknDz6S+Y7gmgHgXk2ln1Xar5zUCsLnhAYx4glHITXGH5Y5rdWgT1Q==" + }, + "node_modules/@luma.gl/core": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.4.tgz", + "integrity": "sha512-+saDz1D3mcPd53vgbG60ryg1w5CF9Z2wdakKHzR810VoJLw97t4aNdg/eNgyWOvbOHxaKJBPm8K0sGjej67+jw==", "dependencies": { "@babel/runtime": "^7.0.0", - "math.gl": "^3.0.0" + "@luma.gl/constants": "8.5.4", + "@luma.gl/engine": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "@luma.gl/shadertools": "8.5.4", + "@luma.gl/webgl": "8.5.4" } }, - "node_modules/@luma.gl/shadertools/node_modules/math.gl": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.4.1.tgz", - "integrity": "sha512-D33ZXryVFcHu1YJ+fgcNp2MkyK+mEfHesHMdQUZBz2hFqIsAwXovM1sJ+0rTcs8IyTFmuRJ2ayHf1igEJEOM2g==", + "node_modules/@luma.gl/engine": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.4.tgz", + "integrity": "sha512-Sfv972IzvR9s9kKWugs67XQUh9jC0e/PpBrzvyGVnPU4XvFq42RZVF73pzEklVU6AlpR8Zg5CPtxGdhyOHtT7w==", "dependencies": { - "@math.gl/core": "3.4.1" + "@babel/runtime": "^7.0.0", + "@luma.gl/constants": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "@luma.gl/shadertools": "8.5.4", + "@luma.gl/webgl": "8.5.4", + "@math.gl/core": "^3.5.0", + "probe.gl": "^3.4.0" } }, - "node_modules/@luma.gl/webgl": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-7.3.2.tgz", - "integrity": "sha512-eWoPPRJOF5xSpqgggdwspsm8exclwxz20c8vqu8D1b3LJTY7cEpq57CMLvITHcJMMJ834TX/r598efTcF76lpw==", + "node_modules/@luma.gl/experimental": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/experimental/-/experimental-8.5.4.tgz", + "integrity": "sha512-09waqRhgIrw+Sq0/in4tw4jPag5YsFfV1nEHJaLAg5RFv92S53IEubSJgkuG02HoOBkPxQ7KYvs9VNmriisnYg==", + "dependencies": { + "@luma.gl/constants": "8.5.4", + "@math.gl/core": "^3.5.0", + "earcut": "^2.0.6" + }, + "peerDependencies": { + "@loaders.gl/gltf": "^3.0.0", + "@loaders.gl/images": "^3.0.0", + "@luma.gl/engine": "^8.4.0", + "@luma.gl/gltools": "^8.4.0", + "@luma.gl/shadertools": "^8.4.0", + "@luma.gl/webgl": "^8.4.0" + } + }, + "node_modules/@luma.gl/gltools": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.4.tgz", + "integrity": "sha512-JotiPuymQz2Xc41AYlS2moJC/EHxU+OX/OMKi0+/MeOlEFLsdochgTA0I64j8yofLTXdeiGCneGtD1Ao8fk+bw==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2", - "@luma.gl/webgl-state-tracker": "7.3.2", - "@luma.gl/webgl2-polyfill": "7.3.2", - "probe.gl": "^3.1.1" + "@luma.gl/constants": "8.5.4", + "probe.gl": "^3.4.0" } }, - "node_modules/@luma.gl/webgl-state-tracker": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl-state-tracker/-/webgl-state-tracker-7.3.2.tgz", - "integrity": "sha512-0LuK3veReSm2UPOiDwC2CRDeE2xk4irqXdhyFO0WSAU1w+YhzbD1hGbjizGczvgfkbz8dFl9h98LbbH75efcKw==", + "node_modules/@luma.gl/shadertools": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.4.tgz", + "integrity": "sha512-rwLBLrACi75aWnuJm8rVKCQnJR2sMTCxHuexfjHJ7Uecl0vVcVJZT7c9EnCFaz5LUTNbdupvuhq0SKNckKiKmw==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2" + "@math.gl/core": "^3.5.0" } }, - "node_modules/@luma.gl/webgl2-polyfill": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl2-polyfill/-/webgl2-polyfill-7.3.2.tgz", - "integrity": "sha512-PMt5xqQ+u7tIqfUaL3s4nuWl604WFNcl1F1ohSUFeEzIIuxFiF6gsdEEvC5VqGoMFxI8T4FOTSeHYIr6uP4+4w==", + "node_modules/@luma.gl/webgl": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.4.tgz", + "integrity": "sha512-dWy4dhTbtvDO9zQBdx1Yb+DxNx/1JWV9rhhJxJUtTKbGZSX0RjkASTT6GBWMl5jrH1JYJefS1wswHmmPVXjK0Q==", "dependencies": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2" + "@luma.gl/constants": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "probe.gl": "^3.4.0" } }, "node_modules/@mapbox/geojson-area": { @@ -7461,15 +5716,20 @@ "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==" }, + "node_modules/@mapbox/martini": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/martini/-/martini-0.2.0.tgz", + "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==" + }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" }, "node_modules/@mapbox/tiny-sdf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz", - "integrity": "sha512-Ihn1nZcGIswJ5XGbgFAvVumOgWpvIjBX9jiRlIl46uQG9vJOF51ViBYHF95rEZupuyQbEmhLaDPLQlU7fUTsBg==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", + "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==" }, "node_modules/@mapbox/unitbezier": { "version": "0.0.0", @@ -7493,18 +5753,46 @@ } }, "node_modules/@math.gl/core": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-3.4.1.tgz", - "integrity": "sha512-miAZL/WPU0B5hKrcg1K2nPU2GnOK6X84bwLoD0eTt2n7qT46ffh51Xu21V9kQp/cisE3l1ypukqSV/VHeaNxhQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-3.5.3.tgz", + "integrity": "sha512-TaSnvG0qFh1VxeNW5L58jSx0nJUMWMpUl6zo6Z3ScQzFySG5cicGOBzk/D40RkIZWPazCKCZ+ZThg5npSK9y3g==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "gl-matrix": "^3.0.0" + } + }, + "node_modules/@math.gl/culling": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/culling/-/culling-3.5.3.tgz", + "integrity": "sha512-ABpAcrvoIOLSm1EUkwgDem4RfO28HWPBs/+taZ/ZSpJG6KiVPklpKU1NCK+05HuJStkpFZ+XlWtehWU6FAMCyA==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@math.gl/core": "3.5.3", + "gl-matrix": "^3.0.0" + } + }, + "node_modules/@math.gl/geospatial": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/geospatial/-/geospatial-3.5.3.tgz", + "integrity": "sha512-cnc8VMQrt30JmlG200VDJmmvSjaGW57gY9KEZ+raapxyyFyfDNuAuIrIxe+zbK66FbvFWTbJlDaNmKqVG+ohyw==", "dependencies": { "@babel/runtime": "^7.12.0", + "@math.gl/core": "3.5.3", "gl-matrix": "^3.0.0" } }, + "node_modules/@math.gl/polygon": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/polygon/-/polygon-3.5.3.tgz", + "integrity": "sha512-VktscmyQg/Rd56nJk0Nj/UyvnPDbsnZNMWCdl3G5AYenYzLWy6h4FEWhLx8pD+Xw7VuFot8LR4WAK2TPzXzrWw==", + "dependencies": { + "@math.gl/core": "3.5.3" + } + }, "node_modules/@math.gl/web-mercator": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.4.1.tgz", - "integrity": "sha512-5LAVmo5U25GY5YIxbI3D0J7r97B9AM5pAcWxnF9YhJx44DSVAYfMdiSISOfS+ivKuBFX44mFZvV9j75QY5aDkQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.5.3.tgz", + "integrity": "sha512-WZE9ALeTS4n3HDgkqTxcNLBU7DL0mjmPXSrcqSZIUeDY00+LCtNvMQWUAwqolpB7nD71vD6HLW8delzVuy4teA==", "dependencies": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.0.0" @@ -7586,15 +5874,6 @@ "unist-util-visit": "2.0.3" } }, - "node_modules/@mdx-js/mdx/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, "node_modules/@mdx-js/mdx/node_modules/@babel/core": { "version": "7.12.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", @@ -7622,69 +5901,6 @@ "node": ">=6.9.0" } }, - "node_modules/@mdx-js/mdx/node_modules/@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@mdx-js/mdx/node_modules/@babel/plugin-syntax-jsx": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", @@ -7694,60 +5910,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@mdx-js/mdx/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@mdx-js/mdx/node_modules/@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@mdx-js/mdx/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@mdx-js/mdx/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@mdx-js/mdx/node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -7846,18 +6008,6 @@ "xtend": "^4.0.1" } }, - "node_modules/@mdx-js/mdx/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@mdx-js/mdx/node_modules/unified": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", @@ -8186,9 +6336,9 @@ "dev": true }, "node_modules/@probe.gl/stats": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-3.3.0.tgz", - "integrity": "sha512-CV4c3EgallqZTO88u34/u9L5asL0nCVP1BEkb4qcXlh8Qz2Vmygbyjz1ViQsct6rSi2lJ52lo6W0PnlpZJJvcA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-3.4.0.tgz", + "integrity": "sha512-Gl37r9qGuiKadIvTZdSZvzCNOttJYw6RcY1oT0oDuB8r2uhuZAdSMQRQTy9FTinp6MY6O9wngGnV6EpQ8wSBAw==", "dependencies": { "@babel/runtime": "^7.0.0" } @@ -8220,6 +6370,11 @@ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" }, + "node_modules/@react-icons/all-files": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@react-icons/all-files/-/all-files-4.1.0.tgz", + "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==" + }, "node_modules/@seznam/compose-react-refs": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@seznam/compose-react-refs/-/compose-react-refs-1.0.4.tgz", @@ -8887,29 +7042,6 @@ "util-deprecate": "^1.0.2" } }, - "node_modules/@storybook/addon-docs/node_modules/@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@storybook/addon-docs/node_modules/@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@storybook/addon-docs/node_modules/@storybook/addons": { "version": "6.1.20", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.20.tgz", @@ -10539,41 +8671,12 @@ "core-js": "^3.0.1" } }, - "node_modules/@storybook/core/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, "node_modules/@storybook/core/node_modules/@babel/compat-data": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.8.tgz", "integrity": "sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==", "dev": true }, - "node_modules/@storybook/core/node_modules/@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, "node_modules/@storybook/core/node_modules/@babel/helper-compilation-targets": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz", @@ -10586,121 +8689,12 @@ "semver": "^6.3.0" } }, - "node_modules/@storybook/core/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.8.tgz", - "integrity": "sha512-qioaRrKHQbn4hkRKDHbnuQ6kAxmmOF+kzKGnIfxPK4j2rckSJCpKzr/SSTlohSCiE3uAQpNDJ9FIh4baeE8W+w==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz", - "integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-replace-supers": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz", - "integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "node_modules/@storybook/core/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, "node_modules/@storybook/core/node_modules/@babel/helper-validator-option": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "dev": true }, - "node_modules/@storybook/core/node_modules/@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@storybook/core/node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@storybook/core/node_modules/@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@storybook/core/node_modules/@babel/plugin-proposal-object-rest-spread": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", @@ -10723,16 +8717,6 @@ "@babel/helper-plugin-utils": "^7.13.0" } }, - "node_modules/@storybook/core/node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, "node_modules/@storybook/core/node_modules/@babel/plugin-transform-classes": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", @@ -10757,34 +8741,6 @@ "@babel/helper-plugin-utils": "^7.13.0" } }, - "node_modules/@storybook/core/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/@storybook/core/node_modules/@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, "node_modules/@storybook/core/node_modules/@storybook/addons": { "version": "6.1.20", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.20.tgz", @@ -11285,18 +9241,6 @@ "node": ">=4" } }, - "node_modules/@storybook/core/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, "node_modules/@storybook/core/node_modules/ejs": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", @@ -11845,12 +9789,6 @@ "node": ">=10" } }, - "node_modules/@storybook/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@storybook/core/node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -12911,88 +10849,6 @@ "fork-ts-checker-webpack-plugin": "^4.1.0" } }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@storybook/preset-typescript/node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@storybook/preset-typescript/node_modules/@storybook/node-logger": { "version": "5.3.21", "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-5.3.21.tgz", @@ -14103,29 +11959,28 @@ } }, "node_modules/@superset-ui/chart-controls": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.53.tgz", - "integrity": "sha512-PjIDka4/lUwXUNEGjkQOIMwVWF2WfknqM6pKFNDPO0/nG4S4faQk96z/ABOXp8GYwIbBshnmmbmW4TCrCQ10Xw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.85.tgz", + "integrity": "sha512-RNk6za6IfcnAKoDFA8XpWgK5AdwPCEprKauv29Z+8VkHrMT8DkoEwDtgl0vutEmLRPECAL+lR76g5B9Z33iCJQ==", "dependencies": { - "@superset-ui/core": "0.17.53", + "@react-icons/all-files": "^4.1.0", + "@superset-ui/core": "0.17.81", "lodash": "^4.17.15", "prop-types": "^15.7.2" }, "peerDependencies": { + "@emotion/react": "^11.1.5", "@types/react": "*", "antd": "^4.9.4", - "react": "^16.13.1", - "react-icons": "^4.2.0" + "react": "^16.13.1" } }, "node_modules/@superset-ui/core": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.53.tgz", - "integrity": "sha512-2bIRrK3Y+4ZSNu6drc1EzHTq6fO3aWfdjCh43ytju88nlADHheQXgwxEKnmjzI141qxiVL2+oSL2kC6pSTkW8A==", + "version": "0.17.81", + "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.81.tgz", + "integrity": "sha512-CaDihqqwFnqtO9yWHxPbZh3MKTot66Zd27ifdIYQCVEMQwz5OpP6zUbULekq7NAbPMHwf7Ju3FOzN0uEzWwSmw==", "dependencies": { "@babel/runtime": "^7.1.2", - "@emotion/cache": "^11.1.3", - "@emotion/react": "^11.1.5", "@emotion/styled": "^11.3.0", "@types/d3-format": "^1.3.0", "@types/d3-interpolate": "^1.3.1", @@ -14154,6 +12009,8 @@ "whatwg-fetch": "^3.0.0" }, "peerDependencies": { + "@emotion/cache": "^11.1.3", + "@emotion/react": "^11.1.5", "@types/react": "*", "@types/react-loadable": "*", "react": "^16.13.1", @@ -14195,19 +12052,6 @@ "@emotion/is-prop-valid": "^1.1.0", "@emotion/serialize": "^1.0.2", "@emotion/utils": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } } }, "node_modules/@superset-ui/core/node_modules/@emotion/utils": { @@ -14236,24 +12080,32 @@ } }, "node_modules/@superset-ui/core/node_modules/d3-scale": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.4.tgz", - "integrity": "sha512-PG6gtpbPCFqKbvdBEswQcJcTzHC8VEd/XzezF5e68KlkT4/ggELw/nR1tv863jY6ufKTvDlzCMZvhe06codbbA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", "dependencies": { "d3-array": "^2.3.0", "d3-format": "1 - 2", "d3-interpolate": "1.2.0 - 2", - "d3-time": "1 - 2", + "d3-time": "^2.1.1", "d3-time-format": "2 - 3" } }, + "node_modules/@superset-ui/core/node_modules/d3-scale/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, "node_modules/@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.53.tgz", - "integrity": "sha512-NLevYzzhQyRgP+vdEfhJyDxJIBbGM/bJTJfFw1iRllny3WQax6iU/X5hUw/iWZqruVNkwSnUA39+EGcjU1aIjg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.85.tgz", + "integrity": "sha512-bmPvflt73ncje+22YTDTkPGIbfylPcqksiCB74/dh/cjUy39lCEpBdL1iYYee6z2vmFdn6uNWuHtpENDKTG83g==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", "d3-tip": "^0.9.1", @@ -14272,24 +12124,24 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.53.tgz", - "integrity": "sha512-a3Y8b/1nSuFvzEzUDTVVmad5/YjTBhz0qU2rcVGrdKp2kzuSVXVVljdN7KVisDUNHhYqrttLM8RQrqGw9f7x1A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.85.tgz", + "integrity": "sha512-raQUDM3Dgcld7UIvXe9XGr4NEJV6x8J+gjVUQLlkvsttA32VN+J1tUhevxmT2eMMxifaaLnGWiBA678Q2ENVfg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2", "react": "^16.13.1" } }, "node_modules/@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.53.tgz", - "integrity": "sha512-zTImQdeBT8raXnxafBIHvaVqOqKoECfyDwgFlPKhs4M7EXPG7U8/VLg0Oi2dCA7/SFZA/ASrJwc/KxW399vJhw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.85.tgz", + "integrity": "sha512-g8OAwInPI9r/k/0ip9pXvQR7L6PInQhN8KfMb4j5VgC4+eUl04BQDpsclVoYOz5KqthkxPvGhdlOH0zHybv3Vg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.0.3", "prop-types": "^15.6.2" @@ -14304,13 +12156,13 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.53.tgz", - "integrity": "sha512-QYL0Feyfu7ZH1GeQ9sfEaEgnW2IQG93sJnM29NO53CjSvdbbZItfU9v6xVnAo6jMwcam7JLNYRtuIPgJevNThw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.85.tgz", + "integrity": "sha512-O2DiLUO0lkkpemtjwJ845veW4vFwZUkb13TkfV1ef37HBDDdEJkUa6Ls8zh7C4aybqn4HG4mr32IHg3DV2WgYg==", "dependencies": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "prop-types": "^15.6.2" }, "peerDependencies": { @@ -14318,12 +12170,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.53.tgz", - "integrity": "sha512-F7hkrBxC7EWrClQ1jb7anzj1SmIjqXVMz2JKhzwEUk++Tafnn0mrB7Yo51u3twFFOY5bwn+KcI1NObzBRkXguQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.85.tgz", + "integrity": "sha512-8QB2IPNadJ5pha2PPs0AQe5zHEnpaIcTY+JGH8LFxUFtuDLjGSuGJJNbIyM/QT63zMp0WCn5VCwgeMuKnuU9Xw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" }, @@ -14332,12 +12184,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.53.tgz", - "integrity": "sha512-NXx/E3AiTxkL+qwaj8B0IDrhWo6P5u5EuXXx1xaWqMTH18YomyeA9l4NBPwsjCfhAMqrEeT0hzeY2/WSoPq5KQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.85.tgz", + "integrity": "sha512-zuxkDDTxxVtDkg9iu7GtcNuG9URh9GmNRM5lH2wsOdKD/UTlZrkt0Lf4tp5kKycknyHWdWFixe1CSLaLzuwwOA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-svg-legend": "^1.x", "d3-tip": "^0.9.1", @@ -14345,14 +12197,14 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.53.tgz", - "integrity": "sha512-EQ/VvG+qCec+IqnwYHA90iHAjkhnPNGkKbTuKlsRyL3ONfxg3n6L4EQOlAA0HvELKkFAZXBxh8TA8Qc3j+g4Fw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.85.tgz", + "integrity": "sha512-dqrTTDeMvdm4Lgb7Cav5lm86GgiQxYda1yXWoXFlqXvwZt40EJqCxYUAAHYKD5JmlqjzIn1wuvCHzJQsDxANDQ==", "dependencies": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", "@vx/scale": "^0.0.197", @@ -14421,12 +12273,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.53.tgz", - "integrity": "sha512-LsM4HOuOkiabRNxMUjjietbFx99admne59Mm5zQdsRPNEpN/EKEWu8R4G4crSSqxxzD9KVnveRPE7OD0n91k/A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.85.tgz", + "integrity": "sha512-lVzXqqLFgZ80I/fDbf2TT3cXCEW+xYKvna4Ldod/dUDFWjVIy3rzP7HicHsS7XCLPpeUHgZUFZhx8NEvTWhDqg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", "prop-types": "^15.6.2" @@ -14456,12 +12308,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.53.tgz", - "integrity": "sha512-JuM77arnxECuSiHkdLMry4JruuVTAfTKTtR8F4qGOpiYiXzGEv4K+y12eqBe1o94ckJF43Esz9e1fdPLDkjqTw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.85.tgz", + "integrity": "sha512-YJIZjcfpMDaJtve6EfEr1lUgCGnsutTxyKEOj3b6wjYMfoLhSUproDoW9TUQtBR3tFrY3PrLrZg+O41L9grDIA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", "prop-types": "^15.6.2", @@ -14482,12 +12334,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.53.tgz", - "integrity": "sha512-QkRVm0XGoOxqOX0nRvHnGon2gG8MmV+dbBBpmPkmspxCWKrn183Wzq5SiMlM4vgo2HaroWUIPuBgLBd7rYZtGw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.85.tgz", + "integrity": "sha512-JIOvvGUlVzbk2zWyQMD8WE1UN95HdOuM1I7py5LIEqJH3CwWN5SGmtDG50DAvKAhEt5InmdQkm7ihr0Uw2LoQw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "distributions": "^1.0.0", "prop-types": "^15.6.2", "reactable": "^1.1.0" @@ -14497,12 +12349,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.53.tgz", - "integrity": "sha512-NcwuEd+rXfmwPshPby0jEgnJnbYfKruM7l0Hb3lIw6iMTc1IV21d1CMftQPvYYdwagam0FapBO2YcSvnvj2rDw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.85.tgz", + "integrity": "sha512-ecSKIBL7806jD+sQu6ub+92tI8W1OfLZWMUW7SeupDphlDJkrQEIQ63FQOCwWEAI9xJhn+hoxIe6M2Y9Um0FwQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" }, @@ -14511,12 +12363,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.53.tgz", - "integrity": "sha512-CTzKjaKCdT/+bFlXUDD4nXC2CO7mXmIPJ2K/M94rY2G2gdAWRZJ1i2HlcvTP+RY/AItzZm3C+E7hYdAQ6toBkA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.85.tgz", + "integrity": "sha512-seF8oZ5lRdrqGzQBDqcQIrIV9QHp6IH7e9+pyG0F4BzSY8WNKIjFVkd92HcNcm/KeqkzID1mqGv1aCzlDQNaiw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", "prop-types": "^15.6.2" @@ -14526,24 +12378,24 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.53.tgz", - "integrity": "sha512-bk7mttnZFGgGmWCfj0kO++65XsMNyQJch0dgfRRnLVTlSnY89/kGqszTKybbCZhsbx4T5bJ+bn6hZKAGH+FnUA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-oXBXgzi4uBKG1FsOd9ZFIhPQvaBIYanzHdqwthafsRDG9kgKL9b//+MCIZgRlqlSJK+vsf4EMxXucFKJtoLDuA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.53.tgz", - "integrity": "sha512-ppvQuKAS0rMhniKenLXSKczmAsHX4igYc0bVZAvfFDmLNW3tnlmivL+zYSw/sQ9PAhjMGDbTBlSio1oJ+91wiA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.85.tgz", + "integrity": "sha512-NilJNxC49YM+j7WqGmna/+G1m0/VXb1hoj2Z2PXPy8KYGMKrLQAwNE0ce7yBnQqSljvxq5Memfjqzqjoio5aoA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "nvd3": "1.8.6", "prop-types": "^15.6.2" @@ -14553,12 +12405,12 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.53.tgz", - "integrity": "sha512-3tvMghg5WUAq40su8cZrjJHoc/TsK1WWx6UFu+j2mPOh/BJJZb8wh7A63X82ubLdyzEqdjxsEs9pzZWzs7kUHw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.85.tgz", + "integrity": "sha512-MV47pYL5QkVAT8rfYrLT9joJF8a48mLiS3suVT+u4vHv3rrsWNTa00irD1HqNoWLDCuZr+U7pCt+UbW/0w2yGw==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-sankey": "^0.4.2", "prop-types": "^15.6.2" @@ -14568,47 +12420,47 @@ } }, "node_modules/@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.53.tgz", - "integrity": "sha512-60aGflqOi5+XDE3BR/p+Pw0xVp7OHsjwroX77CwkwBtFkw1AFVWczaTJH6CYeeCJZXCLYjrbc5OFMuaxIJ+j+Q==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.85.tgz", + "integrity": "sha512-z3I/NCQa64LXKiel+AswpdnUuhsS/0hiziY+T+BuN2geeVrWC+LMQya1hMDg+dDR3RgpkKqgpI9lEf1KTuhHaQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.53.tgz", - "integrity": "sha512-t0z7XPsDtDpnZ+fIpn57w9Vi3oWQ7ximDdjmag1WGhC6+dwR3XxEpNcicI6P6xfNX078RT8Iz89PZQBtagAAkA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.85.tgz", + "integrity": "sha512-L6rRXhEasNQpFN9SQ0ZB6qfa94XIVlCxyEmy8DOR2N39nlLncCweiHtgvgS4Su9H86nidZ/6ryNZ4fKxmm2mBg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.53.tgz", - "integrity": "sha512-LV16Qwiz7ahfhCmuWIGk6f54KpdRJDAyLtr/ifFi8a2AcoG27Lf7hZZ3mCI9Jl5X6c7LLBmvAHfxdbBnLGa8+g==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.85.tgz", + "integrity": "sha512-X4HVCOuhe45qaqUzTfjrKxAr9RoSbdwmWlCWLK2djVJDAFYGqhTxcpNEXxSv+cMqpyMBLhwXqZ5SMV5u0Gu+Eg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "node_modules/@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.53.tgz", - "integrity": "sha512-gnDBTyWPctqucyQzAObH6N+3f9GUQq9qpQ4cNbtvpIoVgXowYA5Q5dIfXBPnq525t78o3eiWqclTYf2Xcd62Kw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.85.tgz", + "integrity": "sha512-bxO1/tY2FI1MzfircbjwuwbLzI+jg2puIlKFek6vkbQuxA9+gCtxKD0SLOlxadNQXW4kUrU2DxhfPlr6th55bA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.4.0", "d3-color": "^1.4.1", @@ -14627,19 +12479,14 @@ "internmap": "^1.0.0" } }, - "node_modules/@superset-ui/legacy-plugin-chart-world-map/node_modules/d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" - }, "node_modules/@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.53.tgz", - "integrity": "sha512-HUlE6IZUjFvPMiXCj1cdRiR4avFLVhT5qwIQZk1l30kycl8/73rTm37Y/syBfZMPrfCrIW3nyReqfcnAaNqw9g==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", "d3-color": "^1.2.3", @@ -14650,43 +12497,178 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.7.tgz", - "integrity": "sha512-TaAX1PlZ5DhsNelgoOjCfPBlFtHZDFcozJEIAV2qXzXUo6rfIgskqIq4X3VbMuYnngZw5of4hAtOH1+Tgv+Wmw==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.11.tgz", + "integrity": "sha512-N4l8zavJ3kQUyoPFqG6zXQT8EELFcXtD/GNRy3aJzSgHObRhK8+aqFaWIn3ncQrBuEiUzGDnHLNni/LRAxz7vA==", "dependencies": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", "bootstrap-slider": "^10.0.0", "d3-array": "^1.2.4", - "d3-color": "^1.2.0", - "d3-scale": "^2.1.2", - "deck.gl": "7.1.11", + "d3-color": "^1.4.1", + "d3-scale": "^3.0.0", + "deck.gl": "8.5.2", "jquery": "^3.4.1", "lodash": "^4.17.15", - "mapbox-gl": "^0.53.0", + "mapbox-gl": "^2.4.0", "moment": "^2.20.1", "mousetrap": "^1.6.1", "prop-types": "^15.6.0", "react-bootstrap-slider": "2.1.5", - "react-map-gl": "^4.0.10", + "react-map-gl": "^6.1.16", "underscore": "^1.8.3", "urijs": "^1.18.10", "xss": "^1.0.6" }, "peerDependencies": { - "@superset-ui/chart-controls": "^0.17.12", - "@superset-ui/core": "^0.17.11", - "react": "^15 || ^16" + "@superset-ui/chart-controls": "^0.17.80", + "@superset-ui/core": "^0.17.80", + "react": "^16.13.1" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/@mapbox/geojson-rewind": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz", + "integrity": "sha512-eL7fMmfTBKjrb+VFHXCGv9Ot0zc3C0U+CwXo1IrP+EPwDczLoXv34Tgq3y+2mPSFNVUXgU42ILWJTC7145KPTA==", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/@mapbox/mapbox-gl-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.0.tgz", + "integrity": "sha512-zu4udqYiBrKMQKwpKJ4hhPON7tz0QR/JZ3iGpHnNWFmH3Sv/ysxlICATUtGCFpsyJf2v1WpFhlzaZ3GhhKmPMA==" + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "dependencies": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/d3-scale/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/d3-time/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/mapbox-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.4.0.tgz", + "integrity": "sha512-oH5fkh209U2Zqvgs1bBS+SQVhrj8rUT9OTgZmg+20GaNthDJFYDCXvGidVAkgacuCHSIALTZKzMV1DFgO+isFQ==", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.0", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.2.5", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.3.0", + "grid-index": "^1.1.0", + "minimist": "^1.2.5", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.3", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=14.15.4" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/react-map-gl": { + "version": "6.1.16", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-6.1.16.tgz", + "integrity": "sha512-d/4kFMMh2hDeZNeQOUm2wC1/as9q93EZiDmM5mGBx0LIch+9pTFgO6ZINIuUD9Zz4JqWGpthyjoKr3QKgrGiRA==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "@types/geojson": "^7946.0.7", + "@types/mapbox-gl": "^2.0.3", + "mapbox-gl": "^2.3.0", + "mjolnir.js": "^2.5.0", + "prop-types": "^15.7.2", + "resize-observer-polyfill": "^1.5.1", + "viewport-mercator-project": "^7.0.3" + }, + "engines": { + "node": ">= 4", + "npm": ">= 3" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-7+bR4FbF5SYsmkHfDp61QiwCKtwNDyPsddk9TzfsDA5DQr5Goii5CVD2SXjglweFCxjrzVZf945ahqYfUIk8UA==", + "dependencies": { + "kdbush": "^3.0.0" + } + }, + "node_modules/@superset-ui/legacy-preset-chart-deckgl/node_modules/viewport-mercator-project": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.3.tgz", + "integrity": "sha512-5nSgVK8jKTSKzOvsa8TSSd2IeQCpHfSNiBOOOMQLvzlxgWD0YoF4xRmyZio3GaLtKSE+50UB892X3R1SAMbaww==", + "dependencies": { + "@math.gl/web-mercator": "^3.4.3" } }, "node_modules/@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.53.tgz", - "integrity": "sha512-wTbQRCZDrnb16tLJzXYbIiCFbHddRJ3fo5DKsbv6MFNrfOLWWx1SjAZ5C60e57u33XpKdTE5jGpEuGdq7BZ55w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.85.tgz", + "integrity": "sha512-KoC8kIqP6zwtGzXBUj6yMaF3Cz2TXygAe7ZCG5XeavgrjgKsWqOXAdQzJ9oBrCPXPQBC5hF7TruBB/Xd839TDA==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-tip": "^0.9.1", "dompurify": "^2.0.6", @@ -14703,15 +12685,15 @@ } }, "node_modules/@superset-ui/plugin-chart-echarts": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.53.tgz", - "integrity": "sha512-XXKqhr2CwZfi02qW55d9SQnNmdewTsAJT6xePBjci0SXAZRmi/T8vRbq2OCDJ7mQ0de7kjVBydAuOEEU/Y554A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.85.tgz", + "integrity": "sha512-dAqRzVVm7Shn2fgKoj2puX0jefe2r14dOQThJ8CG/hRlpmXsKmLnSywwLvQVxq1oUhh+uQYsemdYBCsrQAuxcg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", - "echarts": "^5.1.1", + "echarts": "^5.1.2", "lodash": "^4.17.15", "mathjs": "^8.0.1" }, @@ -14720,25 +12702,27 @@ } }, "node_modules/@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.53.tgz", - "integrity": "sha512-18CTaM1sRgK5laFwHlKV+1A7+l9YWwPAvb7XrMjS8CQq0T2aEqNSQm7KWByG+LEj2x86idM8gaWghNes27yVtQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-tTU79c/C7g5sdZxRNVcl6QFbwqhsv0Bi/XeYOwhTNAe7qf7LmkvwQB78fvh7WW+U2FBltkhHmOxOb/HIJscXVg==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", - "@superset-ui/react-pivottable": "^0.12.8" + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", + "@superset-ui/react-pivottable": "^0.12.12" }, "peerDependencies": { + "@ant-design/icons": "^4.2.2", "react": "^16.13.1" } }, "node_modules/@superset-ui/plugin-chart-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.53.tgz", - "integrity": "sha512-PSeL/zQSTvQyztjUMMm4U4G6oEM3xk3wkC4HTpuLEpjQ7qyGme39M1JeCGvNG4pPZRm0nO4pU+0U/36oR0lAjw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.85.tgz", + "integrity": "sha512-RGeIGAQuiiOy6JgpnQC+rtVDWR8jGZF1DOER+WkgbfTeKiRmVGyo7wOSb7dvQlsaitUg2Y30962arOGeEcgvpA==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@react-icons/all-files": "^4.1.0", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", "d3-array": "^2.4.0", @@ -14751,8 +12735,7 @@ "peerDependencies": { "@types/react": "*", "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-icons": "^4.2.0" + "react-dom": "^16.13.1" } }, "node_modules/@superset-ui/plugin-chart-table/node_modules/d3-array": { @@ -14764,12 +12747,12 @@ } }, "node_modules/@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.53.tgz", - "integrity": "sha512-lQTr9UpeoGgX1B0SkjrvtL0zjgYVoJbm6RVv8ELG+efCG1oYAoIVgw2sahJI4zLqNiHcNeWqHUcu7NK06uc4mA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.85.tgz", + "integrity": "sha512-nCbmndNxUoHWkYRCARD1C4XjzxXtk/OkCUVdB3EKaPNWbi0QuQHUuAZUh3ROIewxd3KAMoSUYRIjTVYDbetnJQ==", "dependencies": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", "d3-cloud": "^1.2.5", @@ -14802,14 +12785,14 @@ } }, "node_modules/@superset-ui/preset-chart-xy": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.53.tgz", - "integrity": "sha512-nmqif4Zd7Tdx4hLoDiiRiNFUFn1kliumjp9RQK68eMaefWcl1vTMT7nPmyFvgUH5390HJygpC3up50+j5Bngkg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.85.tgz", + "integrity": "sha512-Cd2Ji1trP6aHuOfHK0GDza8iQUVkJNbtIFSReb240ViewICH/7mtsrj8jLAvf3tyOORLD3AW2j8s68UDCrBi6g==", "dependencies": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", "@vx/scale": "^0.0.197", @@ -14929,9 +12912,9 @@ } }, "node_modules/@superset-ui/react-pivottable": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.8.tgz", - "integrity": "sha512-7DRxX/w1uSQE1pibSe64t1o+fmiP7ZWT2FJkjK510bSJm8NUIPCXtmpK+NKtNZuCteE9sqE7bQxd54SSq2xWKw==", + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.12.tgz", + "integrity": "sha512-4+wx2kQy3IRKoWHTf2bIkXjlzDA0u/eN2k0FfLfJ5bdER2GuqZErWuKtiZzARsn5kSS9hPIrvt77uv52R3FnfQ==", "dependencies": { "immutability-helper": "^3.1.1", "prop-types": "^15.7.2", @@ -15008,18 +12991,18 @@ } }, "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", - "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/@svgr/babel-preset": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", - "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", "dev": true, "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", @@ -15029,55 +13012,55 @@ "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.4.0" + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" }, "engines": { "node": ">=10" } }, "node_modules/@svgr/core": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", - "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", "dev": true, "dependencies": { - "@svgr/plugin-jsx": "^5.4.0", - "camelcase": "^6.0.0", - "cosmiconfig": "^6.0.0" + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" }, "engines": { "node": ">=10" } }, "node_modules/@svgr/core/node_modules/camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/@svgr/core/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/@svgr/core/node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -15088,14 +13071,14 @@ } }, "node_modules/@svgr/core/node_modules/parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" }, "engines": { @@ -15112,26 +13095,26 @@ } }, "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", - "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", "dev": true, "dependencies": { - "@babel/types": "^7.9.5" + "@babel/types": "^7.12.6" }, "engines": { "node": ">=10" } }, "node_modules/@svgr/plugin-jsx": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", - "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", "dev": true, "dependencies": { - "@babel/core": "^7.7.5", - "@svgr/babel-preset": "^5.4.0", - "@svgr/hast-util-to-babel-ast": "^5.4.0", + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", "svg-parser": "^2.0.2" }, "engines": { @@ -15139,13 +13122,13 @@ } }, "node_modules/@svgr/plugin-svgo": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", - "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", "dev": true, "dependencies": { - "cosmiconfig": "^6.0.0", - "merge-deep": "^3.0.2", + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", "svgo": "^1.2.2" }, "engines": { @@ -15153,25 +13136,34 @@ } }, "node_modules/@svgr/plugin-svgo/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" }, "engines": { - "node": ">=8" + "node": ">=10" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@svgr/plugin-svgo/node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -15182,14 +13174,14 @@ } }, "node_modules/@svgr/plugin-svgo/node_modules/parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" }, "engines": { @@ -15206,1203 +13198,348 @@ } }, "node_modules/@svgr/webpack": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", - "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", "dev": true, "dependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/preset-env": "^7.9.5", - "@babel/preset-react": "^7.9.4", - "@svgr/core": "^5.4.0", - "@svgr/plugin-jsx": "^5.4.0", - "@svgr/plugin-svgo": "^5.4.0", + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", "loader-utils": "^2.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/@svgr/webpack/node_modules/@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/compat-data": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", - "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", + "node_modules/@svgr/webpack/node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, - "dependencies": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" + "engines": { + "node": ">= 4" } }, - "node_modules/@svgr/webpack/node_modules/@babel/core": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.2.tgz", - "integrity": "sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ==", + "node_modules/@svgr/webpack/node_modules/json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.2", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helpers": "^7.10.1", - "@babel/parser": "^7.10.2", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-builder-react-jsx": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.1.tgz", - "integrity": "sha512-KXzzpyWhXgzjXIlJU1ZjIXzUPdej1suE6vzqgImZ/cpAsR/CC8gUcX4EWRmDfWz/cs6HOCPMBIJ3nKoXt3BFuw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/types": "^7.10.1" + "node": ">=6" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "node_modules/@svgr/webpack/node_modules/loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.10.1", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", + "node_modules/@testing-library/dom": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", + "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.4", + "lz-string": "^1.4.4", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-regex": "^7.10.1", - "regexpu-core": "^4.7.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-hoist-variables": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", - "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", + "node_modules/@testing-library/dom/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@babel/types": "^7.10.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-module-transforms": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", - "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", + "node_modules/@testing-library/dom/node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "node_modules/@testing-library/dom/node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "lodash": "^4.17.13" + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", - "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", + "node_modules/@testing-library/dom/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-simple-access": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", - "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" - } + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/@svgr/webpack/node_modules/@babel/helper-wrap-function": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", + "node_modules/@testing-library/jest-dom": { + "version": "5.11.6", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", + "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^4.2.2", + "chalk": "^3.0.0", + "css": "^3.0.0", + "css.escape": "^1.5.1", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@svgr/webpack/node_modules/@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", - "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", - "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "node_modules/@testing-library/react": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.0.tgz", + "integrity": "sha512-90xKYJzskZ7q/AoSuWraQL4EGZlr75uZvDt3nrO4M+rugN02zjO45tmOBq/JBOgDiMIL1tkhHioKXjJsVaSINA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.27.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "node_modules/@testing-library/react-hooks": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz", + "integrity": "sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "@babel/runtime": "^7.12.5", + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", + "@types/react-test-renderer": ">=16.9.0", + "filter-console": "^0.1.1", + "react-error-boundary": "^3.1.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", - "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", + "node_modules/@testing-library/react-hooks/node_modules/react-error-boundary": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.0.tgz", + "integrity": "sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "node_modules/@testing-library/user-event": { + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.7.0.tgz", + "integrity": "sha512-KzRM1KNDoW8pJ2HTenrUhTjV6wJMHvWAagDs8DDrYSWz6y4PN+K2jSvlm2bMHWNRk5LTJPo9jqIjNjJ3FlqXNw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=4" + "node": ">=10", + "npm": ">=6" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } + "node_modules/@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } + "node_modules/@types/aria-query": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", + "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", + "dev": true }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "node_modules/@types/babel__core": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "node_modules/@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/types": "^7.0.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "node_modules/@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "lodash": "^4.17.13" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", - "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", + "node_modules/@types/babel__traverse": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", + "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/types": "^7.3.0" } }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } + "node_modules/@types/braces": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", + "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", + "dev": true }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "node_modules/@types/cheerio": { + "version": "0.22.21", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", + "integrity": "sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", - "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.10.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.1.tgz", - "integrity": "sha512-rBjKcVwjk26H3VX8pavMxGf33LNlbocMHdSeldIEswtQ/hrjyTG8fKKILW1cSkODyRovckN/uZlGb2+sAV9JUQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.1.tgz", - "integrity": "sha512-MBVworWiSRBap3Vs39eHt+6pJuLUAaK4oxGc8g+wY+vuSJvLiEQjW1LSTqKb8OUPtDvHCkdPhk7d6sjC19xyFw==", - "dev": true, - "dependencies": { - "@babel/helper-builder-react-jsx": "^7.10.1", - "@babel/helper-builder-react-jsx-experimental": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.1.tgz", - "integrity": "sha512-4p+RBw9d1qV4S749J42ZooeQaBomFPrSxa9JONLHJ1TxCBo3TzJ79vtmG2S2erUT8PDDrPdw4ZbXGr2/1+dILA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.1.tgz", - "integrity": "sha512-neAbaKkoiL+LXYbGDvh6PjPG+YeA67OsZlE78u50xbWh2L1/C81uHiNP5d1fw+uqUIoiNdCC8ZB+G4Zh3hShJA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-regenerator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", - "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-regex": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/preset-env": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz", - "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.10.1", - "@babel/helper-compilation-targets": "^7.10.2", - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-proposal-async-generator-functions": "^7.10.1", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-dynamic-import": "^7.10.1", - "@babel/plugin-proposal-json-strings": "^7.10.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-numeric-separator": "^7.10.1", - "@babel/plugin-proposal-object-rest-spread": "^7.10.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.1", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.1", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.1", - "@babel/plugin-transform-arrow-functions": "^7.10.1", - "@babel/plugin-transform-async-to-generator": "^7.10.1", - "@babel/plugin-transform-block-scoped-functions": "^7.10.1", - "@babel/plugin-transform-block-scoping": "^7.10.1", - "@babel/plugin-transform-classes": "^7.10.1", - "@babel/plugin-transform-computed-properties": "^7.10.1", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-dotall-regex": "^7.10.1", - "@babel/plugin-transform-duplicate-keys": "^7.10.1", - "@babel/plugin-transform-exponentiation-operator": "^7.10.1", - "@babel/plugin-transform-for-of": "^7.10.1", - "@babel/plugin-transform-function-name": "^7.10.1", - "@babel/plugin-transform-literals": "^7.10.1", - "@babel/plugin-transform-member-expression-literals": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.1", - "@babel/plugin-transform-modules-commonjs": "^7.10.1", - "@babel/plugin-transform-modules-systemjs": "^7.10.1", - "@babel/plugin-transform-modules-umd": "^7.10.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.10.1", - "@babel/plugin-transform-object-super": "^7.10.1", - "@babel/plugin-transform-parameters": "^7.10.1", - "@babel/plugin-transform-property-literals": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.1", - "@babel/plugin-transform-reserved-words": "^7.10.1", - "@babel/plugin-transform-shorthand-properties": "^7.10.1", - "@babel/plugin-transform-spread": "^7.10.1", - "@babel/plugin-transform-sticky-regex": "^7.10.1", - "@babel/plugin-transform-template-literals": "^7.10.1", - "@babel/plugin-transform-typeof-symbol": "^7.10.1", - "@babel/plugin-transform-unicode-escapes": "^7.10.1", - "@babel/plugin-transform-unicode-regex": "^7.10.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.2", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "node_modules/@svgr/webpack/node_modules/@babel/preset-react": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.1.tgz", - "integrity": "sha512-Rw0SxQ7VKhObmFjD/cUcKhPTtzpeviEFX1E6PgP+cYOhQ98icNqtINNFANlsdbQHrmeWnqdxA4Tmnl1jy5tp3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-transform-react-display-name": "^7.10.1", - "@babel/plugin-transform-react-jsx": "^7.10.1", - "@babel/plugin-transform-react-jsx-development": "^7.10.1", - "@babel/plugin-transform-react-jsx-self": "^7.10.1", - "@babel/plugin-transform-react-jsx-source": "^7.10.1", - "@babel/plugin-transform-react-pure-annotations": "^7.10.1" - } - }, - "node_modules/@svgr/webpack/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@svgr/webpack/node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/@svgr/webpack/node_modules/browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - }, - "bin": { - "browserslist": "cli.js" - } - }, - "node_modules/@svgr/webpack/node_modules/caniuse-lite": { - "version": "1.0.30001084", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz", - "integrity": "sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w==", - "dev": true - }, - "node_modules/@svgr/webpack/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@svgr/webpack/node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/@svgr/webpack/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@svgr/webpack/node_modules/electron-to-chromium": { - "version": "1.3.474", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz", - "integrity": "sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw==", - "dev": true - }, - "node_modules/@svgr/webpack/node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@svgr/webpack/node_modules/json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@svgr/webpack/node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/@svgr/webpack/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@svgr/webpack/node_modules/node-releases": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", - "dev": true - }, - "node_modules/@svgr/webpack/node_modules/regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@svgr/webpack/node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "node_modules/@svgr/webpack/node_modules/regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/@svgr/webpack/node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/@svgr/webpack/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@testing-library/dom": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.4.tgz", - "integrity": "sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@testing-library/dom/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@testing-library/dom/node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "5.11.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", - "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^4.2.2", - "chalk": "^3.0.0", - "css": "^3.0.0", - "css.escape": "^1.5.1", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.0.tgz", - "integrity": "sha512-90xKYJzskZ7q/AoSuWraQL4EGZlr75uZvDt3nrO4M+rugN02zjO45tmOBq/JBOgDiMIL1tkhHioKXjJsVaSINA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.27.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@testing-library/react-hooks": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-5.0.3.tgz", - "integrity": "sha512-UrnnRc5II7LMH14xsYNm/WRch/67cBafmrSQcyFh0v+UUmSf1uzfB7zn5jQXSettGwOSxJwdQUN7PgkT0w22Lg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/react": ">=16.9.0", - "@types/react-dom": ">=16.9.0", - "@types/react-test-renderer": ">=16.9.0", - "filter-console": "^0.1.1", - "react-error-boundary": "^3.1.0" - } - }, - "node_modules/@testing-library/react-hooks/node_modules/react-error-boundary": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.0.tgz", - "integrity": "sha512-lmPrdi5SLRJR+AeJkqdkGlW/CRkAUvZnETahK58J4xb5wpbfDngasEGu+w0T1iXEhVrYBJZeW+c4V1hILCnMWQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/@testing-library/user-event": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.7.0.tgz", - "integrity": "sha512-KzRM1KNDoW8pJ2HTenrUhTjV6wJMHvWAagDs8DDrYSWz6y4PN+K2jSvlm2bMHWNRk5LTJPo9jqIjNjJ3FlqXNw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" - }, - "node_modules/@types/aria-query": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", - "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", - "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/braces": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", - "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", - "dev": true - }, - "node_modules/@types/cheerio": { - "version": "0.22.21", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz", - "integrity": "sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==", - "dev": true, - "dependencies": { - "@types/node": "*" + "@types/node": "*" } }, "node_modules/@types/classnames": { @@ -16528,6 +13665,16 @@ "integrity": "sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow==", "dev": true }, + "node_modules/@types/flatbuffers": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@types/flatbuffers/-/flatbuffers-1.10.0.tgz", + "integrity": "sha512-7btbphLrKvo5yl/5CC2OCxUSMx1wV1wvGT1qDXkSt7yi00/YW7E8k6qzXqJHsp+WU0eoG7r6MTQQXI9lIvd0qA==" + }, + "node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, "node_modules/@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -16669,6 +13816,14 @@ "@types/lodash": "*" } }, + "node_modules/@types/mapbox-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.4.0.tgz", + "integrity": "sha512-Na5vXw6Ez0L5To/+pL78dWPNoG6QlPdEDdnkSmIL5HWxemD+s0pTmTWDbMj7tcqJ2hnVyOyukVIveR9HPi7eeA==", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/markdown-to-jsx": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.3.tgz", @@ -16711,9 +13866,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "10.12.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", - "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", + "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" }, "node_modules/@types/node-fetch": { "version": "2.5.8", @@ -17059,9 +14214,9 @@ "integrity": "sha512-mE3eRK0fpTN/GnNBOIg2tGq2cFhchQXF6fCbrLxus75TgnoOECbdHikr948FGO/UAml7/ZhLMa5FbGkF5PKvmw==" }, "node_modules/@types/seedrandom": { - "version": "2.4.29", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.29.tgz", - "integrity": "sha512-nSjRNBaE9y5Zzv6CCwDWH6DVC0E+g8/1Nu7LOSntusxQaHZOv9GJ7rrnS5G1dSGd2k1Ovvg5+JJG6idjuGtpDA==" + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz", + "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==" }, "node_modules/@types/shortid": { "version": "0.0.29", @@ -17090,9 +14245,9 @@ "dev": true }, "node_modules/@types/sortablejs": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.6.tgz", - "integrity": "sha512-QRz8Z+uw2Y4Gwrtxw8hD782zzuxxugdcq8X/FkPsXUa1kfslhGzy13+4HugO9FXNo+jlWVcE6DYmmegniIQ30A==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.7.tgz", + "integrity": "sha512-lGCwwgpj8zW/ZmaueoPVSP7nnc9t8VqVWXS+ASX3eoUUENmiazv0rlXyTRludXzuX9ALjPsMqBu85TgJNWbTOg==", "peer": true }, "node_modules/@types/source-list-map": { @@ -17120,6 +14275,11 @@ "@types/jest": "*" } }, + "node_modules/@types/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ==" + }, "node_modules/@types/uglify-js": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", @@ -18808,6 +15968,36 @@ "normalize-path": "^2.1.1" } }, + "node_modules/apache-arrow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-4.0.1.tgz", + "integrity": "sha512-DyF7GXCbSjsw4P5C8b+qW7OnJKa6w9mJI0mhV0+EfZbVZCmhfiF6ffqcnrI/kzBrRqn9hH/Ft9n5+m4DTbBJpg==", + "dependencies": { + "@types/flatbuffers": "^1.10.0", + "@types/node": "^14.14.37", + "@types/text-encoding-utf-8": "^1.0.1", + "command-line-args": "5.1.1", + "command-line-usage": "6.1.1", + "flatbuffers": "1.12.0", + "json-bignum": "^0.0.3", + "pad-left": "^2.1.0", + "text-encoding-utf-8": "^1.0.2", + "tslib": "^2.2.0" + }, + "bin": { + "arrow2csv": "bin/arrow2csv.js" + } + }, + "node_modules/apache-arrow/node_modules/@types/node": { + "version": "14.17.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.9.tgz", + "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==" + }, + "node_modules/apache-arrow/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, "node_modules/aphrodite": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/aphrodite/-/aphrodite-1.2.5.tgz", @@ -18898,6 +16088,14 @@ "node": ">=0.10.0" } }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -21655,6 +18853,14 @@ "cdl": "bin/cdl.js" } }, + "node_modules/cartocolor": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cartocolor/-/cartocolor-4.0.2.tgz", + "integrity": "sha512-+Gh9mb6lFxsDOLQlBLPxAHCnWXlg2W8q3AcVwqRcy95TdBbcOU89Wrb6h2Hd/6Ww1Kc1pzXmUdpnWD+xeCG0dg==", + "dependencies": { + "colorbrewer": "1.0.0" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -21795,6 +19001,73 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "bundleDependencies": [ + "abbrev", + "ansi-regex", + "aproba", + "are-we-there-yet", + "balanced-match", + "brace-expansion", + "chownr", + "code-point-at", + "concat-map", + "console-control-strings", + "core-util-is", + "debug", + "deep-extend", + "delegates", + "detect-libc", + "fs-minipass", + "fs.realpath", + "gauge", + "glob", + "has-unicode", + "iconv-lite", + "ignore-walk", + "inflight", + "inherits", + "ini", + "is-fullwidth-code-point", + "isarray", + "minimatch", + "minimist", + "minipass", + "minizlib", + "mkdirp", + "ms", + "needle", + "node-pre-gyp", + "nopt", + "npm-bundled", + "npm-packlist", + "npmlog", + "number-is-nan", + "object-assign", + "once", + "os-homedir", + "os-tmpdir", + "osenv", + "path-is-absolute", + "process-nextick-args", + "rc", + "readable-stream", + "rimraf", + "safe-buffer", + "safer-buffer", + "sax", + "semver", + "set-blocking", + "signal-exit", + "string-width", + "string_decoder", + "strip-ansi", + "strip-json-comments", + "tar", + "util-deprecate", + "wide-align", + "wrappy", + "yallist" + ], "dev": true, "hasInstallScript": true, "optional": true, @@ -21811,14 +19084,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/abbrev": { "version": "1.1.1", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/ansi-regex": { "version": "2.1.1", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -21826,14 +19101,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/aproba": { "version": "1.2.0", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/are-we-there-yet": { "version": "1.1.5", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "delegates": "^1.0.0", @@ -21842,25 +19119,34 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/balanced-match": { "version": "1.0.0", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/brace-expansion": { "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/chokidar/node_modules/fsevents/node_modules/chownr": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true + }, "node_modules/chokidar/node_modules/fsevents/node_modules/code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -21868,30 +19154,30 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/debug": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "ms": "^2.1.1" @@ -21899,9 +19185,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=4.0.0" @@ -21909,15 +19195,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/detect-libc": { "version": "1.0.3", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, + "inBundle": true, + "license": "Apache-2.0", "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" @@ -21926,18 +19213,28 @@ "node": ">=0.10" } }, + "node_modules/chokidar/node_modules/fsevents/node_modules/fs-minipass": { + "version": "1.2.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, "node_modules/chokidar/node_modules/fsevents/node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "aproba": "^1.0.3", @@ -21952,9 +19249,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -21970,16 +19267,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -21990,9 +19287,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/ignore-walk": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "minimatch": "^3.0.4" @@ -22000,9 +19297,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "once": "^1.3.0", @@ -22011,16 +19308,26 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, + "node_modules/chokidar/node_modules/fsevents/node_modules/ini": { + "version": "1.3.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/chokidar/node_modules/fsevents/node_modules/is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "number-is-nan": "^1.0.0" @@ -22031,16 +19338,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -22050,23 +19357,58 @@ } }, "node_modules/chokidar/node_modules/fsevents/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "0.0.8", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, + "node_modules/chokidar/node_modules/fsevents/node_modules/minipass": { + "version": "2.3.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/chokidar/node_modules/fsevents/node_modules/minizlib": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/chokidar/node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/chokidar/node_modules/fsevents/node_modules/ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/needle": { "version": "2.3.0", - "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "debug": "^4.1.0", @@ -22082,8 +19424,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/node-pre-gyp": { "version": "0.12.0", - "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", "optional": true, "dependencies": { "detect-libc": "^1.0.2", @@ -22103,8 +19446,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/nopt": { "version": "4.0.1", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "abbrev": "1", @@ -22116,16 +19460,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/npm-bundled": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/npm-packlist": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "ignore-walk": "^3.0.1", @@ -22134,9 +19478,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "are-we-there-yet": "~1.1.2", @@ -22147,9 +19491,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -22157,9 +19501,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -22167,9 +19511,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "wrappy": "1" @@ -22177,9 +19521,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -22187,9 +19531,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -22197,9 +19541,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "os-homedir": "^1.0.0", @@ -22208,9 +19552,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -22218,15 +19562,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/rc": { "version": "1.2.8", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "inBundle": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "optional": true, "dependencies": { "deep-extend": "^0.6.0", @@ -22238,11 +19583,18 @@ "rc": "cli.js" } }, + "node_modules/chokidar/node_modules/fsevents/node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, "node_modules/chokidar/node_modules/fsevents/node_modules/readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "core-util-is": "~1.0.0", @@ -22256,8 +19608,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/rimraf": { "version": "2.6.3", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "glob": "^7.1.3" @@ -22268,29 +19621,30 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/semver": { "version": "5.7.0", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -22298,23 +19652,23 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "safe-buffer": "~5.1.0" @@ -22322,9 +19676,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "code-point-at": "^1.0.0", @@ -22337,9 +19691,9 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "dependencies": { "ansi-regex": "^2.0.0" @@ -22350,26 +19704,45 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/chokidar/node_modules/fsevents/node_modules/tar": { + "version": "4.4.8", + "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "engines": { + "node": ">=4.5" + } + }, "node_modules/chokidar/node_modules/fsevents/node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, + "inBundle": true, + "license": "MIT", "optional": true }, "node_modules/chokidar/node_modules/fsevents/node_modules/wide-align": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, + "inBundle": true, + "license": "ISC", "optional": true, "dependencies": { "string-width": "^1.0.2 || 2" @@ -22377,9 +19750,16 @@ }, "node_modules/chokidar/node_modules/fsevents/node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/chokidar/node_modules/fsevents/node_modules/yallist": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", "optional": true }, "node_modules/chokidar/node_modules/glob-parent": { @@ -22404,19 +19784,6 @@ "node": ">=0.10.0" } }, - "node_modules/chokidar/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "optional": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/chownr": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", @@ -22720,34 +20087,6 @@ "node": ">=0.8" } }, - "node_modules/clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", - "dev": true, - "dependencies": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone-deep/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -22875,6 +20214,11 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorbrewer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.0.0.tgz", + "integrity": "sha1-T5czO5abp2Ejgr5LwzlLNB+0yKI=" + }, "node_modules/colorette": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", @@ -22911,6 +20255,85 @@ "trim": "0.0.1" } }, + "node_modules/command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dependencies": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz", + "integrity": "sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==", + "dependencies": { + "array-back": "^4.0.1", + "chalk": "^2.4.2", + "table-layout": "^1.0.1", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/command-line-usage/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/command-line-usage/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/commander": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", @@ -22968,9 +20391,9 @@ } }, "node_modules/compute-scroll-into-view": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz", - "integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ==" + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", + "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -23646,45 +21069,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, - "node_modules/core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "dependencies": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - } - }, - "node_modules/core-js-compat/node_modules/browserslist": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.1.tgz", - "integrity": "sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001030", - "electron-to-chromium": "^1.3.363", - "node-releases": "^1.1.50" - }, - "bin": { - "browserslist": "cli.js" - } - }, - "node_modules/core-js-compat/node_modules/caniuse-lite": { - "version": "1.0.30001035", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", - "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", - "dev": true - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/core-js-pure": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", @@ -24617,9 +22001,9 @@ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "node_modules/d3-color": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", - "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "node_modules/d3-dispatch": { "version": "1.0.6", @@ -24970,19 +22354,21 @@ "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" }, "node_modules/deck.gl": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/deck.gl/-/deck.gl-7.1.11.tgz", - "integrity": "sha512-OUj9JE544N6Y/DCdfdnsbqKn9o72bWgRfsKhyi8aZ8v76hq7XyelmO2GljBmHGYmuMNVLrKcymNMV0m8EEgpZA==", - "dependencies": { - "@deck.gl/aggregation-layers": "7.1.11", - "@deck.gl/core": "7.1.11", - "@deck.gl/geo-layers": "7.1.11", - "@deck.gl/google-maps": "7.1.11", - "@deck.gl/json": "7.1.11", - "@deck.gl/layers": "7.1.11", - "@deck.gl/mapbox": "7.1.11", - "@deck.gl/mesh-layers": "7.1.11", - "@deck.gl/react": "7.1.11" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/deck.gl/-/deck.gl-8.5.2.tgz", + "integrity": "sha512-tsEyv62Zzc+GT3By0Y1R2gqEJ8K3tGBDaLprAoeAsg7fvIa5ikFBdWEBFHa1UDbgE2UEmYbcBK/yK4GAL8Ia4A==", + "dependencies": { + "@deck.gl/aggregation-layers": "8.5.2", + "@deck.gl/carto": "8.5.2", + "@deck.gl/core": "8.5.2", + "@deck.gl/extensions": "8.5.2", + "@deck.gl/geo-layers": "8.5.2", + "@deck.gl/google-maps": "8.5.2", + "@deck.gl/json": "8.5.2", + "@deck.gl/layers": "8.5.2", + "@deck.gl/mapbox": "8.5.2", + "@deck.gl/mesh-layers": "8.5.2", + "@deck.gl/react": "8.5.2" } }, "node_modules/decode-uri-component": { @@ -25041,6 +22427,14 @@ "node": ">= 0.4" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -25343,6 +22737,16 @@ "mathfn": "^1.0.0" } }, + "node_modules/dnd-core": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", + "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", + "dependencies": { + "@react-dnd/asap": "^4.0.0", + "@react-dnd/invariant": "^2.0.0", + "redux": "^4.0.4" + } + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -25554,6 +22958,11 @@ "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", "dev": true }, + "node_modules/draco3d": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.4.1.tgz", + "integrity": "sha512-9Rxonc70xiovBC+Bq1h57SNZIHzWTibU1VfIGp5z3Xx8dPtv4yT5uGhiH7P5uvJRR2jkrvHafRxR7bTANkvfpg==" + }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -25596,12 +23005,12 @@ } }, "node_modules/echarts": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.1.tgz", - "integrity": "sha512-b3nP8M9XwZM2jISuA+fP0EuJv8lcfgWrinel185Npy8bE/UhXTDIPJcqgQOCWdvk0c5CeT6Dsm1xBjmJXAGlxQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz", + "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==", "dependencies": { "tslib": "2.0.3", - "zrender": "5.1.0" + "zrender": "5.1.1" } }, "node_modules/echarts/node_modules/tslib": { @@ -25619,9 +23028,6 @@ "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" - }, - "bin": { - "editorconfig": "bin/editorconfig" } }, "node_modules/editorconfig/node_modules/lru-cache": { @@ -25709,6 +23115,11 @@ "node": ">= 0.10" } }, + "node_modules/emotion-rgba": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz", + "integrity": "sha512-fSt51Lh4a1fppXY3nQrMUC00p1jIYMSaRRkUhPiOJ3s9oumae1tY41AJytRK9d4YmJDP9njJBndgdDn9j7CbsA==" + }, "node_modules/emotion-theming": { "version": "10.0.27", "resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-10.0.27.tgz", @@ -28144,6 +25555,14 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, + "node_modules/expression-eval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/expression-eval/-/expression-eval-2.1.0.tgz", + "integrity": "sha512-FUJO/Akvl/JOWkvlqZaqbkhsEWlCJWDeZG4tzX96UH68D9FeRgYgtb55C2qtqbORC0Q6x5419EDjWu4IT9kQfg==", + "dependencies": { + "jsep": "^0.3.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -28445,9 +25864,9 @@ "dev": true }, "node_modules/fetch-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.0.tgz", - "integrity": "sha512-FUc9XZuhyE3ka3m53lec29PXVhdRf59QG01nE+OZdfl0M/R0E7Pk6k6qeWzHhX1pHl/f2JPA97sjjbHRgSg/9A==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", + "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==" }, "node_modules/figgy-pudding": { "version": "3.5.1", @@ -28688,6 +26107,17 @@ "node": ">=6" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -28745,6 +26175,11 @@ "rimraf": "bin.js" } }, + "node_modules/flatbuffers": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", + "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==" + }, "node_modules/flatted": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", @@ -28789,18 +26224,6 @@ "node": ">=0.10.0" } }, - "node_modules/for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -29038,9 +26461,12 @@ "dev": true }, "node_modules/fuse.js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.3.0.tgz", - "integrity": "sha512-ESBRkGLWMuVkapqYCcNO1uqMg5qbCKkgb+VS6wsy17Rix0/cMS9kSOZoYkjH8Ko//pgJ/EEGu0GTjk2mjX2LGQ==" + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.6.tgz", + "integrity": "sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==", + "engines": { + "node": ">=10" + } }, "node_modules/gauge": { "version": "2.7.4", @@ -29062,6 +26488,7 @@ "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -29343,6 +26770,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "engines": { "node": ">=4" } @@ -29438,9 +26866,9 @@ } }, "node_modules/h3-js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.0.tgz", - "integrity": "sha512-EcH/qGU4khZsAEG39Uu8MvaCing0JFcuoe3K4Xmg5MofDIu1cNJl7z2AQS8ggvXGxboiLJqsGirhEqFKdd2gAA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", + "integrity": "sha512-LPjlHSwB9zQZrMqKloCZmmmt3yZzIK7nqPcXqwU93zT3TtYG6jP4tZBzAPouxut7lLjdFbMQ75wRBiKfpsnY7w==", "engines": { "node": ">=4", "npm": ">=3", @@ -32102,9 +29530,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-diff/node_modules/chalk": { @@ -32118,9 +29543,6 @@ }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-diff/node_modules/color-convert": { @@ -36077,10 +33499,19 @@ "node": ">=10" } }, + "node_modules/jsep": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-0.3.5.tgz", + "integrity": "sha512-AoRLBDc6JNnKjNcmonituEABS5bcfqDhQAWWXNTFrqu6nVXBpBAGfcoTGZMFlIrh9FjmE1CQyX9CTNwZrXMMDA==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -36096,6 +33527,14 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-bignum": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", + "integrity": "sha1-QRY7UENsdz2CQk28IO1w23YEuNc=", + "engines": { + "node": ">=0.8" + } + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -36398,15 +33837,6 @@ "webpack-sources": "^1.1.0" } }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -36518,18 +33948,6 @@ "node": ">=6" } }, - "node_modules/levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "dependencies": { - "leven": "^3.1.0" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -36980,12 +34398,11 @@ "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==" }, "node_modules/math.gl": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-2.3.3.tgz", - "integrity": "sha512-wZhx7574KHUpJVMzkaQ559zfn3R8iB0BOilwNrfL/fOLQfPo2TPWsKX96PdfS4svKA2XIGi3yfizrv2Redcv0g==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.5.3.tgz", + "integrity": "sha512-cRQRZlc+XvNHd3bIfu3kdPPPAW0vwDelZJmkjn2TDvCyPcmyDtAiZ2Poo1aFoINP7HzN6oHYxapc/0wV3q6Opg==", "dependencies": { - "@babel/runtime": "^7.0.0", - "gl-matrix": "^3.0.0" + "@math.gl/core": "3.5.3" } }, "node_modules/mathfn": { @@ -37235,32 +34652,6 @@ "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", "dev": true }, - "node_modules/merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-deep/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -37564,27 +34955,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "optional": true, - "dependencies": { - "minipass": "^2.9.0" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, "node_modules/mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -37631,28 +35001,6 @@ "node": ">=0.10.0" } }, - "node_modules/mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "dependencies": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-object/node_modules/for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/mjolnir.js": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.5.0.tgz", @@ -37698,6 +35046,17 @@ "node": "*" } }, + "node_modules/moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", @@ -38828,6 +36187,17 @@ "node": ">=4" } }, + "node_modules/pad-left": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pad-left/-/pad-left-2.1.0.tgz", + "integrity": "sha1-FuajstRKjhOMsIOMx8tAOk/J6ZQ=", + "dependencies": { + "repeat-string": "^1.5.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pako": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", @@ -39196,18 +36566,6 @@ "node": ">=6" } }, - "node_modules/pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -42127,9 +39485,6 @@ }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/pretty-format/node_modules/color-convert": { @@ -42195,12 +39550,12 @@ } }, "node_modules/probe.gl": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/probe.gl/-/probe.gl-3.3.0.tgz", - "integrity": "sha512-59E6AEw4N8sU4PKfAl7S2UBYJCOa064WpEFcXfeFOB/36FJtplYY+261DqLjLAvOqRRHiKVEQUBo63PQ3jKeWA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/probe.gl/-/probe.gl-3.4.0.tgz", + "integrity": "sha512-9CLByZATuhuG/Viq3ckfWU+dAhb7dMmjzsyCy4s7ds9ueTejcVRENxL197/XacOK/AN61YrEERB0QnouB0Qc0Q==", "dependencies": { "@babel/runtime": "^7.0.0", - "@probe.gl/stats": "3.3.0" + "@probe.gl/stats": "3.4.0" } }, "node_modules/process": { @@ -43612,17 +40967,6 @@ "@babel/highlight": "^7.10.4" } }, - "node_modules/react-dev-utils/node_modules/@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, "node_modules/react-dev-utils/node_modules/@nodelib/fs.stat": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", @@ -44159,26 +41503,6 @@ "dnd-core": "^11.1.3" } }, - "node_modules/react-dnd-html5-backend/node_modules/dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "dependencies": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - }, - "node_modules/react-dnd/node_modules/dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "dependencies": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - }, "node_modules/react-docgen": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-5.3.1.tgz", @@ -44463,11 +41787,6 @@ "prop-types": "^15.6.1" } }, - "node_modules/react-icons": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz", - "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==" - }, "node_modules/react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", @@ -44559,6 +41878,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "node_modules/react-lines-ellipsis": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-lines-ellipsis/-/react-lines-ellipsis-0.15.0.tgz", + "integrity": "sha512-8kWpEmu7ijmB6Gz5t+eSjNux2SpVXZBsmfeFE8LjMS7tU3H8ai475CyNc0dH0RDTwt4Esr7c06Xq4SB7Gpl9yQ==" + }, "node_modules/react-loadable": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", @@ -44741,11 +42065,7 @@ "node_modules/react-reverse-portal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.0.1.tgz", - "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==", - "peerDependencies": { - "react": "^16.0.0", - "react-dom": "^16.0.0" - } + "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==" }, "node_modules/react-router": { "version": "5.1.2", @@ -44800,6 +42120,14 @@ "prop-types": "^15.5.8" } }, + "node_modules/react-search-input/node_modules/fuse.js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", + "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==", + "engines": { + "node": ">=6" + } + }, "node_modules/react-select": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz", @@ -45409,6 +42737,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" }, + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "engines": { + "node": ">=6" + } + }, "node_modules/reduce-function-call": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", @@ -45781,15 +43117,6 @@ "unified": "9.2.0" } }, - "node_modules/remark-mdx/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, "node_modules/remark-mdx/node_modules/@babel/core": { "version": "7.12.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", @@ -45817,75 +43144,12 @@ "node": ">=6.9.0" } }, - "node_modules/remark-mdx/node_modules/@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/remark-mdx/node_modules/@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/remark-mdx/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, "node_modules/remark-mdx/node_modules/@babel/helper-plugin-utils": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, - "node_modules/remark-mdx/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/remark-mdx/node_modules/@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/remark-mdx/node_modules/@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/remark-mdx/node_modules/@babel/plugin-proposal-object-rest-spread": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", @@ -45906,60 +43170,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/remark-mdx/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/remark-mdx/node_modules/@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/remark-mdx/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/remark-mdx/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remark-mdx/node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -46064,18 +43274,6 @@ "xtend": "^4.0.1" } }, - "node_modules/remark-mdx/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remark-mdx/node_modules/unified": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", @@ -46642,14 +43840,6 @@ "npm": ">=2.0.0" } }, - "node_modules/s2-geometry": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/s2-geometry/-/s2-geometry-1.2.10.tgz", - "integrity": "sha1-xv8i8+zK/Q7qSRtgtEwUG5iHrKs=", - "dependencies": { - "long": "^3.2.0" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -46734,11 +43924,11 @@ } }, "node_modules/scroll-into-view-if-needed": { - "version": "2.2.26", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz", - "integrity": "sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw==", + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", + "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", "dependencies": { - "compute-scroll-into-view": "^1.0.16" + "compute-scroll-into-view": "^1.0.17" } }, "node_modules/seedrandom": { @@ -46746,11 +43936,6 @@ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, - "node_modules/seer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/seer/-/seer-0.2.5.tgz", - "integrity": "sha512-//0Zwt0x97KQhIWrp4oq9AVNvGA2ctCx4dmFddpkORjRr6bW+hyC8eOhWBVIhiU3uHv1XLU1dekfFKOi28RGHA==" - }, "node_modules/select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", @@ -46776,6 +43961,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -46942,42 +44128,6 @@ "sha.js": "bin.js" } }, - "node_modules/shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shallow-clone/node_modules/kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "dependencies": { - "is-buffer": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shallow-clone/node_modules/lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/shallow-copy": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", @@ -47519,9 +44669,9 @@ } }, "node_modules/sortablejs": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz", - "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg==" + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" }, "node_modules/source-list-map": { "version": "2.0.1", @@ -47823,11 +44973,6 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, "engines": { "node": ">=0.10.0" } @@ -49551,6 +46696,36 @@ "node": ">=10.0.0" } }, + "node_modules/table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "dependencies": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/table/node_modules/ajv": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", @@ -49628,46 +46803,6 @@ "node": ">=0.6" } }, - "node_modules/tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, - "dependencies": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "optional": true, - "dependencies": { - "minipass": "^2.6.0" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, "node_modules/telejson": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.1.0.tgz", @@ -49880,6 +47015,11 @@ "node": "*" } }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -50549,9 +47689,9 @@ } }, "node_modules/typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -50561,6 +47701,14 @@ "node": ">=4.2.0" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "engines": { + "node": ">=8" + } + }, "node_modules/ua-parser-js": { "version": "0.7.25", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.25.tgz", @@ -53364,6 +50512,26 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, + "node_modules/wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "dependencies": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -53730,9 +50898,9 @@ } }, "node_modules/zrender": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.0.tgz", - "integrity": "sha512-c+8VRx52ycbmqwHeHLlo/BAfIHBl/JZNLM6cfDQFgzIH05yb+f5J9F/fbRsP+zGc8dW9XHuhdt8/iqukgMZSeg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz", + "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==", "dependencies": { "tslib": "2.0.3" } @@ -53921,11 +51089,11 @@ } }, "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { @@ -53938,6 +51106,7 @@ "version": "7.12.10", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/generator": "^7.12.10", @@ -53956,113 +51125,11 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -54071,6 +51138,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -54079,6 +51147,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -54086,58 +51155,28 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "@babel/generator": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.3.tgz", - "integrity": "sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.10.3", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", - "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.1.tgz", - "integrity": "sha512-irQJ8kpQUV3JasXPSFQ+LCCtJSc5ceZrPFVj6TElR6XCHssi3jV8ch3odIrNtjJFRZZVbrOEfJMI79TPU/h1pQ==", - "dev": true, + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-module-imports": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" } }, "@babel/helper-compilation-targets": { @@ -54152,228 +51191,59 @@ "semver": "^5.5.0" }, "dependencies": { - "browserslist": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", - "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001173", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.634", - "escalade": "^3.1.1", - "node-releases": "^1.1.69" - } - }, - "caniuse-lite": { - "version": "1.0.30001173", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001173.tgz", - "integrity": "sha512-R3aqmjrICdGCTAnSXtNyvWYMK3YtV5jwudbq0T7nN9k4kmE4CBuwPqyJ+KBzepSTh0huivV2gLbSMEzTTmfeYw==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.635", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.635.tgz", - "integrity": "sha512-RRriZOLs9CpW6KTLmgBqyUdnY0QNqqWs0HOtuQGGEMizOTNNn1P7sGRBxARnUeLejOsgwjDyRqT3E/CSst02ZQ==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "node-releases": { - "version": "1.1.69", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", - "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", - "dev": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "requires": { - "@babel/types": "^7.12.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "browserslist": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "caniuse-lite": "^1.0.30001173", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.634", + "escalade": "^3.1.1", + "node-releases": "^1.1.69" } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } + "caniuse-lite": { + "version": "1.0.30001173", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001173.tgz", + "integrity": "sha512-R3aqmjrICdGCTAnSXtNyvWYMK3YtV5jwudbq0T7nN9k4kmE4CBuwPqyJ+KBzepSTh0huivV2gLbSMEzTTmfeYw==", + "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "electron-to-chromium": { + "version": "1.3.635", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.635.tgz", + "integrity": "sha512-RRriZOLs9CpW6KTLmgBqyUdnY0QNqqWs0HOtuQGGEMizOTNNn1P7sGRBxARnUeLejOsgwjDyRqT3E/CSst02ZQ==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "node-releases": { + "version": "1.1.69", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", + "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", + "dev": true } } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", + "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + } + }, "@babel/helper-create-regexp-features-plugin": { "version": "7.8.8", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", @@ -54422,63 +51292,42 @@ } } }, - "@babel/helper-define-map": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", - "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", - "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, "@babel/helper-function-name": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz", - "integrity": "sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.3", - "@babel/template": "^7.10.3", - "@babel/types": "^7.10.3" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz", - "integrity": "sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.10.3" + "@babel/types": "^7.14.5" } }, "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", - "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" } }, "@babel/helper-module-imports": { @@ -54493,6 +51342,7 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, "requires": { "@babel/helper-module-imports": "^7.12.1", "@babel/helper-replace-supers": "^7.12.1", @@ -54503,174 +51353,21 @@ "@babel/traverse": "^7.12.1", "@babel/types": "^7.12.1", "lodash": "^4.17.19" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "requires": { - "@babel/types": "^7.12.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==" }, "@babel/helper-regex": { "version": "7.8.3", @@ -54690,35 +51387,25 @@ "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-wrap-function": "^7.10.4", "@babel/types": "^7.12.1" - }, - "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - } } }, "@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-simple-access": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, "requires": { "@babel/types": "^7.12.1" } @@ -54733,18 +51420,18 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" }, "@babel/helper-validator-option": { "version": "7.12.1", @@ -54762,525 +51449,33 @@ "@babel/template": "^7.10.4", "@babel/traverse": "^7.10.4", "@babel/types": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/helpers": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "dev": true, "requires": { "@babel/template": "^7.10.4", "@babel/traverse": "^7.12.5", "@babel/types": "^7.12.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/node": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.12.10.tgz", - "integrity": "sha512-lJT1sXp1bEfAZ7B2ChEOOiUxaGbIWkcAixqZDpbHnJWUqIjoofOGo5ON1bJ9HOmtMdF7rqKiOoM7zZSI87El3g==", - "dev": true, - "requires": { - "@babel/register": "^7.12.10", - "commander": "^4.0.1", - "core-js": "^3.2.1", - "lodash": "^4.17.19", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.13.4", - "v8flags": "^3.1.1" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - } - } - }, - "@babel/parser": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.3.tgz", - "integrity": "sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", - "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-proposal-decorators": { - "version": "7.13.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.5.tgz", - "integrity": "sha512-i0GDfVNuoapwiheevUOuSW67mInqJ8qw7uWfpjNVeHMn143kXblEy/bmL9AdZ/0yf/4BMQeWXezK0tQIvNPqag==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-decorators": "^7.12.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.8.tgz", - "integrity": "sha512-qioaRrKHQbn4hkRKDHbnuQ6kAxmmOF+kzKGnIfxPK4j2rckSJCpKzr/SSTlohSCiE3uAQpNDJ9FIh4baeE8W+w==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz", - "integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-replace-supers": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz", - "integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -55289,39 +51484,83 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } } } }, + "@babel/node": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.12.10.tgz", + "integrity": "sha512-lJT1sXp1bEfAZ7B2ChEOOiUxaGbIWkcAixqZDpbHnJWUqIjoofOGo5ON1bJ9HOmtMdF7rqKiOoM7zZSI87El3g==", + "dev": true, + "requires": { + "@babel/register": "^7.12.10", + "commander": "^4.0.1", + "core-js": "^3.2.1", + "lodash": "^4.17.19", + "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.4", + "v8flags": "^3.1.1" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.13.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.5.tgz", + "integrity": "sha512-i0GDfVNuoapwiheevUOuSW67mInqJ8qw7uWfpjNVeHMn143kXblEy/bmL9AdZ/0yf/4BMQeWXezK0tQIvNPqag==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-decorators": "^7.12.13" + } + }, "@babel/plugin-proposal-dynamic-import": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", @@ -55393,39 +51632,6 @@ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", - "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-numeric-separator": "^7.10.1" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", - "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" - }, - "dependencies": { - "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - } - } - }, "@babel/plugin-proposal-optional-catch-binding": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", @@ -55448,29 +51654,13 @@ } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", - "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - }, - "dependencies": { - "@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" - } - } + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { @@ -55691,22 +51881,6 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, - "@babel/plugin-transform-classes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", - "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-define-map": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "globals": "^11.1.0" - } - }, "@babel/plugin-transform-computed-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", @@ -55744,16 +51918,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "@babel/plugin-transform-flow-strip-types": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.13.0.tgz", @@ -55781,94 +51945,6 @@ "requires": { "@babel/helper-function-name": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/plugin-transform-literals": { @@ -55880,15 +51956,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "@babel/plugin-transform-modules-amd": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", @@ -55935,15 +52002,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, "@babel/plugin-transform-new-target": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", @@ -55961,176 +52019,6 @@ "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-replace-supers": "^7.12.1" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "requires": { - "@babel/types": "^7.12.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true - }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/plugin-transform-parameters": { @@ -56142,22 +52030,13 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "@babel/plugin-transform-react-constant-elements": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.1.tgz", - "integrity": "sha512-V4os6bkWt/jbrzfyVcZn2ZpuHZkvj3vyBU0U/dtS8SZuMS7Rfx5oknTrtfyXJ2/QZk8gX7Yls5Z921ItNpE30Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.14.5.tgz", + "integrity": "sha512-NBqLEx1GxllIOXJInJAQbrnwwYJsV3WaMHIcOwD8rhYS0AabTWn7kHdHgPgu5RmHLU0q4DMxhAMu8ue/KampgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-react-display-name": { @@ -56179,37 +52058,6 @@ "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-jsx": "^7.12.13", "@babel/types": "^7.12.17" - }, - "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "requires": { - "@babel/types": "^7.12.13" - } - } - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.1.tgz", - "integrity": "sha512-XwDy/FFoCfw9wGFtdn5Z+dHh6HXKHkC6DwKNWpN74VWinUagZfDcEJc3Y8Dn5B3WMVnAllX8Kviaw7MtC5Epwg==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.1.tgz", - "integrity": "sha512-mfhoiai083AkeewsBHUpaS/FM1dmUENHBMpS/tugSJ7VXqXO5dCN1Gkint2YvM1Cdv1uhmAKt1ZOuAjceKmlLA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" } }, "@babel/plugin-transform-regenerator": { @@ -56269,16 +52117,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", - "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, "@babel/plugin-transform-typeof-symbol": { "version": "7.12.10", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", @@ -56297,198 +52135,6 @@ "@babel/helper-create-class-features-plugin": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-typescript": "^7.12.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", - "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", - "dev": true - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", - "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" } }, "@babel/plugin-transform-unicode-regex": { @@ -56501,15 +52147,6 @@ "@babel/helper-plugin-utils": "^7.10.4" }, "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, "@babel/helper-create-regexp-features-plugin": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", @@ -56647,43 +52284,6 @@ "semver": "^5.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, "@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", @@ -56724,88 +52324,12 @@ "@babel/types": "^7.12.1" } }, - "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "requires": { - "@babel/types": "^7.12.7" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", - "dev": true, - "requires": { - "@babel/types": "^7.12.11" - } - }, "@babel/helper-validator-option": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", "dev": true }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", - "dev": true - }, "@babel/plugin-proposal-numeric-separator": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", @@ -56827,16 +52351,6 @@ "@babel/plugin-transform-parameters": "^7.12.1" } }, - "@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, "@babel/plugin-proposal-unicode-property-regex": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", @@ -57001,43 +52515,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" - } - }, - "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "browserslist": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", @@ -57057,17 +52534,6 @@ "integrity": "sha512-R3aqmjrICdGCTAnSXtNyvWYMK3YtV5jwudbq0T7nN9k4kmE4CBuwPqyJ+KBzepSTh0huivV2gLbSMEzTTmfeYw==", "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "core-js-compat": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", @@ -57086,15 +52552,6 @@ } } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, "electron-to-chromium": { "version": "1.3.635", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.635.tgz", @@ -57113,12 +52570,6 @@ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node-releases": { "version": "1.1.69", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", @@ -57153,15 +52604,6 @@ "requires": { "jsesc": "~0.5.0" } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -57201,15 +52643,6 @@ "@babel/plugin-transform-react-pure-annotations": "^7.12.1" }, "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, "@babel/plugin-transform-react-jsx": { "version": "7.12.12", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.12.tgz", @@ -57310,124 +52743,33 @@ } }, "@babel/template": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.3.tgz", - "integrity": "sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.3", - "@babel/parser": "^7.10.3", - "@babel/types": "^7.10.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz", - "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.3" - } - }, - "@babel/highlight": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", - "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.3", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.3.tgz", - "integrity": "sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.3", - "@babel/generator": "^7.10.3", - "@babel/helper-function-name": "^7.10.3", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.3", - "@babel/types": "^7.10.3", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.3.tgz", - "integrity": "sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.3" - } - }, - "@babel/highlight": { - "version": "7.10.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.3.tgz", - "integrity": "sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.3", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -57442,24 +52784,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -58184,79 +53517,146 @@ } }, "@deck.gl/aggregation-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-7.1.11.tgz", - "integrity": "sha512-CHsr+UJhf06Mqb/q60iP7ftHQv3ftHUhJbVO4550PRo+QMFFhHfhxo53gQDDgrQ3stxpAcLT3lXRSNghMoU34g==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/aggregation-layers/-/aggregation-layers-8.5.2.tgz", + "integrity": "sha512-oiqXPmyn2v0lX9tWCvgmWs29stHSLS3tje71Ff2FVXDNmvP5FoZItFa8y7O7KSTkej2/rSwZeSte/a9pri6Njg==", "requires": { + "@luma.gl/shadertools": "^8.5.4", + "@math.gl/web-mercator": "^3.5.3", "d3-hexbin": "^0.2.1" } }, - "@deck.gl/core": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-7.1.11.tgz", - "integrity": "sha512-jUi1CcsnF5KPL2sv7Z0H3x+8amee5csqliZXGbXEBYox1l8naC4PhHg5jTgLaB0ZOHfVDsldPwGdPC+Mi4jP/Q==", + "@deck.gl/carto": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/carto/-/carto-8.5.2.tgz", + "integrity": "sha512-Kw/3NUM+2NcHjxH6b7IOUYXEwmJ4SNQujFzAVFW5amG4Lut8074NGSF5XHi+4M/zgk7vXDFsGRxLqspsA/dg8w==", "requires": { - "@luma.gl/core": "^7.1.0", + "@loaders.gl/loader-utils": "^3.0.6", + "@loaders.gl/mvt": "^3.0.6", + "@loaders.gl/tiles": "^3.0.6", + "@math.gl/web-mercator": "^3.5.3", + "cartocolor": "^4.0.2", + "d3-scale": "^3.2.3" + }, + "dependencies": { + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + } + } + }, + "@deck.gl/core": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/core/-/core-8.5.2.tgz", + "integrity": "sha512-SAFv7fKx6k1Rj8R4qTMQO2wEhEfixROzbcoSS6RivxrfES00KYYj6jJ7iNEnq3dFn6qc37LPpxqtYYHO4BcvYA==", + "requires": { + "@loaders.gl/core": "^3.0.6", + "@loaders.gl/images": "^3.0.6", + "@luma.gl/core": "^8.5.4", + "@math.gl/web-mercator": "^3.5.3", "gl-matrix": "^3.0.0", - "math.gl": "^2.3.0", - "mjolnir.js": "^2.1.2", - "probe.gl": "^3.0.1", - "seer": "^0.2.4", - "viewport-mercator-project": "^6.1.0" + "math.gl": "^3.5.3", + "mjolnir.js": "^2.5.0", + "probe.gl": "^3.4.0" } }, - "@deck.gl/geo-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-7.1.11.tgz", - "integrity": "sha512-gIJ1K98IFSZ12hB+zHyQC+9pMncb9BKVhJTA7pjUpxwcmEkkroqet9zkYQQMeSInK0a67BR9GXjebb/N0U04qA==", + "@deck.gl/extensions": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/extensions/-/extensions-8.5.2.tgz", + "integrity": "sha512-VhbQsMNPM7RCR/ERwb1u1x0rEWAxgXfcCWttW+gYvbiagW/LrAJ22jhOghlRW/wilEmupHYbXQlWkW2V/mYfsg==", "requires": { - "h3-js": "^3.4.3", + "@luma.gl/shadertools": "^8.5.4" + } + }, + "@deck.gl/geo-layers": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/geo-layers/-/geo-layers-8.5.2.tgz", + "integrity": "sha512-t6+TgAdbKWDw8g9UX1y6D+5twcdJuKaXw4qSib/0yVurWi/Mil5Plihybt1l9uBZuwkr+UcpxPR73zzo+qd9MA==", + "requires": { + "@loaders.gl/3d-tiles": "^3.0.6", + "@loaders.gl/gis": "^3.0.6", + "@loaders.gl/loader-utils": "^3.0.6", + "@loaders.gl/mvt": "^3.0.6", + "@loaders.gl/terrain": "^3.0.6", + "@loaders.gl/tiles": "^3.0.6", + "@luma.gl/experimental": "^8.5.4", + "@math.gl/culling": "^3.5.3", + "@math.gl/web-mercator": "^3.5.3", + "h3-js": "^3.6.0", "long": "^3.2.0", - "s2-geometry": "^1.2.10" + "math.gl": "^3.5.3" } }, "@deck.gl/google-maps": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-7.1.11.tgz", - "integrity": "sha512-gYp3NFIsyT5p65HgKjXFWTDzFf7K8+6ce9d9MIqaNgVWFZdsjUy9JL5TttDMQXshaQ1aZpxtLL6ZO3BiI1w8fw==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/google-maps/-/google-maps-8.5.2.tgz", + "integrity": "sha512-Dk3ozenBWgt9nFSYOT4N82urNW/JhiMszfFq6zLt3jUp0N7EJ9d2XO81hclM59BhjIdGWb6drTe96NvtbabVLQ==", + "requires": {} }, "@deck.gl/json": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-7.1.11.tgz", - "integrity": "sha512-obYAXq5VZ0qCTVS8hopS64aXGicUeBNg0/03AAfo+Q5z62cNqagAktGKVZMUsJ13bV8CPohJ2zRWMXO+mAJtew==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/json/-/json-8.5.2.tgz", + "integrity": "sha512-lVS16bvPfLUSidgBURZvGbWEjgK8GjLWlp1iGuLvua2W6TnWIyiKa6a3XoebgeXd8kqwSbQxhNnuSVPX+Di6Rg==", "requires": { - "d3-dsv": "^1.0.8" + "d3-dsv": "^1.0.8", + "expression-eval": "^2.0.0" } }, "@deck.gl/layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-7.1.11.tgz", - "integrity": "sha512-hOylm7Pf3CSvqpDoiCJLnqLAU3PAePISskJ5jjhpXtgHBrm1/Gk4boP4/7t7kFZdbSvVIXin13pPRbT0SWCRPw==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/layers/-/layers-8.5.2.tgz", + "integrity": "sha512-HmpE3qf9CI7sU/xa2DMCNg31pzpzK5XuUHyC70dsLq8AV7Sm3vZQz17KMU/CWSZpVr7yQ8uxTeSQARiv/zeOFQ==", "requires": { - "@loaders.gl/core": "^1.0.3", - "@loaders.gl/images": "^1.0.3", + "@loaders.gl/images": "^3.0.6", "@mapbox/tiny-sdf": "^1.1.0", + "@math.gl/polygon": "^3.5.3", "earcut": "^2.0.6" } }, "@deck.gl/mapbox": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-7.1.11.tgz", - "integrity": "sha512-V4cc9vwXzAOBtWV8x+WtvPVXElGChogkvQketeR2uhz6wIHuH+3sBBRg/Ma476w/II+DKjeHg2AzAZeX3SK7yQ==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mapbox/-/mapbox-8.5.2.tgz", + "integrity": "sha512-nMpzfdPFBVthT+EMgIcKo4YO6bZCqADQtqnxIFtfofZIiKS6R5OSuJ3sXPSNZ9ReCJGzdmndEz7/Qtm9Sia/bA==", + "requires": {} }, "@deck.gl/mesh-layers": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-7.1.11.tgz", - "integrity": "sha512-rI8ffUNh7ac2GpMcGLEiKyRarOPeLfVRlMRKjl9LXU61Wgx6DaHqsMmeqxzjoXEzgiRlY/XgCjepVg0dY6btlQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-8.5.2.tgz", + "integrity": "sha512-dUfQyGjm5CYQg9AQdRsGtEEXGSGHxifPlws0zWWoj1r757wjqM0aZ663TUJEsJQDTLNOvbBLGTiuFeCBUoKO4Q==", "requires": { - "@loaders.gl/core": "^1.0.3", - "@loaders.gl/images": "^1.0.3" + "@loaders.gl/gltf": "^3.0.6", + "@luma.gl/experimental": "^8.5.4", + "@luma.gl/shadertools": "^8.5.4" } }, "@deck.gl/react": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-7.1.11.tgz", - "integrity": "sha512-WUzxhvM3jZIZkBAQgdQR+tFBAVDm5opLCKMWI9YkJUsdJzdv9uwiWCsk3Se1pCTFIa5Asb8U6YAi1CHl+OOFyA==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@deck.gl/react/-/react-8.5.2.tgz", + "integrity": "sha512-h7AJ9nPY1PTjrAVP7T1fvWDChWZrVOsEfYIoEP4W6ILSjvDqEQfVL0+9RhjUwQV2nKrg0QmpqCmbfOrgKQQbYw==", "requires": { "prop-types": "^15.6.0" } @@ -60150,97 +55550,219 @@ } } }, + "@loaders.gl/3d-tiles": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/3d-tiles/-/3d-tiles-3.0.8.tgz", + "integrity": "sha512-jZeOyDPGD2wEkTLW4Do9A4UUQ+OGjhhNXztB0AsttZ69OpkmsxJXb76xxwevf+eThrsTgSTjZ06eC5DHX0kyXA==", + "requires": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/draco": "3.0.8", + "@loaders.gl/gltf": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/math": "3.0.8", + "@loaders.gl/tiles": "3.0.8", + "@math.gl/core": "^3.5.1", + "@math.gl/geospatial": "^3.5.1" + } + }, "@loaders.gl/core": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-1.3.7.tgz", - "integrity": "sha512-dFZkJQc+i2PoqlBMz/aO8Gnn0y6ICafQp8u6cTpCm96h/HHulE8qDBodQlHGHn9EMJDSgVl/zjni+QhqIK31dg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/core/-/core-3.0.8.tgz", + "integrity": "sha512-FIfbhMkoRX2JonEHXHgClC7jwOSsEwvvmjlaTMRAY+gFKvJPGmegkp4VgUZquLFf6GedJt/1TuMMvAX6gdq1pg==", "requires": { - "@babel/runtime": "^7.3.1" + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/worker-utils": "3.0.8", + "probe.gl": "^3.4.0" + } + }, + "@loaders.gl/draco": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/draco/-/draco-3.0.8.tgz", + "integrity": "sha512-ZCXzXNHWQ7H0qk/kC+rWzjMWjLzZGzQcDbdpIuy8xJdp4rTpmMkLUseFPby8vhkmIaqxWPwPB6mx/vM7L6JENg==", + "requires": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@loaders.gl/worker-utils": "3.0.8", + "draco3d": "1.4.1" + } + }, + "@loaders.gl/gis": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/gis/-/gis-3.0.8.tgz", + "integrity": "sha512-7NL+lIb7NezlMupYskVil6M3RZunXJl+TyaVAW82GLbzPSOq+m/G7h3+z0GBa8iv/U/I+cB5BhSN+GZmvFwqEA==", + "requires": { + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "@loaders.gl/gltf": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/gltf/-/gltf-3.0.8.tgz", + "integrity": "sha512-4PXWTlqyvlbZE2Vp4iQ+Y87ZO1WuRvSlbImDhygd0hoINfmJ9ObxrFS3yJcpJTu007nWxXorNVEOKyuoo+4Iyw==", + "requires": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/draco": "3.0.8", + "@loaders.gl/images": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8" } }, "@loaders.gl/images": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-1.3.7.tgz", - "integrity": "sha512-TKqW94vjvWc4RIChhr0Yx6HaVTe8K6h6GFeXcahsKeCxq9/k2qpcigRkXfmb6/37dkp2Qy5COHp73ECgN/q+NA==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/images/-/images-3.0.8.tgz", + "integrity": "sha512-rO2cIYJYlMs/uO9YSoF4/BEA4p/9xQ3gHZ1sIJkPYVnDqzpbu8nvUjWTQqIdL/MkQBTW8tz3twCdM+B6G9Fa2w==", + "requires": { + "@loaders.gl/loader-utils": "3.0.8" + } + }, + "@loaders.gl/loader-utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/loader-utils/-/loader-utils-3.0.8.tgz", + "integrity": "sha512-PW1WyyQ+LXkqoGHBZHsmfNQkKiLAYf1gok+kHnHvY9fCzhJeA1iTNEUKPXGXKgS00m/k5cBTkOWAaOG9KRvBCQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/worker-utils": "3.0.8", + "@probe.gl/stats": "^3.4.0" + } + }, + "@loaders.gl/math": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/math/-/math-3.0.8.tgz", + "integrity": "sha512-jfFpxxr4Bq5JfOPqLVJc4JJGoGGvVTOCWiJhnTtSAKhaNSwldmNWaZ0w8E2nlgPKPMAHiTRKOQnd9sSY5m66Cw==", + "requires": { + "@loaders.gl/images": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@math.gl/core": "^3.5.1" + } + }, + "@loaders.gl/mvt": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/mvt/-/mvt-3.0.8.tgz", + "integrity": "sha512-Jk1QTHgpxMsUT01w5IJJ2en9qq0yOZcL2wGXVc7CFp2h6inB22rC3drUwq1mUNGe6iy3EWIo7EeJVd9B+5JyTQ==", + "requires": { + "@loaders.gl/gis": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@math.gl/polygon": "^3.5.1", + "pbf": "^3.2.1" + } + }, + "@loaders.gl/schema": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-3.0.8.tgz", + "integrity": "sha512-yne5WE7fZZWFl2zF8fzDlYhPVJua6h6mTCSmlQ5pryaMXTZS9mfzXXIFWRL3kswqnQTu/QNFdyFj1mP0haF24w==", + "requires": { + "@types/geojson": "^7946.0.7", + "apache-arrow": "^4.0.0", + "d3-dsv": "^1.2.0" + } + }, + "@loaders.gl/terrain": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/terrain/-/terrain-3.0.8.tgz", + "integrity": "sha512-MtOAYEB/xJB4CN4B0YNPkO4v1ZY332joxiOHQI1x37x4sWVAqOrKLr9jB42sZCB8aINi2WMWGiErtf9wh9L5Pg==", + "requires": { + "@babel/runtime": "^7.3.1", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/schema": "3.0.8", + "@mapbox/martini": "^0.2.0" + } + }, + "@loaders.gl/tiles": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/tiles/-/tiles-3.0.8.tgz", + "integrity": "sha512-Rc+yHFdQg2sYmcYkwvszukFWdm9EW354F9HUR7y/oauos6tsdo4YTj31zgytaYR63/EqWQ7kwI29/eePEcutzg==", + "requires": { + "@loaders.gl/core": "3.0.8", + "@loaders.gl/loader-utils": "3.0.8", + "@loaders.gl/math": "3.0.8", + "@math.gl/core": "^3.5.1", + "@math.gl/culling": "^3.5.1", + "@math.gl/geospatial": "^3.5.1", + "@math.gl/web-mercator": "^3.5.1", + "@probe.gl/stats": "^3.4.0" + } + }, + "@loaders.gl/worker-utils": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@loaders.gl/worker-utils/-/worker-utils-3.0.8.tgz", + "integrity": "sha512-Pg72HuXPcL725TrOlOr83xloVUHj6OMWmno1dI8ccuqfOBsgoRjxNZrcSvwBzfK8tFCzuN2X30I+mHl3BkuYLw==", + "requires": { + "@babel/runtime": "^7.3.1" + } }, "@luma.gl/constants": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-7.3.2.tgz", - "integrity": "sha512-hr6JOOwsGPjjoHnil4sQ6AWsc8P6XXYtRL10TwNYfFTcNxrhSrjQvutYoCzXHH5U0vfHBfPMMUyLASK9FqiHOA==" + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/constants/-/constants-8.5.4.tgz", + "integrity": "sha512-lrA4ja92om/gDHYOvM9itL5S7FVzjKulyknDz6S+Y7gmgHgXk2ln1Xar5zUCsLnhAYx4glHITXGH5Y5rdWgT1Q==" }, "@luma.gl/core": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-7.3.2.tgz", - "integrity": "sha512-XyQPSUJRkZcc//gVX0AgjLLNTkCOO68NRnm7RkIhikRBEUdovb4IOcpmWMCB1/Gyj4hzg/Z1FOAVT4pG1E+agw==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/core/-/core-8.5.4.tgz", + "integrity": "sha512-+saDz1D3mcPd53vgbG60ryg1w5CF9Z2wdakKHzR810VoJLw97t4aNdg/eNgyWOvbOHxaKJBPm8K0sGjej67+jw==", "requires": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2", - "@luma.gl/shadertools": "7.3.2", - "@luma.gl/webgl": "7.3.2", - "@luma.gl/webgl-state-tracker": "7.3.2", - "@luma.gl/webgl2-polyfill": "7.3.2", - "math.gl": "^3.0.0", - "probe.gl": "^3.1.1", - "seer": "^0.2.4" - }, - "dependencies": { - "math.gl": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.4.1.tgz", - "integrity": "sha512-D33ZXryVFcHu1YJ+fgcNp2MkyK+mEfHesHMdQUZBz2hFqIsAwXovM1sJ+0rTcs8IyTFmuRJ2ayHf1igEJEOM2g==", - "requires": { - "@math.gl/core": "3.4.1" - } - } + "@luma.gl/constants": "8.5.4", + "@luma.gl/engine": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "@luma.gl/shadertools": "8.5.4", + "@luma.gl/webgl": "8.5.4" } }, - "@luma.gl/shadertools": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-7.3.2.tgz", - "integrity": "sha512-GiOZTvdEr164zYFy1DNRc7mzduSWLNJ34s+YbkJ/0i07E6tK7gHgM29QNCZ/gROvUDDJ5CHxngZqGkb+XquOMQ==", + "@luma.gl/engine": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/engine/-/engine-8.5.4.tgz", + "integrity": "sha512-Sfv972IzvR9s9kKWugs67XQUh9jC0e/PpBrzvyGVnPU4XvFq42RZVF73pzEklVU6AlpR8Zg5CPtxGdhyOHtT7w==", "requires": { "@babel/runtime": "^7.0.0", - "math.gl": "^3.0.0" - }, - "dependencies": { - "math.gl": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.4.1.tgz", - "integrity": "sha512-D33ZXryVFcHu1YJ+fgcNp2MkyK+mEfHesHMdQUZBz2hFqIsAwXovM1sJ+0rTcs8IyTFmuRJ2ayHf1igEJEOM2g==", - "requires": { - "@math.gl/core": "3.4.1" - } - } + "@luma.gl/constants": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "@luma.gl/shadertools": "8.5.4", + "@luma.gl/webgl": "8.5.4", + "@math.gl/core": "^3.5.0", + "probe.gl": "^3.4.0" } }, - "@luma.gl/webgl": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-7.3.2.tgz", - "integrity": "sha512-eWoPPRJOF5xSpqgggdwspsm8exclwxz20c8vqu8D1b3LJTY7cEpq57CMLvITHcJMMJ834TX/r598efTcF76lpw==", + "@luma.gl/experimental": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/experimental/-/experimental-8.5.4.tgz", + "integrity": "sha512-09waqRhgIrw+Sq0/in4tw4jPag5YsFfV1nEHJaLAg5RFv92S53IEubSJgkuG02HoOBkPxQ7KYvs9VNmriisnYg==", + "requires": { + "@luma.gl/constants": "8.5.4", + "@math.gl/core": "^3.5.0", + "earcut": "^2.0.6" + } + }, + "@luma.gl/gltools": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/gltools/-/gltools-8.5.4.tgz", + "integrity": "sha512-JotiPuymQz2Xc41AYlS2moJC/EHxU+OX/OMKi0+/MeOlEFLsdochgTA0I64j8yofLTXdeiGCneGtD1Ao8fk+bw==", "requires": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2", - "@luma.gl/webgl-state-tracker": "7.3.2", - "@luma.gl/webgl2-polyfill": "7.3.2", - "probe.gl": "^3.1.1" + "@luma.gl/constants": "8.5.4", + "probe.gl": "^3.4.0" } }, - "@luma.gl/webgl-state-tracker": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl-state-tracker/-/webgl-state-tracker-7.3.2.tgz", - "integrity": "sha512-0LuK3veReSm2UPOiDwC2CRDeE2xk4irqXdhyFO0WSAU1w+YhzbD1hGbjizGczvgfkbz8dFl9h98LbbH75efcKw==", + "@luma.gl/shadertools": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/shadertools/-/shadertools-8.5.4.tgz", + "integrity": "sha512-rwLBLrACi75aWnuJm8rVKCQnJR2sMTCxHuexfjHJ7Uecl0vVcVJZT7c9EnCFaz5LUTNbdupvuhq0SKNckKiKmw==", "requires": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2" + "@math.gl/core": "^3.5.0" } }, - "@luma.gl/webgl2-polyfill": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@luma.gl/webgl2-polyfill/-/webgl2-polyfill-7.3.2.tgz", - "integrity": "sha512-PMt5xqQ+u7tIqfUaL3s4nuWl604WFNcl1F1ohSUFeEzIIuxFiF6gsdEEvC5VqGoMFxI8T4FOTSeHYIr6uP4+4w==", + "@luma.gl/webgl": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@luma.gl/webgl/-/webgl-8.5.4.tgz", + "integrity": "sha512-dWy4dhTbtvDO9zQBdx1Yb+DxNx/1JWV9rhhJxJUtTKbGZSX0RjkASTT6GBWMl5jrH1JYJefS1wswHmmPVXjK0Q==", "requires": { "@babel/runtime": "^7.0.0", - "@luma.gl/constants": "7.3.2" + "@luma.gl/constants": "8.5.4", + "@luma.gl/gltools": "8.5.4", + "probe.gl": "^3.4.0" } }, "@mapbox/geojson-area": { @@ -60277,15 +55799,20 @@ "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==" }, + "@mapbox/martini": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/martini/-/martini-0.2.0.tgz", + "integrity": "sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ==" + }, "@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" }, "@mapbox/tiny-sdf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz", - "integrity": "sha512-Ihn1nZcGIswJ5XGbgFAvVumOgWpvIjBX9jiRlIl46uQG9vJOF51ViBYHF95rEZupuyQbEmhLaDPLQlU7fUTsBg==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", + "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==" }, "@mapbox/unitbezier": { "version": "0.0.0", @@ -60306,18 +55833,46 @@ "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" }, "@math.gl/core": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-3.4.1.tgz", - "integrity": "sha512-miAZL/WPU0B5hKrcg1K2nPU2GnOK6X84bwLoD0eTt2n7qT46ffh51Xu21V9kQp/cisE3l1ypukqSV/VHeaNxhQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/core/-/core-3.5.3.tgz", + "integrity": "sha512-TaSnvG0qFh1VxeNW5L58jSx0nJUMWMpUl6zo6Z3ScQzFySG5cicGOBzk/D40RkIZWPazCKCZ+ZThg5npSK9y3g==", "requires": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.0.0" } }, + "@math.gl/culling": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/culling/-/culling-3.5.3.tgz", + "integrity": "sha512-ABpAcrvoIOLSm1EUkwgDem4RfO28HWPBs/+taZ/ZSpJG6KiVPklpKU1NCK+05HuJStkpFZ+XlWtehWU6FAMCyA==", + "requires": { + "@babel/runtime": "^7.12.0", + "@math.gl/core": "3.5.3", + "gl-matrix": "^3.0.0" + } + }, + "@math.gl/geospatial": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/geospatial/-/geospatial-3.5.3.tgz", + "integrity": "sha512-cnc8VMQrt30JmlG200VDJmmvSjaGW57gY9KEZ+raapxyyFyfDNuAuIrIxe+zbK66FbvFWTbJlDaNmKqVG+ohyw==", + "requires": { + "@babel/runtime": "^7.12.0", + "@math.gl/core": "3.5.3", + "gl-matrix": "^3.0.0" + } + }, + "@math.gl/polygon": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/polygon/-/polygon-3.5.3.tgz", + "integrity": "sha512-VktscmyQg/Rd56nJk0Nj/UyvnPDbsnZNMWCdl3G5AYenYzLWy6h4FEWhLx8pD+Xw7VuFot8LR4WAK2TPzXzrWw==", + "requires": { + "@math.gl/core": "3.5.3" + } + }, "@math.gl/web-mercator": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.4.1.tgz", - "integrity": "sha512-5LAVmo5U25GY5YIxbI3D0J7r97B9AM5pAcWxnF9YhJx44DSVAYfMdiSISOfS+ivKuBFX44mFZvV9j75QY5aDkQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.5.3.tgz", + "integrity": "sha512-WZE9ALeTS4n3HDgkqTxcNLBU7DL0mjmPXSrcqSZIUeDY00+LCtNvMQWUAwqolpB7nD71vD6HLW8delzVuy4teA==", "requires": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.0.0" @@ -60389,15 +55944,6 @@ "unist-util-visit": "2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, "@babel/core": { "version": "7.12.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", @@ -60422,63 +55968,6 @@ "source-map": "^0.5.0" } }, - "@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true - }, "@babel/plugin-syntax-jsx": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", @@ -60488,54 +55977,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -60619,15 +56060,6 @@ "xtend": "^4.0.1" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "unified": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", @@ -60927,9 +56359,9 @@ "dev": true }, "@probe.gl/stats": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-3.3.0.tgz", - "integrity": "sha512-CV4c3EgallqZTO88u34/u9L5asL0nCVP1BEkb4qcXlh8Qz2Vmygbyjz1ViQsct6rSi2lJ52lo6W0PnlpZJJvcA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@probe.gl/stats/-/stats-3.4.0.tgz", + "integrity": "sha512-Gl37r9qGuiKadIvTZdSZvzCNOttJYw6RcY1oT0oDuB8r2uhuZAdSMQRQTy9FTinp6MY6O9wngGnV6EpQ8wSBAw==", "requires": { "@babel/runtime": "^7.0.0" } @@ -60961,6 +56393,11 @@ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" }, + "@react-icons/all-files": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@react-icons/all-files/-/all-files-4.1.0.tgz", + "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==" + }, "@seznam/compose-react-refs": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@seznam/compose-react-refs/-/compose-react-refs-1.0.4.tgz", @@ -61619,23 +57056,6 @@ "util-deprecate": "^1.0.2" }, "dependencies": { - "@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true - }, "@storybook/addons": { "version": "6.1.20", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.20.tgz", @@ -63208,41 +58628,12 @@ "webpack-virtual-modules": "^0.2.2" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, "@babel/compat-data": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.8.tgz", "integrity": "sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==", "dev": true }, - "@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, "@babel/helper-compilation-targets": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz", @@ -63255,114 +58646,12 @@ "semver": "^6.3.0" } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.8.tgz", - "integrity": "sha512-qioaRrKHQbn4hkRKDHbnuQ6kAxmmOF+kzKGnIfxPK4j2rckSJCpKzr/SSTlohSCiE3uAQpNDJ9FIh4baeE8W+w==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.13.0", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz", - "integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-replace-supers": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz", - "integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.0", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, "@babel/helper-validator-option": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "dev": true }, - "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true - }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", @@ -63387,16 +58676,6 @@ } } }, - "@babel/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" - } - }, "@babel/plugin-transform-classes": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", @@ -63421,34 +58700,6 @@ "@babel/helper-plugin-utils": "^7.13.0" } }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, "@storybook/addons": { "version": "6.1.20", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.20.tgz", @@ -63887,15 +59138,6 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, "ejs": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", @@ -64339,12 +59581,6 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -65229,78 +60465,6 @@ "fork-ts-checker-webpack-plugin": "^4.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "@storybook/node-logger": { "version": "5.3.21", "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-5.3.21.tgz", @@ -66285,23 +61449,22 @@ } }, "@superset-ui/chart-controls": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.53.tgz", - "integrity": "sha512-PjIDka4/lUwXUNEGjkQOIMwVWF2WfknqM6pKFNDPO0/nG4S4faQk96z/ABOXp8GYwIbBshnmmbmW4TCrCQ10Xw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/chart-controls/-/chart-controls-0.17.85.tgz", + "integrity": "sha512-RNk6za6IfcnAKoDFA8XpWgK5AdwPCEprKauv29Z+8VkHrMT8DkoEwDtgl0vutEmLRPECAL+lR76g5B9Z33iCJQ==", "requires": { - "@superset-ui/core": "0.17.53", + "@react-icons/all-files": "^4.1.0", + "@superset-ui/core": "0.17.81", "lodash": "^4.17.15", "prop-types": "^15.7.2" } }, "@superset-ui/core": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.53.tgz", - "integrity": "sha512-2bIRrK3Y+4ZSNu6drc1EzHTq6fO3aWfdjCh43ytju88nlADHheQXgwxEKnmjzI141qxiVL2+oSL2kC6pSTkW8A==", + "version": "0.17.81", + "resolved": "https://registry.npmjs.org/@superset-ui/core/-/core-0.17.81.tgz", + "integrity": "sha512-CaDihqqwFnqtO9yWHxPbZh3MKTot66Zd27ifdIYQCVEMQwz5OpP6zUbULekq7NAbPMHwf7Ju3FOzN0uEzWwSmw==", "requires": { "@babel/runtime": "^7.1.2", - "@emotion/cache": "^11.1.3", - "@emotion/react": "^11.1.5", "@emotion/styled": "^11.3.0", "@types/d3-format": "^1.3.0", "@types/d3-interpolate": "^1.3.1", @@ -66395,26 +61558,36 @@ } }, "d3-scale": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.4.tgz", - "integrity": "sha512-PG6gtpbPCFqKbvdBEswQcJcTzHC8VEd/XzezF5e68KlkT4/ggELw/nR1tv863jY6ufKTvDlzCMZvhe06codbbA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", "requires": { "d3-array": "^2.3.0", "d3-format": "1 - 2", "d3-interpolate": "1.2.0 - 2", - "d3-time": "1 - 2", + "d3-time": "^2.1.1", "d3-time-format": "2 - 3" + }, + "dependencies": { + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + } } } } }, "@superset-ui/legacy-plugin-chart-calendar": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.53.tgz", - "integrity": "sha512-NLevYzzhQyRgP+vdEfhJyDxJIBbGM/bJTJfFw1iRllny3WQax6iU/X5hUw/iWZqruVNkwSnUA39+EGcjU1aIjg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-calendar/-/legacy-plugin-chart-calendar-0.17.85.tgz", + "integrity": "sha512-bmPvflt73ncje+22YTDTkPGIbfylPcqksiCB74/dh/cjUy39lCEpBdL1iYYee6z2vmFdn6uNWuHtpENDKTG83g==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-selection": "^1.4.0", "d3-tip": "^0.9.1", @@ -66432,24 +61605,24 @@ } }, "@superset-ui/legacy-plugin-chart-chord": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.53.tgz", - "integrity": "sha512-a3Y8b/1nSuFvzEzUDTVVmad5/YjTBhz0qU2rcVGrdKp2kzuSVXVVljdN7KVisDUNHhYqrttLM8RQrqGw9f7x1A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-chord/-/legacy-plugin-chart-chord-0.17.85.tgz", + "integrity": "sha512-raQUDM3Dgcld7UIvXe9XGr4NEJV6x8J+gjVUQLlkvsttA32VN+J1tUhevxmT2eMMxifaaLnGWiBA678Q2ENVfg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2", "react": "^16.13.1" } }, "@superset-ui/legacy-plugin-chart-country-map": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.53.tgz", - "integrity": "sha512-zTImQdeBT8raXnxafBIHvaVqOqKoECfyDwgFlPKhs4M7EXPG7U8/VLg0Oi2dCA7/SFZA/ASrJwc/KxW399vJhw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-country-map/-/legacy-plugin-chart-country-map-0.17.85.tgz", + "integrity": "sha512-g8OAwInPI9r/k/0ip9pXvQR7L6PInQhN8KfMb4j5VgC4+eUl04BQDpsclVoYOz5KqthkxPvGhdlOH0zHybv3Vg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.0.3", "prop-types": "^15.6.2" @@ -66466,34 +61639,34 @@ } }, "@superset-ui/legacy-plugin-chart-event-flow": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.53.tgz", - "integrity": "sha512-QYL0Feyfu7ZH1GeQ9sfEaEgnW2IQG93sJnM29NO53CjSvdbbZItfU9v6xVnAo6jMwcam7JLNYRtuIPgJevNThw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-event-flow/-/legacy-plugin-chart-event-flow-0.17.85.tgz", + "integrity": "sha512-O2DiLUO0lkkpemtjwJ845veW4vFwZUkb13TkfV1ef37HBDDdEJkUa6Ls8zh7C4aybqn4HG4mr32IHg3DV2WgYg==", "requires": { "@data-ui/event-flow": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-force-directed": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.53.tgz", - "integrity": "sha512-F7hkrBxC7EWrClQ1jb7anzj1SmIjqXVMz2JKhzwEUk++Tafnn0mrB7Yo51u3twFFOY5bwn+KcI1NObzBRkXguQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-force-directed/-/legacy-plugin-chart-force-directed-0.17.85.tgz", + "integrity": "sha512-8QB2IPNadJ5pha2PPs0AQe5zHEnpaIcTY+JGH8LFxUFtuDLjGSuGJJNbIyM/QT63zMp0WCn5VCwgeMuKnuU9Xw==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-heatmap": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.53.tgz", - "integrity": "sha512-NXx/E3AiTxkL+qwaj8B0IDrhWo6P5u5EuXXx1xaWqMTH18YomyeA9l4NBPwsjCfhAMqrEeT0hzeY2/WSoPq5KQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-heatmap/-/legacy-plugin-chart-heatmap-0.17.85.tgz", + "integrity": "sha512-zuxkDDTxxVtDkg9iu7GtcNuG9URh9GmNRM5lH2wsOdKD/UTlZrkt0Lf4tp5kKycknyHWdWFixe1CSLaLzuwwOA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-svg-legend": "^1.x", "d3-tip": "^0.9.1", @@ -66501,14 +61674,14 @@ } }, "@superset-ui/legacy-plugin-chart-histogram": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.53.tgz", - "integrity": "sha512-EQ/VvG+qCec+IqnwYHA90iHAjkhnPNGkKbTuKlsRyL3ONfxg3n6L4EQOlAA0HvELKkFAZXBxh8TA8Qc3j+g4Fw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-histogram/-/legacy-plugin-chart-histogram-0.17.85.tgz", + "integrity": "sha512-dqrTTDeMvdm4Lgb7Cav5lm86GgiQxYda1yXWoXFlqXvwZt40EJqCxYUAAHYKD5JmlqjzIn1wuvCHzJQsDxANDQ==", "requires": { "@data-ui/histogram": "^0.0.84", "@data-ui/theme": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@vx/legend": "^0.0.198", "@vx/responsive": "^0.0.199", "@vx/scale": "^0.0.197", @@ -66576,12 +61749,12 @@ } }, "@superset-ui/legacy-plugin-chart-horizon": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.53.tgz", - "integrity": "sha512-LsM4HOuOkiabRNxMUjjietbFx99admne59Mm5zQdsRPNEpN/EKEWu8R4G4crSSqxxzD9KVnveRPE7OD0n91k/A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-horizon/-/legacy-plugin-chart-horizon-0.17.85.tgz", + "integrity": "sha512-lVzXqqLFgZ80I/fDbf2TT3cXCEW+xYKvna4Ldod/dUDFWjVIy3rzP7HicHsS7XCLPpeUHgZUFZhx8NEvTWhDqg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-array": "^2.0.3", "d3-scale": "^3.0.1", "prop-types": "^15.6.2" @@ -66610,12 +61783,12 @@ } }, "@superset-ui/legacy-plugin-chart-map-box": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.53.tgz", - "integrity": "sha512-JuM77arnxECuSiHkdLMry4JruuVTAfTKTtR8F4qGOpiYiXzGEv4K+y12eqBe1o94ckJF43Esz9e1fdPLDkjqTw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-map-box/-/legacy-plugin-chart-map-box-0.17.85.tgz", + "integrity": "sha512-YJIZjcfpMDaJtve6EfEr1lUgCGnsutTxyKEOj3b6wjYMfoLhSUproDoW9TUQtBR3tFrY3PrLrZg+O41L9grDIA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "immutable": "^3.8.2", "mapbox-gl": "^0.53.0", "prop-types": "^15.6.2", @@ -66632,118 +61805,118 @@ } }, "@superset-ui/legacy-plugin-chart-paired-t-test": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.53.tgz", - "integrity": "sha512-QkRVm0XGoOxqOX0nRvHnGon2gG8MmV+dbBBpmPkmspxCWKrn183Wzq5SiMlM4vgo2HaroWUIPuBgLBd7rYZtGw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-paired-t-test/-/legacy-plugin-chart-paired-t-test-0.17.85.tgz", + "integrity": "sha512-JIOvvGUlVzbk2zWyQMD8WE1UN95HdOuM1I7py5LIEqJH3CwWN5SGmtDG50DAvKAhEt5InmdQkm7ihr0Uw2LoQw==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "distributions": "^1.0.0", "prop-types": "^15.6.2", "reactable": "^1.1.0" } }, "@superset-ui/legacy-plugin-chart-parallel-coordinates": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.53.tgz", - "integrity": "sha512-NcwuEd+rXfmwPshPby0jEgnJnbYfKruM7l0Hb3lIw6iMTc1IV21d1CMftQPvYYdwagam0FapBO2YcSvnvj2rDw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-parallel-coordinates/-/legacy-plugin-chart-parallel-coordinates-0.17.85.tgz", + "integrity": "sha512-ecSKIBL7806jD+sQu6ub+92tI8W1OfLZWMUW7SeupDphlDJkrQEIQ63FQOCwWEAI9xJhn+hoxIe6M2Y9Um0FwQ==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.7.2" } }, "@superset-ui/legacy-plugin-chart-partition": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.53.tgz", - "integrity": "sha512-CTzKjaKCdT/+bFlXUDD4nXC2CO7mXmIPJ2K/M94rY2G2gdAWRZJ1i2HlcvTP+RY/AItzZm3C+E7hYdAQ6toBkA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-partition/-/legacy-plugin-chart-partition-0.17.85.tgz", + "integrity": "sha512-seF8oZ5lRdrqGzQBDqcQIrIV9QHp6IH7e9+pyG0F4BzSY8WNKIjFVkd92HcNcm/KeqkzID1mqGv1aCzlDQNaiw==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-hierarchy": "^1.1.8", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-pivot-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.53.tgz", - "integrity": "sha512-bk7mttnZFGgGmWCfj0kO++65XsMNyQJch0dgfRRnLVTlSnY89/kGqszTKybbCZhsbx4T5bJ+bn6hZKAGH+FnUA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-oXBXgzi4uBKG1FsOd9ZFIhPQvaBIYanzHdqwthafsRDG9kgKL9b//+MCIZgRlqlSJK+vsf4EMxXucFKJtoLDuA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "datatables.net-bs": "^1.10.15", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-rose": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.53.tgz", - "integrity": "sha512-ppvQuKAS0rMhniKenLXSKczmAsHX4igYc0bVZAvfFDmLNW3tnlmivL+zYSw/sQ9PAhjMGDbTBlSio1oJ+91wiA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.17.85.tgz", + "integrity": "sha512-NilJNxC49YM+j7WqGmna/+G1m0/VXb1hoj2Z2PXPy8KYGMKrLQAwNE0ce7yBnQqSljvxq5Memfjqzqjoio5aoA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "nvd3": "1.8.6", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sankey": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.53.tgz", - "integrity": "sha512-3tvMghg5WUAq40su8cZrjJHoc/TsK1WWx6UFu+j2mPOh/BJJZb8wh7A63X82ubLdyzEqdjxsEs9pzZWzs7kUHw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey/-/legacy-plugin-chart-sankey-0.17.85.tgz", + "integrity": "sha512-MV47pYL5QkVAT8rfYrLT9joJF8a48mLiS3suVT+u4vHv3rrsWNTa00irD1HqNoWLDCuZr+U7pCt+UbW/0w2yGw==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-sankey": "^0.4.2", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sankey-loop": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.53.tgz", - "integrity": "sha512-60aGflqOi5+XDE3BR/p+Pw0xVp7OHsjwroX77CwkwBtFkw1AFVWczaTJH6CYeeCJZXCLYjrbc5OFMuaxIJ+j+Q==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sankey-loop/-/legacy-plugin-chart-sankey-loop-0.17.85.tgz", + "integrity": "sha512-z3I/NCQa64LXKiel+AswpdnUuhsS/0hiziY+T+BuN2geeVrWC+LMQya1hMDg+dDR3RgpkKqgpI9lEf1KTuhHaQ==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-sankey-diagram": "^0.7.3", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-sunburst": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.53.tgz", - "integrity": "sha512-t0z7XPsDtDpnZ+fIpn57w9Vi3oWQ7ximDdjmag1WGhC6+dwR3XxEpNcicI6P6xfNX078RT8Iz89PZQBtagAAkA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-sunburst/-/legacy-plugin-chart-sunburst-0.17.85.tgz", + "integrity": "sha512-L6rRXhEasNQpFN9SQ0ZB6qfa94XIVlCxyEmy8DOR2N39nlLncCweiHtgvgS4Su9H86nidZ/6ryNZ4fKxmm2mBg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-treemap": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.53.tgz", - "integrity": "sha512-LV16Qwiz7ahfhCmuWIGk6f54KpdRJDAyLtr/ifFi8a2AcoG27Lf7hZZ3mCI9Jl5X6c7LLBmvAHfxdbBnLGa8+g==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.17.85.tgz", + "integrity": "sha512-X4HVCOuhe45qaqUzTfjrKxAr9RoSbdwmWlCWLK2djVJDAFYGqhTxcpNEXxSv+cMqpyMBLhwXqZ5SMV5u0Gu+Eg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3-hierarchy": "^1.1.8", "d3-selection": "^1.4.0", "prop-types": "^15.6.2" } }, "@superset-ui/legacy-plugin-chart-world-map": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.53.tgz", - "integrity": "sha512-gnDBTyWPctqucyQzAObH6N+3f9GUQq9qpQ4cNbtvpIoVgXowYA5Q5dIfXBPnq525t78o3eiWqclTYf2Xcd62Kw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.17.85.tgz", + "integrity": "sha512-bxO1/tY2FI1MzfircbjwuwbLzI+jg2puIlKFek6vkbQuxA9+gCtxKD0SLOlxadNQXW4kUrU2DxhfPlr6th55bA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-array": "^2.4.0", "d3-color": "^1.4.1", @@ -66758,22 +61931,17 @@ "requires": { "internmap": "^1.0.0" } - }, - "d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" } } }, "@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.53.tgz", - "integrity": "sha512-HUlE6IZUjFvPMiXCj1cdRiR4avFLVhT5qwIQZk1l30kycl8/73rTm37Y/syBfZMPrfCrIW3nyReqfcnAaNqw9g==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-color": "^1.2.2", "@types/shortid": "^0.0.29", "d3-color": "^1.2.3", @@ -66781,38 +61949,160 @@ } }, "@superset-ui/legacy-preset-chart-deckgl": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.7.tgz", - "integrity": "sha512-TaAX1PlZ5DhsNelgoOjCfPBlFtHZDFcozJEIAV2qXzXUo6rfIgskqIq4X3VbMuYnngZw5of4hAtOH1+Tgv+Wmw==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-deckgl/-/legacy-preset-chart-deckgl-0.4.11.tgz", + "integrity": "sha512-N4l8zavJ3kQUyoPFqG6zXQT8EELFcXtD/GNRy3aJzSgHObRhK8+aqFaWIn3ncQrBuEiUzGDnHLNni/LRAxz7vA==", "requires": { "@math.gl/web-mercator": "^3.2.2", "@types/d3-array": "^2.0.0", "bootstrap-slider": "^10.0.0", "d3-array": "^1.2.4", - "d3-color": "^1.2.0", - "d3-scale": "^2.1.2", - "deck.gl": "7.1.11", + "d3-color": "^1.4.1", + "d3-scale": "^3.0.0", + "deck.gl": "8.5.2", "jquery": "^3.4.1", "lodash": "^4.17.15", - "mapbox-gl": "^0.53.0", + "mapbox-gl": "^2.4.0", "moment": "^2.20.1", "mousetrap": "^1.6.1", "prop-types": "^15.6.0", "react-bootstrap-slider": "2.1.5", - "react-map-gl": "^4.0.10", + "react-map-gl": "^6.1.16", "underscore": "^1.8.3", "urijs": "^1.18.10", "xss": "^1.0.6" + }, + "dependencies": { + "@mapbox/geojson-rewind": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz", + "integrity": "sha512-eL7fMmfTBKjrb+VFHXCGv9Ot0zc3C0U+CwXo1IrP+EPwDczLoXv34Tgq3y+2mPSFNVUXgU42ILWJTC7145KPTA==", + "requires": { + "get-stream": "^6.0.1", + "minimist": "^1.2.5" + } + }, + "@mapbox/mapbox-gl-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.0.tgz", + "integrity": "sha512-zu4udqYiBrKMQKwpKJ4hhPON7tz0QR/JZ3iGpHnNWFmH3Sv/ysxlICATUtGCFpsyJf2v1WpFhlzaZ3GhhKmPMA==" + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + }, + "dependencies": { + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + } + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + }, + "dependencies": { + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + } + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "mapbox-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.4.0.tgz", + "integrity": "sha512-oH5fkh209U2Zqvgs1bBS+SQVhrj8rUT9OTgZmg+20GaNthDJFYDCXvGidVAkgacuCHSIALTZKzMV1DFgO+isFQ==", + "requires": { + "@mapbox/geojson-rewind": "^0.5.0", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.2.5", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.3.0", + "grid-index": "^1.1.0", + "minimist": "^1.2.5", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.3", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + } + }, + "react-map-gl": { + "version": "6.1.16", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-6.1.16.tgz", + "integrity": "sha512-d/4kFMMh2hDeZNeQOUm2wC1/as9q93EZiDmM5mGBx0LIch+9pTFgO6ZINIuUD9Zz4JqWGpthyjoKr3QKgrGiRA==", + "requires": { + "@babel/runtime": "^7.0.0", + "@types/geojson": "^7946.0.7", + "@types/mapbox-gl": "^2.0.3", + "mapbox-gl": "^2.3.0", + "mjolnir.js": "^2.5.0", + "prop-types": "^15.7.2", + "resize-observer-polyfill": "^1.5.1", + "viewport-mercator-project": "^7.0.3" + } + }, + "supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-7+bR4FbF5SYsmkHfDp61QiwCKtwNDyPsddk9TzfsDA5DQr5Goii5CVD2SXjglweFCxjrzVZf945ahqYfUIk8UA==", + "requires": { + "kdbush": "^3.0.0" + } + }, + "viewport-mercator-project": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.3.tgz", + "integrity": "sha512-5nSgVK8jKTSKzOvsa8TSSd2IeQCpHfSNiBOOOMQLvzlxgWD0YoF4xRmyZio3GaLtKSE+50UB892X3R1SAMbaww==", + "requires": { + "@math.gl/web-mercator": "^3.4.3" + } + } } }, "@superset-ui/legacy-preset-chart-nvd3": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.53.tgz", - "integrity": "sha512-wTbQRCZDrnb16tLJzXYbIiCFbHddRJ3fo5DKsbv6MFNrfOLWWx1SjAZ5C60e57u33XpKdTE5jGpEuGdq7BZ55w==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.17.85.tgz", + "integrity": "sha512-KoC8kIqP6zwtGzXBUj6yMaF3Cz2TXygAe7ZCG5XeavgrjgKsWqOXAdQzJ9oBrCPXPQBC5hF7TruBB/Xd839TDA==", "requires": { "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "d3": "^3.5.17", "d3-tip": "^0.9.1", "dompurify": "^2.0.6", @@ -66826,36 +62116,37 @@ } }, "@superset-ui/plugin-chart-echarts": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.53.tgz", - "integrity": "sha512-XXKqhr2CwZfi02qW55d9SQnNmdewTsAJT6xePBjci0SXAZRmi/T8vRbq2OCDJ7mQ0de7kjVBydAuOEEU/Y554A==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-echarts/-/plugin-chart-echarts-0.17.85.tgz", + "integrity": "sha512-dAqRzVVm7Shn2fgKoj2puX0jefe2r14dOQThJ8CG/hRlpmXsKmLnSywwLvQVxq1oUhh+uQYsemdYBCsrQAuxcg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/mathjs": "^6.0.7", "d3-array": "^1.2.0", - "echarts": "^5.1.1", + "echarts": "^5.1.2", "lodash": "^4.17.15", "mathjs": "^8.0.1" } }, "@superset-ui/plugin-chart-pivot-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.53.tgz", - "integrity": "sha512-18CTaM1sRgK5laFwHlKV+1A7+l9YWwPAvb7XrMjS8CQq0T2aEqNSQm7KWByG+LEj2x86idM8gaWghNes27yVtQ==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-pivot-table/-/plugin-chart-pivot-table-0.17.85.tgz", + "integrity": "sha512-tTU79c/C7g5sdZxRNVcl6QFbwqhsv0Bi/XeYOwhTNAe7qf7LmkvwQB78fvh7WW+U2FBltkhHmOxOb/HIJscXVg==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", - "@superset-ui/react-pivottable": "^0.12.8" + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", + "@superset-ui/react-pivottable": "^0.12.12" } }, "@superset-ui/plugin-chart-table": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.53.tgz", - "integrity": "sha512-PSeL/zQSTvQyztjUMMm4U4G6oEM3xk3wkC4HTpuLEpjQ7qyGme39M1JeCGvNG4pPZRm0nO4pU+0U/36oR0lAjw==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.17.85.tgz", + "integrity": "sha512-RGeIGAQuiiOy6JgpnQC+rtVDWR8jGZF1DOER+WkgbfTeKiRmVGyo7wOSb7dvQlsaitUg2Y30962arOGeEcgvpA==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@react-icons/all-files": "^4.1.0", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-array": "^2.9.0", "@types/react-table": "^7.0.29", "d3-array": "^2.4.0", @@ -66877,12 +62168,12 @@ } }, "@superset-ui/plugin-chart-word-cloud": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.53.tgz", - "integrity": "sha512-lQTr9UpeoGgX1B0SkjrvtL0zjgYVoJbm6RVv8ELG+efCG1oYAoIVgw2sahJI4zLqNiHcNeWqHUcu7NK06uc4mA==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-word-cloud/-/plugin-chart-word-cloud-0.17.85.tgz", + "integrity": "sha512-nCbmndNxUoHWkYRCARD1C4XjzxXtk/OkCUVdB3EKaPNWbi0QuQHUuAZUh3ROIewxd3KAMoSUYRIjTVYDbetnJQ==", "requires": { - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@types/d3-cloud": "^1.2.1", "@types/d3-scale": "^2.0.2", "d3-cloud": "^1.2.5", @@ -66913,14 +62204,14 @@ } }, "@superset-ui/preset-chart-xy": { - "version": "0.17.53", - "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.53.tgz", - "integrity": "sha512-nmqif4Zd7Tdx4hLoDiiRiNFUFn1kliumjp9RQK68eMaefWcl1vTMT7nPmyFvgUH5390HJygpC3up50+j5Bngkg==", + "version": "0.17.85", + "resolved": "https://registry.npmjs.org/@superset-ui/preset-chart-xy/-/preset-chart-xy-0.17.85.tgz", + "integrity": "sha512-Cd2Ji1trP6aHuOfHK0GDza8iQUVkJNbtIFSReb240ViewICH/7mtsrj8jLAvf3tyOORLD3AW2j8s68UDCrBi6g==", "requires": { "@data-ui/theme": "^0.0.84", "@data-ui/xy-chart": "^0.0.84", - "@superset-ui/chart-controls": "0.17.53", - "@superset-ui/core": "0.17.53", + "@superset-ui/chart-controls": "0.17.85", + "@superset-ui/core": "0.17.81", "@vx/axis": "^0.0.198", "@vx/legend": "^0.0.198", "@vx/scale": "^0.0.197", @@ -67039,9 +62330,9 @@ } }, "@superset-ui/react-pivottable": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.8.tgz", - "integrity": "sha512-7DRxX/w1uSQE1pibSe64t1o+fmiP7ZWT2FJkjK510bSJm8NUIPCXtmpK+NKtNZuCteE9sqE7bQxd54SSq2xWKw==", + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@superset-ui/react-pivottable/-/react-pivottable-0.12.12.tgz", + "integrity": "sha512-4+wx2kQy3IRKoWHTf2bIkXjlzDA0u/eN2k0FfLfJ5bdER2GuqZErWuKtiZzARsn5kSS9hPIrvt77uv52R3FnfQ==", "requires": { "immutability-helper": "^3.1.1", "prop-types": "^15.7.2", @@ -67093,15 +62384,15 @@ "dev": true }, "@svgr/babel-plugin-transform-svg-component": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", - "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", "dev": true }, "@svgr/babel-preset": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", - "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", "dev": true, "requires": { "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", @@ -67111,43 +62402,43 @@ "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", - "@svgr/babel-plugin-transform-svg-component": "^5.4.0" + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" } }, "@svgr/core": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", - "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", "dev": true, "requires": { - "@svgr/plugin-jsx": "^5.4.0", - "camelcase": "^6.0.0", - "cosmiconfig": "^6.0.0" + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" }, "dependencies": { "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" } }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -67155,14 +62446,14 @@ } }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -67175,54 +62466,60 @@ } }, "@svgr/hast-util-to-babel-ast": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", - "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", "dev": true, "requires": { - "@babel/types": "^7.9.5" + "@babel/types": "^7.12.6" } }, "@svgr/plugin-jsx": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", - "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", "dev": true, "requires": { - "@babel/core": "^7.7.5", - "@svgr/babel-preset": "^5.4.0", - "@svgr/hast-util-to-babel-ast": "^5.4.0", + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", "svg-parser": "^2.0.2" } }, "@svgr/plugin-svgo": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", - "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", "dev": true, "requires": { - "cosmiconfig": "^6.0.0", - "merge-deep": "^3.0.2", + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", "svgo": "^1.2.2" }, "dependencies": { "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" } }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -67230,14 +62527,14 @@ } }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -67250,735 +62547,21 @@ } }, "@svgr/webpack": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", - "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", "dev": true, "requires": { - "@babel/core": "^7.9.0", - "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/preset-env": "^7.9.5", - "@babel/preset-react": "^7.9.4", - "@svgr/core": "^5.4.0", - "@svgr/plugin-jsx": "^5.4.0", - "@svgr/plugin-svgo": "^5.4.0", + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", "loader-utils": "^2.0.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } - }, - "@babel/compat-data": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", - "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.2.tgz", - "integrity": "sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.2", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helpers": "^7.10.1", - "@babel/parser": "^7.10.2", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.1.tgz", - "integrity": "sha512-KXzzpyWhXgzjXIlJU1ZjIXzUPdej1suE6vzqgImZ/cpAsR/CC8gUcX4EWRmDfWz/cs6HOCPMBIJ3nKoXt3BFuw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.1", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", - "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-regex": "^7.10.1", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", - "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-module-transforms": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", - "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" - } - }, - "@babel/helper-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", - "dev": true, - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", - "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", - "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-wrap-function": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", - "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", - "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", - "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", - "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", - "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.10.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.1.tgz", - "integrity": "sha512-rBjKcVwjk26H3VX8pavMxGf33LNlbocMHdSeldIEswtQ/hrjyTG8fKKILW1cSkODyRovckN/uZlGb2+sAV9JUQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.1.tgz", - "integrity": "sha512-MBVworWiSRBap3Vs39eHt+6pJuLUAaK4oxGc8g+wY+vuSJvLiEQjW1LSTqKb8OUPtDvHCkdPhk7d6sjC19xyFw==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx": "^7.10.1", - "@babel/helper-builder-react-jsx-experimental": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.1.tgz", - "integrity": "sha512-4p+RBw9d1qV4S749J42ZooeQaBomFPrSxa9JONLHJ1TxCBo3TzJ79vtmG2S2erUT8PDDrPdw4ZbXGr2/1+dILA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.1.tgz", - "integrity": "sha512-neAbaKkoiL+LXYbGDvh6PjPG+YeA67OsZlE78u50xbWh2L1/C81uHiNP5d1fw+uqUIoiNdCC8ZB+G4Zh3hShJA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-syntax-jsx": "^7.10.1" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", - "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-regex": "^7.10.1" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/preset-env": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz", - "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.10.1", - "@babel/helper-compilation-targets": "^7.10.2", - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-proposal-async-generator-functions": "^7.10.1", - "@babel/plugin-proposal-class-properties": "^7.10.1", - "@babel/plugin-proposal-dynamic-import": "^7.10.1", - "@babel/plugin-proposal-json-strings": "^7.10.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", - "@babel/plugin-proposal-numeric-separator": "^7.10.1", - "@babel/plugin-proposal-object-rest-spread": "^7.10.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", - "@babel/plugin-proposal-optional-chaining": "^7.10.1", - "@babel/plugin-proposal-private-methods": "^7.10.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.1", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.1", - "@babel/plugin-transform-arrow-functions": "^7.10.1", - "@babel/plugin-transform-async-to-generator": "^7.10.1", - "@babel/plugin-transform-block-scoped-functions": "^7.10.1", - "@babel/plugin-transform-block-scoping": "^7.10.1", - "@babel/plugin-transform-classes": "^7.10.1", - "@babel/plugin-transform-computed-properties": "^7.10.1", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-dotall-regex": "^7.10.1", - "@babel/plugin-transform-duplicate-keys": "^7.10.1", - "@babel/plugin-transform-exponentiation-operator": "^7.10.1", - "@babel/plugin-transform-for-of": "^7.10.1", - "@babel/plugin-transform-function-name": "^7.10.1", - "@babel/plugin-transform-literals": "^7.10.1", - "@babel/plugin-transform-member-expression-literals": "^7.10.1", - "@babel/plugin-transform-modules-amd": "^7.10.1", - "@babel/plugin-transform-modules-commonjs": "^7.10.1", - "@babel/plugin-transform-modules-systemjs": "^7.10.1", - "@babel/plugin-transform-modules-umd": "^7.10.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.10.1", - "@babel/plugin-transform-object-super": "^7.10.1", - "@babel/plugin-transform-parameters": "^7.10.1", - "@babel/plugin-transform-property-literals": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.1", - "@babel/plugin-transform-reserved-words": "^7.10.1", - "@babel/plugin-transform-shorthand-properties": "^7.10.1", - "@babel/plugin-transform-spread": "^7.10.1", - "@babel/plugin-transform-sticky-regex": "^7.10.1", - "@babel/plugin-transform-template-literals": "^7.10.1", - "@babel/plugin-transform-typeof-symbol": "^7.10.1", - "@babel/plugin-transform-unicode-escapes": "^7.10.1", - "@babel/plugin-transform-unicode-regex": "^7.10.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.2", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/preset-react": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.1.tgz", - "integrity": "sha512-Rw0SxQ7VKhObmFjD/cUcKhPTtzpeviEFX1E6PgP+cYOhQ98icNqtINNFANlsdbQHrmeWnqdxA4Tmnl1jy5tp3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/plugin-transform-react-display-name": "^7.10.1", - "@babel/plugin-transform-react-jsx": "^7.10.1", - "@babel/plugin-transform-react-jsx-development": "^7.10.1", - "@babel/plugin-transform-react-jsx-self": "^7.10.1", - "@babel/plugin-transform-react-jsx-source": "^7.10.1", - "@babel/plugin-transform-react-pure-annotations": "^7.10.1" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001084", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz", - "integrity": "sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "electron-to-chromium": { - "version": "1.3.474", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz", - "integrity": "sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw==", - "dev": true - }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -68004,64 +62587,6 @@ "emojis-list": "^3.0.0", "json5": "^2.1.2" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node-releases": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", - "dev": true - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -68081,48 +62606,6 @@ "pretty-format": "^26.6.2" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -68173,15 +62656,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -68486,6 +62960,16 @@ "integrity": "sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow==", "dev": true }, + "@types/flatbuffers": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@types/flatbuffers/-/flatbuffers-1.10.0.tgz", + "integrity": "sha512-7btbphLrKvo5yl/5CC2OCxUSMx1wV1wvGT1qDXkSt7yi00/YW7E8k6qzXqJHsp+WU0eoG7r6MTQQXI9lIvd0qA==" + }, + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -68627,6 +63111,14 @@ "@types/lodash": "*" } }, + "@types/mapbox-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.4.0.tgz", + "integrity": "sha512-Na5vXw6Ez0L5To/+pL78dWPNoG6QlPdEDdnkSmIL5HWxemD+s0pTmTWDbMj7tcqJ2hnVyOyukVIveR9HPi7eeA==", + "requires": { + "@types/geojson": "*" + } + }, "@types/markdown-to-jsx": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.3.tgz", @@ -68669,9 +63161,9 @@ "dev": true }, "@types/node": { - "version": "10.12.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", - "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", + "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==" }, "@types/node-fetch": { "version": "2.5.8", @@ -69019,9 +63511,9 @@ "integrity": "sha512-mE3eRK0fpTN/GnNBOIg2tGq2cFhchQXF6fCbrLxus75TgnoOECbdHikr948FGO/UAml7/ZhLMa5FbGkF5PKvmw==" }, "@types/seedrandom": { - "version": "2.4.29", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.29.tgz", - "integrity": "sha512-nSjRNBaE9y5Zzv6CCwDWH6DVC0E+g8/1Nu7LOSntusxQaHZOv9GJ7rrnS5G1dSGd2k1Ovvg5+JJG6idjuGtpDA==" + "version": "2.4.30", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz", + "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==" }, "@types/shortid": { "version": "0.0.29", @@ -69050,9 +63542,9 @@ "dev": true }, "@types/sortablejs": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.6.tgz", - "integrity": "sha512-QRz8Z+uw2Y4Gwrtxw8hD782zzuxxugdcq8X/FkPsXUa1kfslhGzy13+4HugO9FXNo+jlWVcE6DYmmegniIQ30A==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.7.tgz", + "integrity": "sha512-lGCwwgpj8zW/ZmaueoPVSP7nnc9t8VqVWXS+ASX3eoUUENmiazv0rlXyTRludXzuX9ALjPsMqBu85TgJNWbTOg==", "peer": true }, "@types/source-list-map": { @@ -69080,6 +63572,11 @@ "@types/jest": "*" } }, + "@types/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ==" + }, "@types/uglify-js": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", @@ -70558,6 +65055,35 @@ "normalize-path": "^2.1.1" } }, + "apache-arrow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-4.0.1.tgz", + "integrity": "sha512-DyF7GXCbSjsw4P5C8b+qW7OnJKa6w9mJI0mhV0+EfZbVZCmhfiF6ffqcnrI/kzBrRqn9hH/Ft9n5+m4DTbBJpg==", + "requires": { + "@types/flatbuffers": "^1.10.0", + "@types/node": "^14.14.37", + "@types/text-encoding-utf-8": "^1.0.1", + "command-line-args": "5.1.1", + "command-line-usage": "6.1.1", + "flatbuffers": "1.12.0", + "json-bignum": "^0.0.3", + "pad-left": "^2.1.0", + "text-encoding-utf-8": "^1.0.2", + "tslib": "^2.2.0" + }, + "dependencies": { + "@types/node": { + "version": "14.17.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.9.tgz", + "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==" + }, + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } + }, "aphrodite": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/aphrodite/-/aphrodite-1.2.5.tgz", @@ -70638,6 +65164,11 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" + }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -73047,6 +67578,14 @@ "redeyed": "~0.4.0" } }, + "cartocolor": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cartocolor/-/cartocolor-4.0.2.tgz", + "integrity": "sha512-+Gh9mb6lFxsDOLQlBLPxAHCnWXlg2W8q3AcVwqRcy95TdBbcOU89Wrb6h2Hd/6Ww1Kc1pzXmUdpnWD+xeCG0dg==", + "requires": { + "colorbrewer": "1.0.0" + } + }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -73181,25 +67720,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "bundled": true, "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73209,13 +67748,13 @@ }, "balanced-match": { "version": "1.0.0", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "bundled": true, "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73223,38 +67762,39 @@ "concat-map": "0.0.1" } }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "bundled": true, "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "bundled": true, "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "bundled": true, "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73263,35 +67803,40 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "bundled": true, "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, "dev": true, "optional": true }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73307,8 +67852,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73322,15 +67866,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73339,8 +67881,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73349,8 +67890,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73360,15 +67900,19 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73377,15 +67921,13 @@ }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73393,22 +67935,48 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "0.0.8", + "bundled": true, "dev": true, "optional": true }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "bundled": true, "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73419,7 +67987,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73437,7 +68005,7 @@ }, "nopt": { "version": "4.0.1", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73447,15 +68015,13 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "bundled": true, "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73465,8 +68031,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73478,22 +68043,19 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "bundled": true, "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73502,22 +68064,19 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73527,21 +68086,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73549,12 +68106,19 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } } }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73569,7 +68133,7 @@ }, "rimraf": { "version": "2.6.3", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73578,49 +68142,43 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "bundled": true, "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "bundled": true, "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "dev": true, "optional": true }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73629,8 +68187,7 @@ }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73641,8 +68198,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73651,22 +68207,34 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "dev": true, "optional": true }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -73675,8 +68243,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, "dev": true, "optional": true } @@ -73702,16 +68275,6 @@ } } } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } } } }, @@ -73966,30 +68529,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", - "dev": true, - "requires": { - "for-own": "^0.1.3", - "is-plain-object": "^2.0.1", - "kind-of": "^3.0.2", - "lazy-cache": "^1.0.3", - "shallow-clone": "^0.1.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -74097,6 +68636,11 @@ "simple-swizzle": "^0.2.2" } }, + "colorbrewer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.0.0.tgz", + "integrity": "sha1-T5czO5abp2Ejgr5LwzlLNB+0yKI=" + }, "colorette": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", @@ -74127,6 +68671,66 @@ "trim": "0.0.1" } }, + "command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "requires": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "command-line-usage": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz", + "integrity": "sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==", + "requires": { + "array-back": "^4.0.1", + "chalk": "^2.4.2", + "table-layout": "^1.0.1", + "typical": "^5.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" + } + } + }, "commander": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", @@ -74177,9 +68781,9 @@ } }, "compute-scroll-into-view": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz", - "integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ==" + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", + "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" }, "concat-map": { "version": "0.0.1", @@ -74715,41 +69319,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, - "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - }, - "dependencies": { - "browserslist": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.1.tgz", - "integrity": "sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001030", - "electron-to-chromium": "^1.3.363", - "node-releases": "^1.1.50" - } - }, - "caniuse-lite": { - "version": "1.0.30001035", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", - "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", - "dev": true - }, - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, "core-js-pure": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", @@ -75542,9 +70111,9 @@ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "d3-color": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", - "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "d3-dispatch": { "version": "1.0.6", @@ -75862,19 +70431,21 @@ "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" }, "deck.gl": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/deck.gl/-/deck.gl-7.1.11.tgz", - "integrity": "sha512-OUj9JE544N6Y/DCdfdnsbqKn9o72bWgRfsKhyi8aZ8v76hq7XyelmO2GljBmHGYmuMNVLrKcymNMV0m8EEgpZA==", - "requires": { - "@deck.gl/aggregation-layers": "7.1.11", - "@deck.gl/core": "7.1.11", - "@deck.gl/geo-layers": "7.1.11", - "@deck.gl/google-maps": "7.1.11", - "@deck.gl/json": "7.1.11", - "@deck.gl/layers": "7.1.11", - "@deck.gl/mapbox": "7.1.11", - "@deck.gl/mesh-layers": "7.1.11", - "@deck.gl/react": "7.1.11" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/deck.gl/-/deck.gl-8.5.2.tgz", + "integrity": "sha512-tsEyv62Zzc+GT3By0Y1R2gqEJ8K3tGBDaLprAoeAsg7fvIa5ikFBdWEBFHa1UDbgE2UEmYbcBK/yK4GAL8Ia4A==", + "requires": { + "@deck.gl/aggregation-layers": "8.5.2", + "@deck.gl/carto": "8.5.2", + "@deck.gl/core": "8.5.2", + "@deck.gl/extensions": "8.5.2", + "@deck.gl/geo-layers": "8.5.2", + "@deck.gl/google-maps": "8.5.2", + "@deck.gl/json": "8.5.2", + "@deck.gl/layers": "8.5.2", + "@deck.gl/mapbox": "8.5.2", + "@deck.gl/mesh-layers": "8.5.2", + "@deck.gl/react": "8.5.2" } }, "decode-uri-component": { @@ -75931,6 +70502,11 @@ } } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -76175,6 +70751,16 @@ "mathfn": "^1.0.0" } }, + "dnd-core": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", + "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", + "requires": { + "@react-dnd/asap": "^4.0.0", + "@react-dnd/invariant": "^2.0.0", + "redux": "^4.0.4" + } + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -76378,6 +70964,11 @@ } } }, + "draco3d": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.4.1.tgz", + "integrity": "sha512-9Rxonc70xiovBC+Bq1h57SNZIHzWTibU1VfIGp5z3Xx8dPtv4yT5uGhiH7P5uvJRR2jkrvHafRxR7bTANkvfpg==" + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -76420,12 +71011,12 @@ } }, "echarts": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.1.tgz", - "integrity": "sha512-b3nP8M9XwZM2jISuA+fP0EuJv8lcfgWrinel185Npy8bE/UhXTDIPJcqgQOCWdvk0c5CeT6Dsm1xBjmJXAGlxQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz", + "integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==", "requires": { "tslib": "2.0.3", - "zrender": "5.1.0" + "zrender": "5.1.1" }, "dependencies": { "tslib": { @@ -76525,6 +71116,11 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "emotion-rgba": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/emotion-rgba/-/emotion-rgba-0.0.9.tgz", + "integrity": "sha512-fSt51Lh4a1fppXY3nQrMUC00p1jIYMSaRRkUhPiOJ3s9oumae1tY41AJytRK9d4YmJDP9njJBndgdDn9j7CbsA==" + }, "emotion-theming": { "version": "10.0.27", "resolved": "https://registry.npmjs.org/emotion-theming/-/emotion-theming-10.0.27.tgz", @@ -78534,6 +73130,14 @@ } } }, + "expression-eval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/expression-eval/-/expression-eval-2.1.0.tgz", + "integrity": "sha512-FUJO/Akvl/JOWkvlqZaqbkhsEWlCJWDeZG4tzX96UH68D9FeRgYgtb55C2qtqbORC0Q6x5419EDjWu4IT9kQfg==", + "requires": { + "jsep": "^0.3.0" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -78805,9 +73409,9 @@ } }, "fetch-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.0.tgz", - "integrity": "sha512-FUc9XZuhyE3ka3m53lec29PXVhdRf59QG01nE+OZdfl0M/R0E7Pk6k6qeWzHhX1pHl/f2JPA97sjjbHRgSg/9A==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", + "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==" }, "figgy-pudding": { "version": "3.5.1", @@ -79013,6 +73617,14 @@ "pkg-dir": "^3.0.0" } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "requires": { + "array-back": "^3.0.1" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -79060,6 +73672,11 @@ } } }, + "flatbuffers": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz", + "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==" + }, "flatted": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", @@ -79098,15 +73715,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -79298,9 +73906,9 @@ "dev": true }, "fuse.js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.3.0.tgz", - "integrity": "sha512-ESBRkGLWMuVkapqYCcNO1uqMg5qbCKkgb+VS6wsy17Rix0/cMS9kSOZoYkjH8Ko//pgJ/EEGu0GTjk2mjX2LGQ==" + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.4.6.tgz", + "integrity": "sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==" }, "gauge": { "version": "2.7.4", @@ -79321,7 +73929,8 @@ "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true }, "geojson-vt": { "version": "3.2.1", @@ -79559,7 +74168,8 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true }, "globalthis": { "version": "1.0.2", @@ -79642,9 +74252,9 @@ } }, "h3-js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.0.tgz", - "integrity": "sha512-EcH/qGU4khZsAEG39Uu8MvaCing0JFcuoe3K4Xmg5MofDIu1cNJl7z2AQS8ggvXGxboiLJqsGirhEqFKdd2gAA==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", + "integrity": "sha512-LPjlHSwB9zQZrMqKloCZmmmt3yZzIK7nqPcXqwU93zT3TtYG6jP4tZBzAPouxut7lLjdFbMQ75wRBiKfpsnY7w==" }, "hammerjs": { "version": "2.0.8", @@ -85007,10 +79617,16 @@ } } }, + "jsep": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-0.3.5.tgz", + "integrity": "sha512-AoRLBDc6JNnKjNcmonituEABS5bcfqDhQAWWXNTFrqu6nVXBpBAGfcoTGZMFlIrh9FjmE1CQyX9CTNwZrXMMDA==" + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, "json-bigint": { "version": "1.0.0", @@ -85020,6 +79636,11 @@ "bignumber.js": "^9.0.0" } }, + "json-bignum": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", + "integrity": "sha1-QRY7UENsdz2CQk28IO1w23YEuNc=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -85279,12 +79900,6 @@ "webpack-sources": "^1.1.0" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, "lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -85371,15 +79986,6 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "requires": { - "leven": "^3.1.0" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -85787,12 +80393,11 @@ "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==" }, "math.gl": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-2.3.3.tgz", - "integrity": "sha512-wZhx7574KHUpJVMzkaQ559zfn3R8iB0BOilwNrfL/fOLQfPo2TPWsKX96PdfS4svKA2XIGi3yfizrv2Redcv0g==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-3.5.3.tgz", + "integrity": "sha512-cRQRZlc+XvNHd3bIfu3kdPPPAW0vwDelZJmkjn2TDvCyPcmyDtAiZ2Poo1aFoINP7HzN6oHYxapc/0wV3q6Opg==", "requires": { - "@babel/runtime": "^7.0.0", - "gl-matrix": "^3.0.0" + "@math.gl/core": "3.5.3" } }, "mathfn": { @@ -86026,28 +80631,6 @@ "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", "dev": true }, - "merge-deep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", - "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "clone-deep": "^0.2.4", - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -86297,29 +80880,6 @@ "minipass": "^3.0.0" } }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -86359,24 +80919,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mjolnir.js": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.5.0.tgz", @@ -86409,6 +80951,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, + "moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "requires": { + "moment": ">= 2.9.0" + } + }, "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", @@ -87348,6 +81898,14 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "pad-left": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pad-left/-/pad-left-2.1.0.tgz", + "integrity": "sha1-FuajstRKjhOMsIOMx8tAOk/J6ZQ=", + "requires": { + "repeat-string": "^1.5.4" + } + }, "pako": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", @@ -87652,15 +82210,6 @@ } } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -90074,12 +84623,12 @@ "dev": true }, "probe.gl": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/probe.gl/-/probe.gl-3.3.0.tgz", - "integrity": "sha512-59E6AEw4N8sU4PKfAl7S2UBYJCOa064WpEFcXfeFOB/36FJtplYY+261DqLjLAvOqRRHiKVEQUBo63PQ3jKeWA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/probe.gl/-/probe.gl-3.4.0.tgz", + "integrity": "sha512-9CLByZATuhuG/Viq3ckfWU+dAhb7dMmjzsyCy4s7ds9ueTejcVRENxL197/XacOK/AN61YrEERB0QnouB0Qc0Q==", "requires": { "@babel/runtime": "^7.0.0", - "@probe.gl/stats": "3.3.0" + "@probe.gl/stats": "3.4.0" } }, "process": { @@ -91377,17 +85926,6 @@ "@babel/highlight": "^7.10.4" } }, - "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, "@nodelib/fs.stat": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", @@ -91782,18 +86320,6 @@ "@types/hoist-non-react-statics": "^3.3.1", "dnd-core": "^11.1.3", "hoist-non-react-statics": "^3.3.0" - }, - "dependencies": { - "dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "requires": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - } } }, "react-dnd-html5-backend": { @@ -91802,18 +86328,6 @@ "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", "requires": { "dnd-core": "^11.1.3" - }, - "dependencies": { - "dnd-core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", - "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", - "requires": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.0.4" - } - } } }, "react-docgen": { @@ -92070,11 +86584,6 @@ "prop-types": "^15.6.1" } }, - "react-icons": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.2.0.tgz", - "integrity": "sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ==" - }, "react-input-autosize": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", @@ -92164,6 +86673,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-lines-ellipsis": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-lines-ellipsis/-/react-lines-ellipsis-0.15.0.tgz", + "integrity": "sha512-8kWpEmu7ijmB6Gz5t+eSjNux2SpVXZBsmfeFE8LjMS7tU3H8ai475CyNc0dH0RDTwt4Esr7c06Xq4SB7Gpl9yQ==" + }, "react-loadable": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", @@ -92341,8 +86855,7 @@ "react-reverse-portal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-reverse-portal/-/react-reverse-portal-2.0.1.tgz", - "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==", - "requires": {} + "integrity": "sha512-sj/D9nSHspqV8i8hWkTSZ5Ohnrqk2A5fkDKw4Xe/zV4OfF1UYwmbzrxLdmNRdKkWgQwnXIxaa2E3FC7QYdZAeA==" }, "react-router": { "version": "5.1.2", @@ -92397,6 +86910,13 @@ "requires": { "fuse.js": "^3.0.0", "prop-types": "^15.5.8" + }, + "dependencies": { + "fuse.js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", + "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==" + } } }, "react-select": { @@ -92963,6 +87483,11 @@ } } }, + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==" + }, "reduce-function-call": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", @@ -93297,15 +87822,6 @@ "unified": "9.2.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, "@babel/core": { "version": "7.12.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", @@ -93330,69 +87846,12 @@ "source-map": "^0.5.0" } }, - "@babel/generator": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", - "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, "@babel/helper-plugin-utils": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.8.tgz", - "integrity": "sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", - "dev": true - }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", @@ -93413,54 +87872,6 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -93550,15 +87961,6 @@ "xtend": "^4.0.1" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "unified": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", @@ -94065,14 +88467,6 @@ "tslib": "^1.9.0" } }, - "s2-geometry": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/s2-geometry/-/s2-geometry-1.2.10.tgz", - "integrity": "sha1-xv8i8+zK/Q7qSRtgtEwUG5iHrKs=", - "requires": { - "long": "^3.2.0" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -94145,11 +88539,11 @@ } }, "scroll-into-view-if-needed": { - "version": "2.2.26", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz", - "integrity": "sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw==", + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", + "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", "requires": { - "compute-scroll-into-view": "^1.0.16" + "compute-scroll-into-view": "^1.0.17" } }, "seedrandom": { @@ -94157,11 +88551,6 @@ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, - "seer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/seer/-/seer-0.2.5.tgz", - "integrity": "sha512-//0Zwt0x97KQhIWrp4oq9AVNvGA2ctCx4dmFddpkORjRr6bW+hyC8eOhWBVIhiU3uHv1XLU1dekfFKOi28RGHA==" - }, "select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", @@ -94186,7 +88575,8 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true }, "send": { "version": "0.16.2", @@ -94335,35 +88725,6 @@ "safe-buffer": "^5.0.1" } }, - "shallow-clone": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", - "dev": true, - "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^2.0.1", - "lazy-cache": "^0.2.3", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", - "dev": true, - "requires": { - "is-buffer": "^1.0.2" - } - }, - "lazy-cache": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", - "dev": true - } - } - }, "shallow-copy": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", @@ -94828,9 +89189,9 @@ } }, "sortablejs": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz", - "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg==" + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" }, "source-list-map": { "version": "2.0.1", @@ -96612,51 +90973,35 @@ } } }, - "tapable": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", - "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", - "dev": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, + "table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" }, "dependencies": { - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" } } }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + }, "telejson": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/telejson/-/telejson-5.1.0.tgz", @@ -96835,6 +91180,11 @@ } } }, + "text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -97375,11 +91725,16 @@ } }, "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" + }, "ua-parser-js": { "version": "0.7.25", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.25.tgz", @@ -99800,6 +94155,22 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, + "wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" + } + } + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -100097,9 +94468,9 @@ "dev": true }, "zrender": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.0.tgz", - "integrity": "sha512-c+8VRx52ycbmqwHeHLlo/BAfIHBl/JZNLM6cfDQFgzIH05yb+f5J9F/fbRsP+zGc8dW9XHuhdt8/iqukgMZSeg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz", + "integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==", "requires": { "tslib": "2.0.3" }, diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 120644c2b81f..20e1afc52bb4 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -1,6 +1,6 @@ { "name": "superset", - "version": "0.999.0dev", + "version": "0.0.0dev", "description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.", "license": "Apache-2.0", "directories": { @@ -17,7 +17,7 @@ "prod": "npm run build", "build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --colors", "build-instrumented": "cross-env NODE_ENV=development BABEL_ENV=instrumented webpack --mode=development --colors", - "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=production webpack --mode=production --colors", + "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --mode=production --colors", "lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && npm run type", "prettier-check": "prettier --check '{src,stylesheets}/**/*.{css,less,sass,scss}'", "lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx . && npm run clean-css && npm run type", @@ -67,35 +67,35 @@ "@emotion/babel-preset-css-prop": "^11.2.0", "@emotion/cache": "^11.1.3", "@emotion/react": "^11.1.5", - "@superset-ui/chart-controls": "^0.17.53", - "@superset-ui/core": "^0.17.53", - "@superset-ui/legacy-plugin-chart-calendar": "^0.17.53", - "@superset-ui/legacy-plugin-chart-chord": "^0.17.53", - "@superset-ui/legacy-plugin-chart-country-map": "^0.17.53", - "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.53", - "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.53", - "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.53", - "@superset-ui/legacy-plugin-chart-histogram": "^0.17.53", - "@superset-ui/legacy-plugin-chart-horizon": "^0.17.53", - "@superset-ui/legacy-plugin-chart-map-box": "^0.17.53", - "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.53", - "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.53", - "@superset-ui/legacy-plugin-chart-partition": "^0.17.53", - "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.53", - "@superset-ui/legacy-plugin-chart-rose": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sankey": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.53", - "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.53", - "@superset-ui/legacy-plugin-chart-treemap": "^0.17.53", - "@superset-ui/legacy-plugin-chart-world-map": "^0.17.53", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.53", - "@superset-ui/legacy-preset-chart-deckgl": "^0.4.7", - "@superset-ui/legacy-preset-chart-nvd3": "^0.17.53", - "@superset-ui/plugin-chart-echarts": "^0.17.53", - "@superset-ui/plugin-chart-pivot-table": "^0.17.53", - "@superset-ui/plugin-chart-table": "^0.17.53", - "@superset-ui/plugin-chart-word-cloud": "^0.17.53", - "@superset-ui/preset-chart-xy": "^0.17.53", + "@superset-ui/chart-controls": "^0.17.85", + "@superset-ui/core": "^0.17.81", + "@superset-ui/legacy-plugin-chart-calendar": "^0.17.85", + "@superset-ui/legacy-plugin-chart-chord": "^0.17.85", + "@superset-ui/legacy-plugin-chart-country-map": "^0.17.85", + "@superset-ui/legacy-plugin-chart-event-flow": "^0.17.85", + "@superset-ui/legacy-plugin-chart-force-directed": "^0.17.85", + "@superset-ui/legacy-plugin-chart-heatmap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-histogram": "^0.17.85", + "@superset-ui/legacy-plugin-chart-horizon": "^0.17.85", + "@superset-ui/legacy-plugin-chart-map-box": "^0.17.85", + "@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.85", + "@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.85", + "@superset-ui/legacy-plugin-chart-partition": "^0.17.85", + "@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/legacy-plugin-chart-rose": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.85", + "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", + "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", + "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", + "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", + "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", + "@superset-ui/plugin-chart-echarts": "^0.17.85", + "@superset-ui/plugin-chart-pivot-table": "^0.17.85", + "@superset-ui/plugin-chart-table": "^0.17.85", + "@superset-ui/plugin-chart-word-cloud": "^0.17.85", + "@superset-ui/preset-chart-xy": "^0.17.85", "@vx/responsive": "^0.0.195", "abortcontroller-polyfill": "^1.1.9", "antd": "^4.9.4", @@ -110,8 +110,10 @@ "d3-color": "^1.2.0", "d3-scale": "^2.1.2", "dom-to-image": "^2.6.0", + "emotion-rgba": "0.0.9", "fontsource-fira-code": "^3.0.5", "fontsource-inter": "^3.0.5", + "fuse.js": "^6.4.6", "geolib": "^2.0.24", "global-box": "^1.2.0", "html-webpack-plugin": "^4.5.1", @@ -129,6 +131,7 @@ "mathjs": "^8.0.1", "memoize-one": "^5.1.1", "moment": "^2.26.0", + "moment-timezone": "^0.5.33", "mousetrap": "^1.6.1", "mustache": "^2.2.1", "omnibar": "^2.1.1", @@ -146,10 +149,10 @@ "react-dom": "^16.13.0", "react-gravatar": "^2.6.1", "react-hot-loader": "^4.12.20", - "react-icons": "^4.2.0", "react-js-cron": "^1.2.0", "react-json-tree": "^0.11.2", "react-jsonschema-form": "^1.2.0", + "react-lines-ellipsis": "^0.15.0", "react-loadable": "^5.5.0", "react-markdown": "^4.3.1", "react-redux": "^7.2.0", @@ -176,6 +179,7 @@ "redux-undo": "^1.0.0-beta9-9-7", "regenerator-runtime": "^0.13.5", "rison": "^0.1.1", + "scroll-into-view-if-needed": "^2.2.28", "shortid": "^2.2.6", "urijs": "^1.19.6", "use-immer": "^0.4.2", @@ -295,12 +299,11 @@ "storybook-addon-jsx": "^7.3.3", "storybook-addon-paddings": "^3.2.0", "style-loader": "^1.0.0", - "terser-webpack-plugin": "^1.1.0", "thread-loader": "^1.2.0", "transform-loader": "^0.2.3", "ts-jest": "^26.4.2", "ts-loader": "^8.0.7", - "typescript": "^4.0.3", + "typescript": "^4.1.6", "url-loader": "^1.0.1", "webpack": "^4.42.0", "webpack-bundle-analyzer": "^3.6.1", diff --git a/superset-frontend/spec/fixtures/mockDashboardLayout.js b/superset-frontend/spec/fixtures/mockDashboardLayout.js index 90918d388fc9..404b415d78d8 100644 --- a/superset-frontend/spec/fixtures/mockDashboardLayout.js +++ b/superset-frontend/spec/fixtures/mockDashboardLayout.js @@ -129,7 +129,7 @@ export const dashboardLayoutWithTabs = { children: ['ROW_ID2'], parents: ['ROOT_ID', 'TABS_ID'], meta: { - text: 'tab2', + text: '', defaultText: 'tab2', }, }, diff --git a/superset-frontend/spec/fixtures/mockDatasource.js b/superset-frontend/spec/fixtures/mockDatasource.js index e434ad73b067..55d95ebde222 100644 --- a/superset-frontend/spec/fixtures/mockDatasource.js +++ b/superset-frontend/spec/fixtures/mockDatasource.js @@ -164,6 +164,7 @@ export default { column_name: 'num_girls', }, ], + column_types: [0, 1, 2], id, granularity_sqla: [['ds', 'ds']], name: 'birth_names', diff --git a/superset-frontend/spec/fixtures/mockNativeFilters.ts b/superset-frontend/spec/fixtures/mockNativeFilters.ts index afc4959d39fa..e087072bfa73 100644 --- a/superset-frontend/spec/fixtures/mockNativeFilters.ts +++ b/superset-frontend/spec/fixtures/mockNativeFilters.ts @@ -45,7 +45,6 @@ export const nativeFilters: NativeFiltersState = { rootPath: ['ROOT_ID'], excluded: [], }, - isInstant: true, controlValues: { multiSelect: false, enableEmptyFilter: false, @@ -79,7 +78,6 @@ export const nativeFilters: NativeFiltersState = { enableEmptyFilter: false, inverseSelection: false, }, - isInstant: true, }, }, }; @@ -136,7 +134,6 @@ export const singleNativeFiltersState = { cascadeParentIds: [], scope: { rootPath: ['ROOT_ID'], excluded: [227, 229] }, inverseSelection: false, - isInstant: true, allowsMultipleValues: false, isRequired: false, }, diff --git a/superset-frontend/spec/fixtures/mockReportState.js b/superset-frontend/spec/fixtures/mockReportState.js new file mode 100644 index 000000000000..075af8bfe096 --- /dev/null +++ b/superset-frontend/spec/fixtures/mockReportState.js @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import dashboardInfo from './mockDashboardInfo'; +import { user } from '../javascripts/sqllab/fixtures'; + +export default { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: dashboardInfo.id, + name: 'Weekly Report', + owners: [user.userId], + recipients: [ + { + recipient_config_json: { + target: user.email, + }, + type: 'Email', + }, + ], + type: 'Report', +}; diff --git a/superset-frontend/spec/fixtures/mockState.js b/superset-frontend/spec/fixtures/mockState.js index dfa77fdf477a..36870045eecf 100644 --- a/superset-frontend/spec/fixtures/mockState.js +++ b/superset-frontend/spec/fixtures/mockState.js @@ -28,6 +28,7 @@ import dashboardInfo from './mockDashboardInfo'; import { emptyFilters } from './mockDashboardFilters'; import dashboardState from './mockDashboardState'; import { sliceEntitiesForChart } from './mockSliceEntities'; +import { user } from '../javascripts/sqllab/fixtures'; export default { datasources, @@ -40,5 +41,6 @@ export default { dashboardState, dashboardLayout, messageToasts, + user, impressionId: 'mock_impression_id', }; diff --git a/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx new file mode 100644 index 000000000000..bc92df4df75d --- /dev/null +++ b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import datasources from 'spec/fixtures/mockDatasource'; +import messageToasts from 'spec/javascripts/messageToasts/mockMessageToasts'; +import { + nativeFiltersInfo, + mockDataMaskInfo, +} from 'spec/javascripts/dashboard/fixtures/mockNativeFilters'; +import chartQueries from 'spec/fixtures/mockChartQueries'; +import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout'; +import dashboardInfo from 'spec/fixtures/mockDashboardInfo'; +import { emptyFilters } from 'spec/fixtures/mockDashboardFilters'; +import dashboardState from 'spec/fixtures/mockDashboardState'; +import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities'; +import reports from 'spec/fixtures/mockReportState'; + +export default { + datasources, + sliceEntities: sliceEntitiesForChart, + charts: chartQueries, + nativeFilters: nativeFiltersInfo, + dataMask: mockDataMaskInfo, + dashboardInfo, + dashboardFilters: emptyFilters, + dashboardState, + dashboardLayout, + messageToasts, + impressionId: 'mock_impression_id', + reports, +}; diff --git a/superset-frontend/src/dashboard/stylesheets/buttons.less b/superset-frontend/spec/helpers/IntersectionObserver.ts similarity index 66% rename from superset-frontend/src/dashboard/stylesheets/buttons.less rename to superset-frontend/spec/helpers/IntersectionObserver.ts index 9cec645b95bb..95bcab8836ed 100644 --- a/superset-frontend/src/dashboard/stylesheets/buttons.less +++ b/superset-frontend/spec/helpers/IntersectionObserver.ts @@ -16,28 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -@import '../../../stylesheets/less/variables.less'; +class IntersectionObserver { + disconnect() { + return null; + } -.icon-button { - color: @gray; - font-size: @font-size-l; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - outline: none; + observe() { + return null; + } - &:hover, - &:active, - &:focus { - color: @almost-black; - outline: none; - text-decoration: none; + takeRecords() { + return null; } -} -.icon-button-label { - color: @gray-dark; - padding-left: 8px; - font-size: @font-size-m; + unobserve() { + return null; + } } + +export { IntersectionObserver }; diff --git a/superset-frontend/spec/helpers/reducerIndex.ts b/superset-frontend/spec/helpers/reducerIndex.ts index e84b7f6f5119..113368389509 100644 --- a/superset-frontend/spec/helpers/reducerIndex.ts +++ b/superset-frontend/spec/helpers/reducerIndex.ts @@ -30,6 +30,7 @@ import saveModal from 'src/explore/reducers/saveModalReducer'; import explore from 'src/explore/reducers/exploreReducer'; import sqlLab from 'src/SqlLab/reducers/sqlLab'; import localStorageUsageInKilobytes from 'src/SqlLab/reducers/localStorageUsage'; +import reports from 'src/reports/reducers/reports'; const impressionId = (state = '') => state; @@ -53,5 +54,6 @@ export default { explore, sqlLab, localStorageUsageInKilobytes, + reports, common: () => common, }; diff --git a/superset-frontend/spec/helpers/shim.ts b/superset-frontend/spec/helpers/shim.ts index 32f3f27fb81b..e2cef968ca76 100644 --- a/superset-frontend/spec/helpers/shim.ts +++ b/superset-frontend/spec/helpers/shim.ts @@ -25,6 +25,7 @@ import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import { configure as configureTranslation } from '@superset-ui/core'; import { Worker } from './Worker'; +import { IntersectionObserver } from './IntersectionObserver'; import setupSupersetClient from './setupSupersetClient'; configure({ adapter: new Adapter() }); @@ -46,6 +47,7 @@ g.window = g.window || {}; g.window.location = { href: 'about:blank' }; g.window.performance = { now: () => new Date().getTime() }; g.window.Worker = Worker; +g.window.IntersectionObserver = IntersectionObserver; g.URL.createObjectURL = () => ''; Object.defineProperty(window, 'matchMedia', { diff --git a/superset-frontend/spec/helpers/testing-library.tsx b/superset-frontend/spec/helpers/testing-library.tsx index 7366b8d4c642..56489cce8471 100644 --- a/superset-frontend/spec/helpers/testing-library.tsx +++ b/superset-frontend/spec/helpers/testing-library.tsx @@ -20,6 +20,7 @@ import '@testing-library/jest-dom/extend-expect'; import React, { ReactNode, ReactElement } from 'react'; import { render, RenderOptions } from '@testing-library/react'; import { ThemeProvider, supersetTheme } from '@superset-ui/core'; +import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { combineReducers, createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; @@ -32,13 +33,20 @@ type Options = Omit<RenderOptions, 'queries'> & { useRedux?: boolean; useDnd?: boolean; useQueryParams?: boolean; + useRouter?: boolean; initialState?: {}; reducers?: {}; }; function createWrapper(options?: Options) { - const { useDnd, useRedux, useQueryParams, initialState, reducers } = - options || {}; + const { + useDnd, + useRedux, + useQueryParams, + useRouter, + initialState, + reducers, + } = options || {}; return ({ children }: { children?: ReactNode }) => { let result = ( @@ -63,6 +71,10 @@ function createWrapper(options?: Options) { result = <QueryParamProvider>{result}</QueryParamProvider>; } + if (useRouter) { + result = <BrowserRouter>{result}</BrowserRouter>; + } + return result; }; } diff --git a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx index 20f63a6fcabd..937012a9f8be 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx @@ -23,7 +23,6 @@ import sinon from 'sinon'; import fetchMock from 'fetch-mock'; import { ParentSize } from '@vx/responsive'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; -import { Sticky, StickyContainer } from 'react-sticky'; import Tabs from 'src/components/Tabs'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; @@ -50,21 +49,21 @@ jest.mock('src/dashboard/actions/dashboardState'); describe('DashboardBuilder', () => { let favStarStub; - let focusedTabStub; + let activeTabsStub; beforeAll(() => { // this is invoked on mount, so we stub it instead of making a request favStarStub = sinon .stub(dashboardStateActions, 'fetchFaveStar') .returns({ type: 'mock-action' }); - focusedTabStub = sinon - .stub(dashboardStateActions, 'setLastFocusedTab') + activeTabsStub = sinon + .stub(dashboardStateActions, 'setActiveTabs') .returns({ type: 'mock-action' }); }); afterAll(() => { favStarStub.restore(); - focusedTabStub.restore(); + activeTabsStub.restore(); }); function setup(overrideState = {}, overrideStore) { @@ -90,14 +89,14 @@ describe('DashboardBuilder', () => { it('should render a StickyContainer with class "dashboard"', () => { const wrapper = setup(); - const stickyContainer = wrapper.find(StickyContainer); + const stickyContainer = wrapper.find('[data-test="dashboard-content"]'); expect(stickyContainer).toHaveLength(1); expect(stickyContainer.prop('className')).toBe('dashboard'); }); it('should add the "dashboard--editing" class if editMode=true', () => { const wrapper = setup({ dashboardState: { editMode: true } }); - const stickyContainer = wrapper.find(StickyContainer).first(); + const stickyContainer = wrapper.find('[data-test="dashboard-content"]'); expect(stickyContainer.prop('className')).toBe( 'dashboard dashboard--editing', ); @@ -113,12 +112,12 @@ describe('DashboardBuilder', () => { { dashboardLayout: undoableDashboardLayoutWithTabs }, mockStoreWithTabs, ); - const sticky = wrapper.find(Sticky); + + const sticky = wrapper.find('[data-test="top-level-tabs"]'); const dashboardComponent = sticky.find(DashboardComponent); const tabChildren = undoableDashboardLayoutWithTabs.present.TABS_ID.children; - expect(sticky).toHaveLength(1); expect(dashboardComponent).toHaveLength(1 + tabChildren.length); // tab + tabs expect(dashboardComponent.at(0).prop('id')).toBe('TABS_ID'); tabChildren.forEach((tabId, i) => { diff --git a/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx index d6bd4974d548..f24b973199de 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx @@ -20,10 +20,15 @@ import React from 'react'; import { shallow } from 'enzyme'; import { supersetTheme } from '@superset-ui/core'; import { Provider } from 'react-redux'; +import { Store } from 'redux'; import * as SupersetUI from '@superset-ui/core'; -import { CHART_UPDATE_SUCCEEDED } from 'src/chart/chartAction'; +import { styledMount as mount } from 'spec/helpers/theming'; +import { + CHART_RENDERING_SUCCEEDED, + CHART_UPDATE_SUCCEEDED, +} from 'src/chart/chartAction'; import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; -import FiltersBadge from 'src/dashboard/containers/FiltersBadge'; +import { FiltersBadge } from 'src/dashboard/components/FiltersBadge'; import { getMockStoreWithFilters, getMockStoreWithNativeFilters, @@ -34,6 +39,15 @@ import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout'; import Icons from 'src/components/Icons'; import { FeatureFlag } from 'src/featureFlags'; +const defaultStore = getMockStoreWithFilters(); +function setup(store: Store = defaultStore) { + return mount( + <Provider store={store}> + <FiltersBadge chartId={sliceId} /> + </Provider>, + ); +} + describe('FiltersBadge', () => { // there's this bizarre "active filters" thing // that doesn't actually use any kind of state management. @@ -71,9 +85,7 @@ describe('FiltersBadge', () => { <FiltersBadge chartId={sliceId} />, </Provider>, ); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).not.toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist(); }); it('shows the indicator when filters have been applied', () => { @@ -91,14 +103,13 @@ describe('FiltersBadge', () => { ], dashboardFilters, }); - const wrapper = shallow( - <FiltersBadge {...{ store }} chartId={sliceId} />, - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('1'); - expect(wrapper.dive().find('WarningFilled')).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '1', + ); + expect(wrapper.find('WarningFilled')).not.toExist(); }); it("shows a warning when there's a rejected filter", () => { @@ -118,19 +129,18 @@ describe('FiltersBadge', () => { ], dashboardFilters, }); - const wrapper = shallow( - <FiltersBadge {...{ store }} chartId={sliceId} />, - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('0'); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '0', + ); expect( - wrapper.dive().find('[data-test="incompatible-filter-count"]'), + wrapper.find('[data-test="incompatible-filter-count"]'), ).toHaveText('1'); // to look at the shape of the wrapper use: - // console.log(wrapper.dive().debug()) - expect(wrapper.dive().find(Icons.AlertSolid)).toExist(); + // console.log(wrapper.debug()) + expect(wrapper.find(Icons.AlertSolid)).toExist(); }); }); @@ -149,14 +159,9 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - <Provider store={store}> - <FiltersBadge chartId={sliceId} />, - </Provider>, - ); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist(); }); it('shows the indicator when filters have been applied', () => { @@ -177,14 +182,13 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - <FiltersBadge {...{ store }} chartId={sliceId} />, - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('1'); - expect(wrapper.dive().find('WarningFilled')).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '1', + ); + expect(wrapper.find('WarningFilled')).not.toExist(); }); it("shows a warning when there's a rejected filter", () => { @@ -207,17 +211,16 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - <FiltersBadge {...{ store }} chartId={sliceId} />, - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('0'); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '0', + ); expect( - wrapper.dive().find('[data-test="incompatible-filter-count"]'), + wrapper.find('[data-test="incompatible-filter-count"]'), ).toHaveText('1'); - expect(wrapper.dive().find(Icons.AlertSolid)).toExist(); + expect(wrapper.find(Icons.AlertSolid)).toExist(); }); }); }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx index fd6952ba1c74..711dc45f120f 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx @@ -54,7 +54,7 @@ describe('RefreshIntervalModal', () => { }); it('should change refreshFrequency with edit mode', () => { const wrapper = getMountWrapper(mockedProps); - wrapper.instance().handleFrequencyChange({ value: 30 }); + wrapper.instance().handleFrequencyChange(30); wrapper.instance().onSave(); expect(mockedProps.onChange).toHaveBeenCalled(); expect(mockedProps.onChange).toHaveBeenCalledWith(30, mockedProps.editMode); @@ -69,11 +69,11 @@ describe('RefreshIntervalModal', () => { const wrapper = getMountWrapper(props); wrapper.find('span[role="button"]').simulate('click'); - wrapper.instance().handleFrequencyChange({ value: 30 }); + wrapper.instance().handleFrequencyChange(30); wrapper.update(); expect(wrapper.find(ModalTrigger).find(Alert)).toExist(); - wrapper.instance().handleFrequencyChange({ value: 3601 }); + wrapper.instance().handleFrequencyChange(3601); wrapper.update(); expect(wrapper.find(ModalTrigger).find(Alert)).not.toExist(); }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/SliceAdder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/SliceAdder_spec.jsx index 2a0cb55cadd5..16703d0505ce 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/SliceAdder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/SliceAdder_spec.jsx @@ -155,10 +155,10 @@ describe('SliceAdder', () => { }); it('handleSelect', () => { - const newSortBy = { value: 'viz_type' }; + const newSortBy = 'viz_type'; wrapper.instance().handleSelect(newSortBy); expect(spy.calledOnce).toBe(true); - expect(spy.lastCall.args[1]).toBe(newSortBy.value); + expect(spy.lastCall.args[1]).toBe(newSortBy); }); it('handleKeyPress', () => { diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx index 2a1d0bc98b2b..3f7d0bfaa246 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/ChartHolder_spec.jsx @@ -36,6 +36,7 @@ import { sliceId } from 'spec/fixtures/mockChartQueries'; import dashboardInfo from 'spec/fixtures/mockDashboardInfo'; import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout'; import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities'; +import { initialState } from 'spec/javascripts/sqllab/fixtures'; import { nativeFiltersInfo } from '../../fixtures/mockNativeFilters'; describe('ChartHolder', () => { @@ -61,6 +62,7 @@ describe('ChartHolder', () => { function setup(overrideProps) { const mockStore = getMockStore({ + ...initialState, sliceEntities: sliceEntitiesForChart, }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx index 513fe4923fc0..fdbd766f7607 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Chart_spec.jsx @@ -23,7 +23,7 @@ import sinon from 'sinon'; import Chart from 'src/dashboard/components/gridComponents/Chart'; import SliceHeader from 'src/dashboard/components/SliceHeader'; import ChartContainer from 'src/chart/ChartContainer'; - +import * as exploreUtils from 'src/explore/exploreUtils'; import { sliceEntitiesForChart as sliceEntities } from 'spec/fixtures/mockSliceEntities'; import mockDatasource from 'spec/fixtures/mockDatasource'; import chartQueries, { @@ -38,6 +38,7 @@ describe('Chart', () => { updateSliceName() {}, // from redux + maxRows: 666, chart: chartQueries[queryId], formData: chartQueries[queryId].formData, datasource: mockDatasource[sliceEntities.slices[queryId].datasource], @@ -59,6 +60,8 @@ describe('Chart', () => { unsetFocusedFilterField() {}, addSuccessToast() {}, addDangerToast() {}, + exportCSV() {}, + exportFullCSV() {}, componentId: 'test', dashboardId: 111, editMode: false, @@ -86,7 +89,6 @@ describe('Chart', () => { it('should render a description if it has one and isExpanded=true', () => { const wrapper = setup(); expect(wrapper.find('.slice_description')).not.toExist(); - wrapper.setProps({ ...props, isExpanded: true }); expect(wrapper.find('.slice_description')).toExist(); }); @@ -104,4 +106,30 @@ describe('Chart', () => { wrapper.instance().changeFilter(); expect(changeFilter.callCount).toBe(1); }); + it('should call exportChart when exportCSV is clicked', () => { + const stubbedExportCSV = sinon + .stub(exploreUtils, 'exportChart') + .returns(() => {}); + const wrapper = setup(); + wrapper.instance().exportCSV(props.slice.sliceId); + expect(stubbedExportCSV.calledOnce).toBe(true); + expect(stubbedExportCSV.lastCall.args[0]).toEqual( + expect.objectContaining({ + formData: expect.anything(), + resultType: 'results', + resultFormat: 'csv', + }), + ); + exploreUtils.exportChart.restore(); + }); + it('should call exportChart with row_limit props.maxRows when exportFullCSV is clicked', () => { + const stubbedExportCSV = sinon + .stub(exploreUtils, 'exportChart') + .returns(() => {}); + const wrapper = setup(); + wrapper.instance().exportFullCSV(props.slice.sliceId); + expect(stubbedExportCSV.calledOnce).toBe(true); + expect(stubbedExportCSV.lastCall.args[0].formData.row_limit).toEqual(666); + exploreUtils.exportChart.restore(); + }); }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx index 7ce170ae4d21..2e05c4c70f53 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Column_spec.jsx @@ -34,8 +34,9 @@ import IconButton from 'src/dashboard/components/IconButton'; import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer'; import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; -import { mockStore } from 'spec/fixtures/mockStore'; +import { getMockStore } from 'spec/fixtures/mockStore'; import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout'; +import { initialState } from 'spec/javascripts/sqllab/fixtures'; describe('Column', () => { const columnWithoutChildren = { @@ -65,6 +66,9 @@ describe('Column', () => { function setup(overrideProps) { // We have to wrap provide DragDropContext for the underlying DragDroppable // otherwise we cannot assert on DragDroppable children + const mockStore = getMockStore({ + ...initialState, + }); const wrapper = mount( <Provider store={mockStore}> <DndProvider backend={HTML5Backend}> diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx index 3ab795265d0c..6331f6883295 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Divider_spec.jsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { mount } from 'enzyme'; +import { styledMount as mount } from 'spec/helpers/theming'; import sinon from 'sinon'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx index 9ece06e18013..c9bb21ba1672 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Row_spec.jsx @@ -34,8 +34,9 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; import { DASHBOARD_GRID_ID } from 'src/dashboard/util/constants'; import { supersetTheme, ThemeProvider } from '@superset-ui/core'; -import { mockStore } from 'spec/fixtures/mockStore'; +import { getMockStore } from 'spec/fixtures/mockStore'; import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout'; +import { initialState } from 'spec/javascripts/sqllab/fixtures'; describe('Row', () => { const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] }; @@ -61,6 +62,9 @@ describe('Row', () => { function setup(overrideProps) { // We have to wrap provide DragDropContext for the underlying DragDroppable // otherwise we cannot assert on DragDroppable children + const mockStore = getMockStore({ + ...initialState, + }); const wrapper = mount( <Provider store={mockStore}> <DndProvider backend={HTML5Backend}> diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx index 6fb564337cea..c0f15c35d8bc 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tab_spec.jsx @@ -31,7 +31,8 @@ import Tab, { RENDER_TAB_CONTENT, } from 'src/dashboard/components/gridComponents/Tab'; import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout'; -import { mockStoreWithTabs } from 'spec/fixtures/mockStore'; +import { getMockStore } from 'spec/fixtures/mockStore'; +import { initialState } from 'spec/javascripts/sqllab/fixtures'; describe('Tabs', () => { const props = { @@ -62,8 +63,13 @@ describe('Tabs', () => { function setup(overrideProps) { // We have to wrap provide DragDropContext for the underlying DragDroppable // otherwise we cannot assert on DragDroppable children + const mockStore = getMockStore({ + ...initialState, + dashboardLayout: dashboardLayoutWithTabs, + dashboardFilters: {}, + }); const wrapper = mount( - <Provider store={mockStoreWithTabs}> + <Provider store={mockStore}> <DndProvider backend={HTML5Backend}> <Tab {...props} {...overrideProps} /> </DndProvider> diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx index 2f15359c7f19..0985fd6ed930 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx @@ -31,12 +31,13 @@ import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; import HoverMenu from 'src/dashboard/components/menu/HoverMenu'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; -import Tabs from 'src/dashboard/components/gridComponents/Tabs'; +import { Tabs } from 'src/dashboard/components/gridComponents/Tabs'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout'; import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout'; -import { mockStoreWithTabs } from 'spec/fixtures/mockStore'; +import { getMockStore } from 'spec/fixtures/mockStore'; import { nativeFilters } from 'spec/fixtures/mockNativeFilters'; +import { initialState } from 'spec/javascripts/sqllab/fixtures'; describe('Tabs', () => { fetchMock.post('glob:*/r/shortner/', {}); @@ -65,11 +66,17 @@ describe('Tabs', () => { nativeFilters: nativeFilters.filters, }; + const mockStore = getMockStore({ + ...initialState, + dashboardLayout: dashboardLayoutWithTabs, + dashboardFilters: {}, + }); + function setup(overrideProps) { // We have to wrap provide DragDropContext for the underlying DragDroppable // otherwise we cannot assert on DragDroppable children const wrapper = mount( - <Provider store={mockStoreWithTabs}> + <Provider store={mockStore}> <DndProvider backend={HTML5Backend}> <Tabs {...props} {...overrideProps} /> </DndProvider> diff --git a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx index 5c355109676d..7fc0d7e2c74d 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx @@ -119,22 +119,12 @@ describe('FiltersConfigModal', () => { expect(onCancel.mock.calls).toHaveLength(1); }); - it('shows correct alert message for an unsaved filter', async () => { + it('shows correct alert message for unsaved filters', async () => { addFilter(); await clickCancel(); expect(onCancel.mock.calls).toHaveLength(0); expect(wrapper.find(Alert).text()).toContain( - 'Are you sure you want to cancel? "New filter" will not be saved.', - ); - }); - - it('shows correct alert message for 2 unsaved filters', async () => { - addFilter(); - addFilter(); - await clickCancel(); - expect(onCancel.mock.calls).toHaveLength(0); - expect(wrapper.find(Alert).text()).toContain( - 'Are you sure you want to cancel? "New filter" and "New filter" will not be saved.', + 'There are unsaved changes.', ); }); }); diff --git a/superset-frontend/spec/javascripts/dashboard/fixtures/mockNativeFilters.ts b/superset-frontend/spec/javascripts/dashboard/fixtures/mockNativeFilters.ts index 8a106a2b5e88..0bf022d55fe8 100644 --- a/superset-frontend/spec/javascripts/dashboard/fixtures/mockNativeFilters.ts +++ b/superset-frontend/spec/javascripts/dashboard/fixtures/mockNativeFilters.ts @@ -62,7 +62,6 @@ export const nativeFiltersInfo: NativeFiltersState = { rootPath: [], excluded: [], }, - isInstant: true, controlValues: { allowsMultipleValues: true, isRequired: false, diff --git a/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js b/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js index 77a19c771724..8ceac1ab296e 100644 --- a/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js +++ b/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js @@ -34,35 +34,64 @@ describe('getChartIdsFromLayout', () => { }); it('should encode filters', () => { - const url = getDashboardUrl('path', filters); + const url = getDashboardUrl({ pathname: 'path', filters }); expect(url).toBe( 'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D', ); }); it('should encode filters with hash', () => { - const urlWithHash = getDashboardUrl('path', filters, 'iamhashtag'); + const urlWithHash = getDashboardUrl({ + pathname: 'path', + filters, + hash: 'iamhashtag', + }); expect(urlWithHash).toBe( 'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D#iamhashtag', ); }); it('should encode filters with standalone', () => { - const urlWithStandalone = getDashboardUrl( - 'path', + const urlWithStandalone = getDashboardUrl({ + pathname: 'path', filters, - '', - DashboardStandaloneMode.HIDE_NAV, - ); + standalone: DashboardStandaloneMode.HIDE_NAV, + }); expect(urlWithStandalone).toBe( `path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D&standalone=${DashboardStandaloneMode.HIDE_NAV}`, ); }); it('should encode filters with missing standalone', () => { - const urlWithStandalone = getDashboardUrl('path', filters, '', null); + const urlWithStandalone = getDashboardUrl({ + pathname: 'path', + filters, + standalone: null, + }); expect(urlWithStandalone).toBe( 'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D', ); }); + + it('should encode native filters', () => { + const urlWithNativeFilters = getDashboardUrl({ + pathname: 'path', + dataMask: { + 'NATIVE_FILTER-foo123': { + filterState: { + label: 'custom label', + value: ['a', 'b'], + }, + }, + 'NATIVE_FILTER-bar456': { + filterState: { + value: undefined, + }, + }, + }, + }); + expect(urlWithNativeFilters).toBe( + 'path?preselect_filters=%7B%7D&native_filters=%28NATIVE_FILTER-bar456%3A%28filterState%3A%28value%3A%21n%29%29%2CNATIVE_FILTER-foo123%3A%28filterState%3A%28label%3A%27custom+label%27%2Cvalue%3A%21%28a%2Cb%29%29%29%29', + ); + }); }); diff --git a/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts b/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts index 6561b60b6ab1..ddcb3cf55f48 100644 --- a/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts +++ b/superset-frontend/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.ts @@ -48,6 +48,7 @@ describe('getFormDataWithExtraFilters', () => { val: ['United States'], }, ], + datasource: '123', }, }; const mockArgs: GetFormDataWithExtraFiltersArguments = { diff --git a/superset-frontend/spec/javascripts/datasource/ChangeDatasourceModal_spec.jsx b/superset-frontend/spec/javascripts/datasource/ChangeDatasourceModal_spec.jsx index 4e7e9346f882..77a35eba895a 100644 --- a/superset-frontend/spec/javascripts/datasource/ChangeDatasourceModal_spec.jsx +++ b/superset-frontend/spec/javascripts/datasource/ChangeDatasourceModal_spec.jsx @@ -48,7 +48,7 @@ const datasourceData = { }; const DATASOURCES_ENDPOINT = - 'glob:*/api/v1/dataset/?q=(order_column:changed_on_delta_humanized,order_direction:asc,page:0,page_size:20)'; + 'glob:*/api/v1/dataset/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)'; const DATASOURCE_ENDPOINT = `glob:*/datasource/get/${datasourceData.type}/${datasourceData.id}`; const DATASOURCE_PAYLOAD = { new: 'data' }; diff --git a/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx b/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx index 39bcfad8658e..247d04a706ff 100644 --- a/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx +++ b/superset-frontend/spec/javascripts/datasource/DatasourceEditor_spec.jsx @@ -26,7 +26,7 @@ import { render, screen } from 'spec/helpers/testing-library'; import { Radio } from 'src/components/Radio'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import Tabs from 'src/components/Tabs'; import DatasourceEditor from 'src/datasource/DatasourceEditor'; import Field from 'src/CRUD/Field'; @@ -40,8 +40,7 @@ const props = { addDangerToast: () => {}, onChange: () => {}, }; - -const DATASOURCE_ENDPOINT = 'glob:*/datasource/external_metadata/*'; +const DATASOURCE_ENDPOINT = 'glob:*/datasource/external_metadata_by_name/*'; describe('DatasourceEditor', () => { const mockStore = configureStore([thunk]); @@ -174,8 +173,8 @@ describe('DatasourceEditor', () => { const sourceTab = wrapper.find(Tabs.TabPane).first(); expect(sourceTab.find(Radio).first().prop('disabled')).toBe(false); - const icon = sourceTab.find(Icon); - expect(icon.prop('name')).toBe('lock-unlocked'); + const icon = wrapper.find(Icons.LockUnlocked); + expect(icon).toExist(); const tableSelector = sourceTab.find(Field).shallow().find(TableSelector); expect(tableSelector.length).toBe(1); @@ -187,8 +186,8 @@ describe('DatasourceEditor', () => { expect(sourceTab.find(Radio).length).toBe(2); expect(sourceTab.find(Radio).first().prop('disabled')).toBe(true); - const icon = sourceTab.find(Icon); - expect(icon.prop('name')).toBe('lock-locked'); + const icon = wrapper.find(Icons.LockLocked); + expect(icon).toExist(); icon.parent().simulate('click'); expect(wrapper.state('isEditMode')).toBe(true); @@ -210,7 +209,7 @@ describe('DatasourceEditor', () => { expect(sourceTab.find(Radio).length).toBe(2); expect(sourceTab.find(Radio).first().prop('disabled')).toBe(true); - const icon = sourceTab.find(Icon); + const icon = sourceTab.find(Icons.LockLocked); expect(icon).toHaveLength(0); isFeatureEnabledMock.mockRestore(); @@ -233,4 +232,21 @@ describe('DatasourceEditor RTL', () => { ); expect(warningMarkdown.value).toEqual('someone'); }); + it('properly updates the metric information', async () => { + render(<DatasourceEditor {...props} />, { + useRedux: true, + }); + const metricButton = screen.getByTestId('collection-tab-Metrics'); + userEvent.click(metricButton); + const expandToggle = await screen.findAllByLabelText(/toggle expand/i); + userEvent.click(expandToggle[1]); + const certifiedBy = await screen.findByPlaceholderText(/certified by/i); + userEvent.type(certifiedBy, 'I am typing a new name'); + const certificationDetails = await screen.findByPlaceholderText( + /certification details/i, + ); + expect(certifiedBy.value).toEqual('I am typing a new name'); + userEvent.type(certificationDetails, 'I am typing something new'); + expect(certificationDetails.value).toEqual('I am typing something new'); + }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx index 2e25d130f02a..53e1cdc0f2d1 100644 --- a/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx @@ -51,3 +51,12 @@ test('calls onChange with correct values', async () => { expect(defaultProps.onChange).toHaveBeenLastCalledWith([1, 2]), ); }); + +test('receives 0 value', async () => { + render(<BoundsControl {...defaultProps} />); + const minInput = screen.getAllByRole('spinbutton')[0]; + userEvent.type(minInput, '0'); + await waitFor(() => + expect(defaultProps.onChange).toHaveBeenLastCalledWith([0, null]), + ); +}); diff --git a/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx index ab776da70d6c..565c4f9f28b5 100644 --- a/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/ColorScheme_spec.jsx @@ -18,7 +18,7 @@ */ /* eslint-disable no-unused-expressions */ import React from 'react'; -import { Select } from 'src/components/Select'; +import { Select } from 'src/components'; import { getCategoricalSchemeRegistry } from '@superset-ui/core'; import { styledMount as mount } from 'spec/helpers/theming'; import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl'; @@ -37,7 +37,7 @@ describe('ColorSchemeControl', () => { wrapper = mount(<ColorSchemeControl {...defaultProps} />); }); - it('renders a Creatable', () => { + it('renders a Select', () => { expect(wrapper.find(Select)).toExist(); }); }); diff --git a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx deleted file mode 100644 index afa33d0d0647..000000000000 --- a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import { shallow } from 'enzyme'; - -import { ExploreChartHeader } from 'src/explore/components/ExploreChartHeader'; -import ExploreActionButtons from 'src/explore/components/ExploreActionButtons'; -import EditableTitle from 'src/components/EditableTitle'; - -const saveSliceStub = jest.fn(); -const updateChartTitleStub = jest.fn(); -const mockProps = { - actions: { - saveSlice: saveSliceStub, - updateChartTitle: updateChartTitleStub, - }, - can_overwrite: true, - can_download: true, - isStarred: true, - slice: { - form_data: { - viz_type: 'line', - }, - }, - table_name: 'foo', - form_data: { - viz_type: 'table', - }, - timeout: 1000, - chart: { - id: 0, - queryResponse: {}, - }, - chartHeight: '30px', -}; - -describe('ExploreChartHeader', () => { - let wrapper; - beforeEach(() => { - wrapper = shallow(<ExploreChartHeader {...mockProps} />); - }); - - it('is valid', () => { - expect(React.isValidElement(<ExploreChartHeader {...mockProps} />)).toBe( - true, - ); - }); - - it('renders', () => { - expect(wrapper.find(EditableTitle)).toExist(); - expect(wrapper.find(ExploreActionButtons)).toExist(); - }); - - it('should update title but not save', () => { - const editableTitle = wrapper.find(EditableTitle); - expect(editableTitle.props().onSaveTitle).toBe(updateChartTitleStub); - }); -}); diff --git a/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx index 188469876c39..c255e6a62b53 100644 --- a/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/MetricsControl_spec.jsx @@ -67,60 +67,14 @@ const sumValueAdhocMetric = new AdhocMetric({ label: 'SUM(value)', }); -describe('MetricsControl', () => { +// TODO: rewrite the tests to RTL +describe.skip('MetricsControl', () => { it('renders Select', () => { const { component } = setup(); expect(component.find(LabelsContainer)).toExist(); }); describe('constructor', () => { - it('unifies options for the dropdown select with aggregates', () => { - const { component } = setup(); - expect(component.state('options')).toEqual([ - { - optionName: '_col_source', - type: 'VARCHAR(255)', - column_name: 'source', - }, - { - optionName: '_col_target', - type: 'VARCHAR(255)', - column_name: 'target', - }, - { optionName: '_col_value', type: 'DOUBLE', column_name: 'value' }, - ...Object.keys(AGGREGATES).map(aggregate => ({ - aggregate_name: aggregate, - optionName: `_aggregate_${aggregate}`, - })), - { - optionName: 'sum__value', - metric_name: 'sum__value', - expression: 'SUM(energy_usage.value)', - }, - { - optionName: 'avg__value', - metric_name: 'avg__value', - expression: 'AVG(energy_usage.value)', - }, - ]); - }); - - it('does not show aggregates in options if no columns', () => { - const { component } = setup({ columns: [] }); - expect(component.state('options')).toEqual([ - { - optionName: 'sum__value', - metric_name: 'sum__value', - expression: 'SUM(energy_usage.value)', - }, - { - optionName: 'avg__value', - metric_name: 'avg__value', - expression: 'AVG(energy_usage.value)', - }, - ]); - }); - it('coerces Adhoc Metrics from form data into instances of the AdhocMetric class and leaves saved metrics', () => { const { component } = setup({ value: [ @@ -178,194 +132,7 @@ describe('MetricsControl', () => { }); }); - describe('checkIfAggregateInInput', () => { - it('handles an aggregate in the input', () => { - const { component } = setup(); - - expect(component.state('aggregateInInput')).toBeNull(); - component.instance().checkIfAggregateInInput('AVG('); - expect(component.state('aggregateInInput')).toBe(AGGREGATES.AVG); - }); - - it('handles no aggregate in the input', () => { - const { component } = setup(); - - expect(component.state('aggregateInInput')).toBeNull(); - component.instance().checkIfAggregateInInput('colu'); - expect(component.state('aggregateInInput')).toBeNull(); - }); - }); - describe('option filter', () => { - it('includes user defined metrics', () => { - const { component } = setup({ datasourceType: 'druid' }); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'a_metric', - optionName: 'a_metric', - expression: 'SUM(FANCY(metric))', - }, - }, - 'a', - ), - ).toBe(true); - }); - - it('includes auto generated avg metrics for druid', () => { - const { component } = setup({ datasourceType: 'druid' }); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'avg__metric', - optionName: 'avg__metric', - expression: 'AVG(metric)', - }, - }, - 'a', - ), - ).toBe(true); - }); - - it('includes columns and aggregates', () => { - const { component } = setup(); - - expect( - !!component.instance().selectFilterOption( - { - data: { - type: 'VARCHAR(255)', - column_name: 'source', - optionName: '_col_source', - }, - }, - 'sou', - ), - ).toBe(true); - - expect( - !!component - .instance() - .selectFilterOption( - { data: { aggregate_name: 'AVG', optionName: '_aggregate_AVG' } }, - 'av', - ), - ).toBe(true); - }); - - it('includes columns based on verbose_name', () => { - const { component } = setup(); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'sum__num', - verbose_name: 'babies', - optionName: '_col_sum_num', - }, - }, - 'bab', - ), - ).toBe(true); - }); - - it('excludes auto generated avg metrics for sqla', () => { - const { component } = setup(); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'avg__metric', - optionName: 'avg__metric', - expression: 'AVG(metric)', - }, - }, - 'a', - ), - ).toBe(false); - }); - - it('includes custom made simple saved metrics', () => { - const { component } = setup(); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'my_fancy_sum_metric', - optionName: 'my_fancy_sum_metric', - expression: 'SUM(value)', - }, - }, - 'sum', - ), - ).toBe(true); - }); - - it('excludes auto generated metrics', () => { - const { component } = setup(); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'sum__value', - optionName: 'sum__value', - expression: 'SUM(value)', - }, - }, - 'sum', - ), - ).toBe(false); - - expect( - !!component.instance().selectFilterOption( - { - data: { - metric_name: 'sum__value', - optionName: 'sum__value', - expression: 'SUM("table"."value")', - }, - }, - 'sum', - ), - ).toBe(false); - }); - - it('filters out metrics if the input begins with an aggregate', () => { - const { component } = setup(); - component.setState({ aggregateInInput: true }); - - expect( - !!component.instance().selectFilterOption( - { - data: { metric_name: 'metric', expression: 'SUM(FANCY(metric))' }, - }, - 'SUM(', - ), - ).toBe(false); - }); - - it('includes columns if the input begins with an aggregate', () => { - const { component } = setup(); - component.setState({ aggregateInInput: true }); - - expect( - !!component - .instance() - .selectFilterOption( - { data: { type: 'DOUBLE', column_name: 'value' } }, - 'SUM(', - ), - ).toBe(true); - }); - it('Removes metrics if savedMetrics changes', () => { const { props, component, onChange } = setup({ value: [ diff --git a/superset-frontend/spec/javascripts/explore/components/VizTypeControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/VizTypeControl_spec.jsx index 0a47e6b2bf99..c027fd0c3dff 100644 --- a/superset-frontend/spec/javascripts/explore/components/VizTypeControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/VizTypeControl_spec.jsx @@ -18,22 +18,31 @@ */ import React from 'react'; import sinon from 'sinon'; -import { shallow } from 'enzyme'; +import userEvent from '@testing-library/user-event'; import { getChartMetadataRegistry, ChartMetadata } from '@superset-ui/core'; +import { render, screen } from 'spec/helpers/testing-library'; import VizTypeControl from 'src/explore/components/controls/VizTypeControl'; -import Modal from 'src/components/Modal'; -import { Input } from 'src/common/components'; +import { DynamicPluginProvider } from 'src/components/DynamicPlugins'; +import { act } from 'react-dom/test-utils'; const defaultProps = { name: 'viz_type', label: 'Visualization Type', value: 'vis1', onChange: sinon.spy(), + isModalOpenInit: true, }; -describe('VizTypeControl', () => { - let wrapper; +/** + * AntD and/or the Icon component seems to be doing some kind of async changes, + * so even though the test passes, there is a warning an update to Icon was not + * wrapped in act(). This sufficiently act-ifies whatever side effects are going + * on and prevents those warnings. + */ +const waitForEffects = () => + act(() => new Promise(resolve => setTimeout(resolve, 0))); +describe('VizTypeControl', () => { const registry = getChartMetadataRegistry(); registry .registerValue( @@ -41,6 +50,7 @@ describe('VizTypeControl', () => { new ChartMetadata({ name: 'vis1', thumbnail: '', + tags: ['Popular'], }), ) .registerValue( @@ -48,29 +58,37 @@ describe('VizTypeControl', () => { new ChartMetadata({ name: 'vis2', thumbnail: '', + tags: ['foobar'], }), ); - beforeEach(() => { - wrapper = shallow(<VizTypeControl {...defaultProps} />); - }); - - it('renders a Modal', () => { - expect(wrapper.find(Modal)).toExist(); + beforeEach(async () => { + render( + <DynamicPluginProvider> + <VizTypeControl {...defaultProps} /> + </DynamicPluginProvider>, + ); + await waitForEffects(); }); - it('calls onChange when toggled', () => { - const select = wrapper.find('.viztype-selector-container').first(); - select.simulate('click'); + it('calls onChange when submitted', () => { + const thumbnail = screen.getAllByTestId('viztype-selector-container')[0]; + const submit = screen.getByText('Select'); + userEvent.click(thumbnail); + expect(defaultProps.onChange.called).toBe(false); + userEvent.click(submit); expect(defaultProps.onChange.called).toBe(true); }); - it('filters images based on text input', () => { - expect(wrapper.find('img')).toHaveLength(2); - wrapper.find(Input).simulate('change', { - target: { - value: 'vis2', - }, - }); - expect(wrapper.find('img')).toExist(); + + it('filters images based on text input', async () => { + const thumbnails = screen.getByTestId('viztype-selector-container'); + expect(thumbnails).toBeInTheDocument(); + + const searchInput = screen.getByPlaceholderText('Search all charts'); + userEvent.type(searchInput, 'foo'); + await waitForEffects(); + + const thumbnail = screen.getByTestId('viztype-selector-container'); + expect(thumbnail).toBeInTheDocument(); }); }); diff --git a/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx b/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx index fa5cae1f8214..0686f82f9fd7 100644 --- a/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx +++ b/superset-frontend/spec/javascripts/messageToasts/components/Toast_spec.jsx @@ -18,6 +18,7 @@ */ import React from 'react'; import { mount } from 'enzyme'; +import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import Toast from 'src/messageToasts/components/Toast'; import { act } from 'react-dom/test-utils'; import mockMessageToasts from '../mockMessageToasts'; @@ -27,7 +28,11 @@ const props = { onCloseToast() {}, }; -const setup = overrideProps => mount(<Toast {...props} {...overrideProps} />); +const setup = overrideProps => + mount(<Toast {...props} {...overrideProps} />, { + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); describe('Toast', () => { it('should render', () => { diff --git a/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx index 1ba1ac821fb1..b153c148394c 100644 --- a/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx @@ -81,9 +81,13 @@ describe('Left Panel Expansion', () => { </Provider> </ThemeProvider>, ); - const dbSelect = screen.getByText(/select a database/i); - const schemaSelect = screen.getByText(/select a schema \(0\)/i); - const dropdown = screen.getByText(/Select table/i); + const dbSelect = screen.getByRole('combobox', { + name: 'Select a database', + }); + const schemaSelect = screen.getByRole('combobox', { + name: 'Select a schema', + }); + const dropdown = screen.getByText(/Select a table/i); const abUser = screen.getByText(/ab_user/i); expect(dbSelect).toBeInTheDocument(); expect(schemaSelect).toBeInTheDocument(); diff --git a/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx b/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx index 8c07008815ae..c10e12048ed2 100644 --- a/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx +++ b/superset-frontend/spec/javascripts/sqllab/TableElement_spec.jsx @@ -55,7 +55,7 @@ describe('TableElement', () => { }, }, ); - expect(wrapper.find(IconTooltip)).toHaveLength(5); + expect(wrapper.find(IconTooltip)).toHaveLength(4); }); it('has 14 columns', () => { const wrapper = shallow(<TableElement {...mockedProps} />); @@ -112,20 +112,20 @@ describe('TableElement', () => { }, ); expect( - wrapper.find(IconTooltip).at(2).hasClass('fa-sort-alpha-asc'), + wrapper.find(IconTooltip).at(1).hasClass('fa-sort-alpha-asc'), ).toEqual(true); expect( - wrapper.find(IconTooltip).at(2).hasClass('fa-sort-numeric-asc'), + wrapper.find(IconTooltip).at(1).hasClass('fa-sort-numeric-asc'), ).toEqual(false); wrapper.find('.header-container').hostNodes().simulate('click'); expect(wrapper.find(ColumnElement).first().props().column.name).toBe('id'); wrapper.find('.header-container').simulate('mouseEnter'); wrapper.find('.sort-cols').hostNodes().simulate('click'); expect( - wrapper.find(IconTooltip).at(2).hasClass('fa-sort-numeric-asc'), + wrapper.find(IconTooltip).at(1).hasClass('fa-sort-numeric-asc'), ).toEqual(true); expect( - wrapper.find(IconTooltip).at(2).hasClass('fa-sort-alpha-asc'), + wrapper.find(IconTooltip).at(1).hasClass('fa-sort-alpha-asc'), ).toEqual(false); expect(wrapper.find(ColumnElement).first().props().column.name).toBe( 'active', diff --git a/superset-frontend/src/CRUD/CollectionTable.tsx b/superset-frontend/src/CRUD/CollectionTable.tsx index 6efe2e6a61ed..85380a553b68 100644 --- a/superset-frontend/src/CRUD/CollectionTable.tsx +++ b/superset-frontend/src/CRUD/CollectionTable.tsx @@ -42,24 +42,45 @@ interface CRUDCollectionProps { ) => ReactNode)[]; onChange?: (arg0: any) => void; tableColumns: Array<any>; + sortColumns: Array<string>; stickyHeader?: boolean; } +type Sort = number | string | boolean | any; + +enum SortOrder { + asc = 1, + desc = 2, + unsort = 0, +} + interface CRUDCollectionState { collection: object; + collectionArray: Array<object>; expandedColumns: object; + sortColumn: string; + sort: SortOrder; +} + +function createCollectionArray(collection: object) { + return Object.keys(collection).map(k => collection[k]); } function createKeyedCollection(arr: Array<object>) { - const newArr = arr.map((o: any) => ({ + const collectionArray = arr.map((o: any) => ({ ...o, id: o.id || shortid.generate(), })); - const map = {}; - newArr.forEach((o: any) => { - map[o.id] = o; + + const collection = {}; + collectionArray.forEach((o: any) => { + collection[o.id] = o; }); - return map; + + return { + collection, + collectionArray, + }; } const CrudTableWrapper = styled.div<{ stickyHeader?: boolean }>` @@ -67,15 +88,23 @@ const CrudTableWrapper = styled.div<{ stickyHeader?: boolean }>` stickyHeader && ` height: 350px; - overflow: auto; + overflow-y: auto; + overflow-x: auto; + .table { + min-width: 800px; + } thead th { background: #fff; position: sticky; top: 0; z-index: 9; + min } `} + th span { + vertical-align: ${({ theme }) => theme.gridUnit * -2}px; + } `; const CrudButtonWrapper = styled.div` @@ -89,9 +118,16 @@ export default class CRUDCollection extends React.PureComponent< > { constructor(props: CRUDCollectionProps) { super(props); + + const { collection, collectionArray } = createKeyedCollection( + props.collection, + ); this.state = { expandedColumns: {}, - collection: createKeyedCollection(props.collection), + collection, + collectionArray, + sortColumn: '', + sort: 0, }; this.renderItem = this.renderItem.bind(this); this.onAddItem = this.onAddItem.bind(this); @@ -100,12 +136,18 @@ export default class CRUDCollection extends React.PureComponent< this.onFieldsetChange = this.onFieldsetChange.bind(this); this.renderTableBody = this.renderTableBody.bind(this); this.changeCollection = this.changeCollection.bind(this); + this.sortColumn = this.sortColumn.bind(this); + this.renderSortIcon = this.renderSortIcon.bind(this); } UNSAFE_componentWillReceiveProps(nextProps: CRUDCollectionProps) { if (nextProps.collection !== this.props.collection) { + const { collection, collectionArray } = createKeyedCollection( + nextProps.collection, + ); this.setState({ - collection: createKeyedCollection(nextProps.collection), + collection, + collectionArray, }); } } @@ -126,10 +168,7 @@ export default class CRUDCollection extends React.PureComponent< if (!newItem.id) { newItem = { ...newItem, id: shortid.generate() }; } - this.changeCollection({ - ...this.state.collection, - [newItem.id]: newItem, - }); + this.changeCollection(this.state.collection, newItem); } } @@ -150,10 +189,18 @@ export default class CRUDCollection extends React.PureComponent< return label; } - changeCollection(collection: any) { + changeCollection(collection: any, newItem?: object) { this.setState({ collection }); if (this.props.onChange) { - this.props.onChange(Object.keys(collection).map(k => collection[k])); + const collectionArray = this.state.collectionArray + .map((c: { id: number }) => collection[c.id]) + // filter out removed items + .filter(c => c !== undefined); + + if (newItem) { + collectionArray.unshift(newItem); + } + this.props.onChange(collectionArray); } } @@ -181,15 +228,73 @@ export default class CRUDCollection extends React.PureComponent< })); } + sortColumn(col: string, sort = SortOrder.unsort) { + const { sortColumns } = this.props; + // default sort logic sorting string, boolean and number + const compareSort = (m: Sort, n: Sort) => { + if (typeof m === 'string') { + return (m || ' ').localeCompare(n); + } + return m - n; + }; + return () => { + if (sortColumns?.includes(col)) { + // display in unsorted order if no sort specified + if (sort === SortOrder.unsort) { + const { collection } = createKeyedCollection(this.props.collection); + const collectionArray = createCollectionArray(collection); + this.setState({ + collectionArray, + sortColumn: '', + sort, + }); + return; + } + + // newly ordered collection + const sorted = [ + ...this.state.collectionArray, + ].sort((a: object, b: object) => compareSort(a[col], b[col])); + const newCollection = + sort === SortOrder.asc ? sorted : sorted.reverse(); + + this.setState(prevState => ({ + ...prevState, + collectionArray: newCollection, + sortColumn: col, + sort, + })); + } + }; + } + + renderSortIcon(col: string) { + if (this.state.sortColumn === col && this.state.sort === SortOrder.asc) { + return <Icons.SortAsc onClick={this.sortColumn(col, 2)} />; + } + if (this.state.sortColumn === col && this.state.sort === SortOrder.desc) { + return <Icons.SortDesc onClick={this.sortColumn(col, 0)} />; + } + return <Icons.Sort onClick={this.sortColumn(col, 1)} />; + } + renderHeaderRow() { const cols = this.effectiveTableColumns(); - const { allowDeletes, expandFieldset, extraButtons } = this.props; + const { + allowDeletes, + expandFieldset, + extraButtons, + sortColumns, + } = this.props; return ( <thead> <tr> {expandFieldset && <th aria-label="Expand" className="tiny-cell" />} {cols.map(col => ( - <th key={col}>{this.getLabel(col)}</th> + <th key={col}> + {this.getLabel(col)} + {sortColumns?.includes(col) && this.renderSortIcon(col)} + </th> ))} {extraButtons} {allowDeletes && ( @@ -261,7 +366,6 @@ export default class CRUDCollection extends React.PureComponent< aria-label="Delete item" className="pointer" data-test="crud-delete-icon" - iconSize="m" role="button" tabIndex={0} onClick={this.deleteItem.bind(this, record.id)} @@ -298,9 +402,7 @@ export default class CRUDCollection extends React.PureComponent< } renderTableBody() { - const data = Object.keys(this.state.collection).map( - k => this.state.collection[k], - ); + const data = this.state.collectionArray; const content = data.length ? data.map(d => this.renderItem(d)) : this.renderEmptyCell(); @@ -315,7 +417,7 @@ export default class CRUDCollection extends React.PureComponent< <span className="m-t-10 m-r-10"> <Button buttonSize="small" - buttonStyle="primary" + buttonStyle="tertiary" onClick={this.onAddItem} data-test="add-item-button" > diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index 18d2141bf36f..94391e0ac8a3 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -250,18 +250,22 @@ export function queryFailed(query, msg, link, errors) { }) : Promise.resolve(); - return sync - .then(() => dispatch({ type: QUERY_FAILED, query, msg, link, errors })) - .catch(() => - dispatch( - addDangerToast( - t( - 'An error occurred while storing the latest query id in the backend. ' + - 'Please contact your administrator if this problem persists.', + return ( + sync + .catch(() => + dispatch( + addDangerToast( + t( + 'An error occurred while storing the latest query id in the backend. ' + + 'Please contact your administrator if this problem persists.', + ), ), ), - ), - ); + ) + // We should always show the error message, even if we couldn't sync the + // state to the backend + .then(() => dispatch({ type: QUERY_FAILED, query, msg, link, errors })) + ); }; } diff --git a/superset-frontend/src/SqlLab/components/HighlightedSql.tsx b/superset-frontend/src/SqlLab/components/HighlightedSql.tsx index 2682f83a05eb..e4f40d8fa645 100644 --- a/superset-frontend/src/SqlLab/components/HighlightedSql.tsx +++ b/superset-frontend/src/SqlLab/components/HighlightedSql.tsx @@ -80,7 +80,7 @@ function HighlightSqlModal({ rawSql, sql }: HighlightedSqlModalTypes) { </SyntaxHighlighter> {rawSql && rawSql !== sql && ( <div> - <h4>{t('Raw SQL')}</h4> + <h4>{t('Executed SQL')}</h4> <SyntaxHighlighter language="sql" style={github}> {rawSql} </SyntaxHighlighter> diff --git a/superset-frontend/src/SqlLab/components/QueryTable/index.jsx b/superset-frontend/src/SqlLab/components/QueryTable/index.jsx index d23fd49ca6d2..596cbe257f4e 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/index.jsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.jsx @@ -22,13 +22,12 @@ import moment from 'moment'; import Card from 'src/components/Card'; import ProgressBar from 'src/components/ProgressBar'; import Label from 'src/components/Label'; -import { t, styled } from '@superset-ui/core'; +import { t, useTheme } from '@superset-ui/core'; import { useSelector } from 'react-redux'; import TableView from 'src/components/TableView'; import Button from 'src/components/Button'; import { fDuration } from 'src/modules/dates'; import Icons from 'src/components/Icons'; -import Icon from 'src/components/Icon'; import { Tooltip } from 'src/components/Tooltip'; import ResultSet from '../ResultSet'; import ModalTrigger from '../../../components/ModalTrigger'; @@ -55,81 +54,65 @@ const openQuery = id => { window.open(url); }; -const statusAttributes = { - success: { - color: ({ theme }) => theme.colors.success.base, - config: { - name: 'check', - label: t('Success'), - status: 'success', +const QueryTable = props => { + const theme = useTheme(); + const statusAttributes = { + success: { + config: { + icon: <Icons.Check iconColor={theme.colors.success.base} />, + label: t('Success'), + }, }, - }, - failed: { - color: ({ theme }) => theme.colors.error.base, - config: { - name: 'x-small', - label: t('Failed'), - status: 'failed', + failed: { + config: { + icon: <Icons.XSmall iconColor={theme.colors.error.base} />, + label: t('Failed'), + }, }, - }, - stopped: { - color: ({ theme }) => theme.colors.error.base, - config: { - name: 'x-small', - label: t('Failed'), - status: 'failed', + stopped: { + config: { + icon: <Icons.XSmall iconColor={theme.colors.error.base} />, + label: t('Failed'), + }, }, - }, - running: { - color: ({ theme }) => theme.colors.primary.base, - config: { - name: 'running', - label: t('Running'), - status: 'running', + running: { + config: { + icon: <Icons.Running iconColor={theme.colors.primary.base} />, + label: t('Running'), + }, }, - }, - fetching: { - color: ({ theme }) => theme.colors.primary.base, - config: { - name: 'queued', - label: t('fetching'), - status: 'fetching', + fetching: { + config: { + icon: <Icons.Queued iconColor={theme.colors.primary.base} />, + label: t('fetching'), + }, }, - }, - timed_out: { - color: ({ theme }) => theme.colors.grayscale.light1, - config: { - name: 'offline', - label: t('Offline'), - status: 'offline', + timed_out: { + config: { + icon: <Icons.Offline iconColor={theme.colors.grayscale.light1} />, + label: t('Offline'), + }, }, - }, - scheduled: { - color: ({ theme }) => theme.colors.greyscale.base, - config: { - name: 'queued', - label: t('Scheduled'), - status: 'queued', + scheduled: { + config: { + icon: <Icons.Queued iconColor={theme.colors.grayscale.base} />, + label: t('Scheduled'), + }, }, - }, - pending: { - color: ({ theme }) => theme.colors.greyscale.base, - config: { - name: 'queued', - label: t('Scheduled'), - status: 'queued', + pending: { + config: { + icon: <Icons.Queued iconColor={theme.colors.grayscale.base} />, + label: t('Scheduled'), + }, }, - }, -}; - -const StatusIcon = styled(Icon, { - shouldForwardProp: prop => prop !== 'status', -})` - color: ${({ status, theme }) => - statusAttributes[status]?.color || theme.colors.grayscale.base}; -`; + error: { + config: { + icon: <Icons.Error iconColor={theme.colors.error.base} />, + label: t('Unknown Status'), + }, + }, + }; -const QueryTable = props => { const setHeaders = column => { if (column === 'sql') { return column.toUpperCase(); @@ -172,6 +155,8 @@ const QueryTable = props => { return props.queries .map(query => { const q = { ...query }; + const status = statusAttributes[q.state] || statusAttributes.error; + if (q.endDttm) { q.duration = fDuration(q.startDttm, q.endDttm); } @@ -268,16 +253,8 @@ const QueryTable = props => { /> ); q.state = ( - <Tooltip - title={statusAttributes[q.state].config.label} - placement="bottom" - > - <span> - <StatusIcon - name={statusAttributes[q.state].config.name} - status={statusAttributes[q.state].config.status} - /> - </span> + <Tooltip title={status.config.label} placement="bottom"> + <span>{status.config.icon}</span> </Tooltip> ); q.actions = ( diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx index 9d22ce5b7c70..076904386294 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton.tsx @@ -21,7 +21,7 @@ import { t, styled, supersetTheme } from '@superset-ui/core'; import { Menu } from 'src/common/components'; import Button, { ButtonProps } from 'src/components/Button'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { DropdownButton, DropdownButtonProps, @@ -77,6 +77,10 @@ const StyledButton = styled.span` &:last-of-type { margin-right: ${({ theme }) => theme.gridUnit * 2}px; } + span[name='caret-down'] { + display: flex; + margin-right: ${({ theme }) => theme.gridUnit * -2}px; + } } `; @@ -116,8 +120,8 @@ const RunQueryActionButton = ({ ? { overlay: overlayCreateAsMenu, icon: ( - <Icon - color={ + <Icons.CaretDown + iconColor={ isDisabled ? supersetTheme.colors.grayscale.base : supersetTheme.colors.grayscale.light5 diff --git a/superset-frontend/src/SqlLab/components/SaveQuery.tsx b/superset-frontend/src/SqlLab/components/SaveQuery.tsx index 6b589b758677..1c0dee426940 100644 --- a/superset-frontend/src/SqlLab/components/SaveQuery.tsx +++ b/superset-frontend/src/SqlLab/components/SaveQuery.tsx @@ -18,15 +18,21 @@ */ import React, { useState } from 'react'; import { Row, Col, Input, TextArea } from 'src/common/components'; -import { t, supersetTheme, styled } from '@superset-ui/core'; +import { t, styled } from '@superset-ui/core'; import Button from 'src/components/Button'; import { Form, FormItem } from 'src/components/Form'; import Modal from 'src/components/Modal'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; const Styles = styled.span` - svg { - vertical-align: -${supersetTheme.gridUnit * 1.25}px; + span[role='img'] { + display: flex; + margin: 0; + color: ${({ theme }) => theme.colors.grayscale.base}; + svg { + vertical-align: -${({ theme }) => theme.gridUnit * 1.25}px; + margin: 0; + } } `; @@ -150,12 +156,7 @@ export default function SaveQuery({ return ( <Styles className="SaveQuery"> <Button buttonSize="small" onClick={toggleSave}> - <Icon - name="save" - color={supersetTheme.colors.primary.base} - width={20} - height={20} - />{' '} + <Icons.Save iconSize="xl" /> {isSaved ? t('Save') : t('Save as')} </Button> <Modal diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx index 10e3e43c31c2..5f6c89c7b73c 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery.tsx @@ -17,11 +17,11 @@ * under the License. */ import React from 'react'; -import { t, useTheme } from '@superset-ui/core'; +import { t, useTheme, styled } from '@superset-ui/core'; import Button from 'src/components/Button'; import withToasts from 'src/messageToasts/enhancers/withToasts'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import CopyToClipboard from 'src/components/CopyToClipboard'; import { storeQuery } from 'src/utils/common'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; @@ -39,6 +39,16 @@ interface ShareSqlLabQueryPropTypes { addDangerToast: (msg: string) => void; } +const StyledIcon = styled(Icons.Link)` + &:first-of-type { + margin: 0; + display: flex; + svg { + margin: 0; + } + } +`; + function ShareSqlLabQuery({ queryEditor, addDangerToast, @@ -86,14 +96,12 @@ function ShareSqlLabQuery({ : t('Save the query to enable this feature'); return ( <Button buttonSize="small" tooltip={tooltip} disabled={!canShare}> - <Icon - name="link" - color={ + <StyledIcon + iconColor={ canShare ? theme.colors.primary.base : theme.colors.grayscale.base } - width={20} - height={20} - />{' '} + iconSize="xl" + /> {t('Copy link')} </Button> ); diff --git a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx index f084fc4384fe..80f9e2d7985c 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.tsx @@ -118,6 +118,9 @@ export default function SouthPane({ } let results; if (latestQuery) { + if (latestQuery?.extra?.errors) { + latestQuery.errors = latestQuery.extra.errors; + } if ( isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) && latestQuery.state === 'success' && diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx index 61efad9bfa6a..540cfb77f3f4 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx @@ -24,7 +24,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import PropTypes from 'prop-types'; import Split from 'react-split'; -import { t, styled, supersetTheme } from '@superset-ui/core'; +import { t, styled, supersetTheme, withTheme } from '@superset-ui/core'; import debounce from 'lodash/debounce'; import throttle from 'lodash/throttle'; import StyledModal from 'src/components/Modal'; @@ -38,7 +38,7 @@ import { Switch, Input, } from 'src/common/components'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { detectOS } from 'src/utils/common'; import { addQueryEditor, @@ -194,6 +194,7 @@ class SqlEditor extends React.PureComponent { WINDOW_RESIZE_THROTTLE_MS, ); + this.onBeforeUnload = this.onBeforeUnload.bind(this); this.renderDropdown = this.renderDropdown.bind(this); } @@ -212,6 +213,7 @@ class SqlEditor extends React.PureComponent { this.setState({ height: this.getSqlEditorHeight() }); window.addEventListener('resize', this.handleWindowResize); + window.addEventListener('beforeunload', this.onBeforeUnload); // setup hotkeys const hotkeys = this.getHotkeyConfig(); @@ -222,6 +224,7 @@ class SqlEditor extends React.PureComponent { componentWillUnmount() { window.removeEventListener('resize', this.handleWindowResize); + window.removeEventListener('beforeunload', this.onBeforeUnload); } onResizeStart() { @@ -242,6 +245,16 @@ class SqlEditor extends React.PureComponent { } } + onBeforeUnload(event) { + if ( + this.props.database?.extra_json?.cancel_query_on_windows_unload && + this.props.latestQuery?.state === 'running' + ) { + event.preventDefault(); + this.stopQuery(); + } + } + onSqlChanged(sql) { this.setState({ sql }); this.setQueryEditorSqlWithDebounce(sql); @@ -564,7 +577,7 @@ class SqlEditor extends React.PureComponent { this.props.database || {}; const showMenu = allowCTAS || allowCVAS; - + const { theme } = this.props; const runMenuBtn = ( <Menu> {allowCTAS && ( @@ -639,7 +652,7 @@ class SqlEditor extends React.PureComponent { this.props.defaultQueryLimit, )} </span> - <Icon name="triangle-down" /> + <Icons.TriangleDown iconColor={theme.colors.grayscale.base} /> </a> </Dropdown> </LimitSelectStyled> @@ -667,7 +680,7 @@ class SqlEditor extends React.PureComponent { <ShareSqlLabQuery queryEditor={qe} /> </span> <Dropdown overlay={this.renderDropdown()} trigger="click"> - <Icon name="more-horiz" /> + <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} /> </Dropdown> </div> </StyledToolbar> @@ -783,4 +796,5 @@ function mapDispatchToProps(dispatch) { ); } -export default connect(mapStateToProps, mapDispatchToProps)(SqlEditor); +const themedSqlEditor = withTheme(SqlEditor); +export default connect(mapStateToProps, mapDispatchToProps)(themedSqlEditor); diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx index 59af1d345da1..3989a23b6a76 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx @@ -21,8 +21,10 @@ import PropTypes from 'prop-types'; import Button from 'src/components/Button'; import { t, styled, css } from '@superset-ui/core'; import Collapse from 'src/components/Collapse'; +import Icons from 'src/components/Icons'; import TableElement from './TableElement'; import TableSelector from '../../components/TableSelector'; +import { IconTooltip } from '../../components/IconTooltip'; const propTypes = { queryEditor: PropTypes.object.isRequired, @@ -134,6 +136,23 @@ export default class SqlEditorLeftBar extends React.PureComponent { this.props.actions.addTable(this.props.queryEditor, tableName, schemaName); } + renderExpandIconWithTooltip = ({ isActive }) => ( + <IconTooltip + css={css` + transform: rotate(90deg); + `} + aria-label="Collapse" + tooltip={t(`${isActive ? 'Collapse' : 'Expand'} table preview`)} + > + <Icons.RightOutlined + iconSize="s" + css={css` + transform: ${isActive ? 'rotateY(180deg)' : ''}; + `} + /> + </IconTooltip> + ); + render() { const shouldShowReset = window.location.search === '?reset=1'; const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above @@ -184,6 +203,7 @@ export default class SqlEditorLeftBar extends React.PureComponent { expandIconPosition="right" ghost onChange={this.onToggleTable} + expandIcon={this.renderExpandIconWithTooltip} > {this.props.tables.map(table => ( <TableElement diff --git a/superset-frontend/src/SqlLab/components/TableElement.jsx b/superset-frontend/src/SqlLab/components/TableElement.tsx similarity index 76% rename from superset-frontend/src/SqlLab/components/TableElement.jsx rename to superset-frontend/src/SqlLab/components/TableElement.tsx index 384771a96e83..d64ce2157fba 100644 --- a/superset-frontend/src/SqlLab/components/TableElement.jsx +++ b/superset-frontend/src/SqlLab/components/TableElement.tsx @@ -17,7 +17,6 @@ * under the License. */ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; import Collapse from 'src/components/Collapse'; import Card from 'src/components/Card'; import ButtonGroup from 'src/components/ButtonGroup'; @@ -25,23 +24,42 @@ import { t, styled } from '@superset-ui/core'; import { debounce } from 'lodash'; import { Tooltip } from 'src/components/Tooltip'; -import Icons from 'src/components/Icons'; import CopyToClipboard from '../../components/CopyToClipboard'; import { IconTooltip } from '../../components/IconTooltip'; -import ColumnElement from './ColumnElement'; +import ColumnElement, { ColumnKeyTypeType } from './ColumnElement'; import ShowSQL from './ShowSQL'; import ModalTrigger from '../../components/ModalTrigger'; import Loading from '../../components/Loading'; -const propTypes = { - table: PropTypes.object, - actions: PropTypes.object, -}; +interface Column { + name: string; + keys?: { type: ColumnKeyTypeType }[]; + type: string; +} -const defaultProps = { - actions: {}, - table: null, -}; +interface Table { + id: string; + name: string; + partitions?: { + partitionQuery: string; + latest: object[]; + }; + metadata?: Record<string, string>; + indexes?: object[]; + selectStar?: string; + view?: string; + isMetadataLoading: boolean; + isExtraMetadataLoading: boolean; + columns: Column[]; +} + +interface TableElementProps { + table: Table; + actions: { + removeDataPreview: (table: Table) => void; + removeTable: (table: Table) => void; + }; +} const StyledSpan = styled.span` color: ${({ theme }) => theme.colors.primary.dark1}; @@ -53,16 +71,14 @@ const StyledSpan = styled.span` const Fade = styled.div` transition: all ${({ theme }) => theme.transitionTiming}s; - opacity: ${props => (props.hovered ? 1 : 0)}; + opacity: ${(props: { hovered: boolean }) => (props.hovered ? 1 : 0)}; `; -const TableElement = props => { +const TableElement = ({ table, actions, ...props }: TableElementProps) => { const [sortColumns, setSortColumns] = useState(false); const [hovered, setHovered] = useState(false); - const { table, actions, isActive } = props; - - const setHover = hovered => { + const setHover = (hovered: boolean) => { debounce(() => setHovered(hovered), 100)(); }; @@ -76,7 +92,8 @@ const TableElement = props => { }; const renderWell = () => { - let header; + let partitions; + let metadata; if (table.partitions) { let partitionQuery; let partitionClipBoard; @@ -92,22 +109,41 @@ const TableElement = props => { /> ); } - let latest = Object.entries(table.partitions?.latest || []).map( - ([key, value]) => `${key}=${value}`, - ); - latest = latest.join('/'); - header = ( - <Card size="small"> - <div> - <small> - {t('latest partition:')} {latest} - </small>{' '} - {partitionClipBoard} - </div> - </Card> + const latest = Object.entries(table.partitions?.latest || []) + .map(([key, value]) => `${key}=${value}`) + .join('/'); + + partitions = ( + <div> + <small> + {t('latest partition:')} {latest} + </small>{' '} + {partitionClipBoard} + </div> ); } - return header; + + if (table.metadata) { + metadata = Object.entries(table.metadata).map(([key, value]) => ( + <div> + <small> + <strong>{key}:</strong> {value} + </small> + </div> + )); + } + + if (!partitions && (!metadata || !metadata.length)) { + // hide partition and metadata card view + return null; + } + + return ( + <Card size="small"> + {partitions} + {metadata} + </Card> + ); }; const renderControls = () => { @@ -142,9 +178,9 @@ const TableElement = props => { } onClick={toggleSortColumns} tooltip={ - !sortColumns - ? t('Sort columns alphabetically') - : t('Original table column order') + sortColumns + ? t('Original table column order') + : t('Sort columns alphabetically') } /> {table.selectStar && ( @@ -216,16 +252,10 @@ const TableElement = props => { if (table.columns) { cols = table.columns.slice(); if (sortColumns) { - cols.sort((a, b) => { + cols.sort((a: Column, b: Column) => { const colA = a.name.toUpperCase(); const colB = b.name.toUpperCase(); - if (colA < colB) { - return -1; - } - if (colA > colB) { - return 1; - } - return 0; + return colA < colB ? -1 : colA > colB ? 1 : 0; }); } } @@ -247,41 +277,17 @@ const TableElement = props => { return metadata; }; - const collapseExpandIcon = () => ( - <IconTooltip - style={{ - position: 'fixed', - right: '16px', - left: 'auto', - fontSize: '12px', - transform: 'rotate(90deg)', - display: 'flex', - alignItems: 'center', - }} - aria-label="Collapse" - tooltip={t(`${isActive ? 'Collapse' : 'Expand'} table preview`)} - > - <Icons.RightOutlined - iconSize="s" - style={isActive ? { transform: 'rotateY(180deg)' } : null} - /> - </IconTooltip> - ); - return ( <Collapse.Panel {...props} + key={table.id} header={renderHeader()} className="TableElement" forceRender - expandIcon={collapseExpandIcon} > {renderBody()} </Collapse.Panel> ); }; -TableElement.propTypes = propTypes; -TableElement.defaultProps = defaultProps; - export default TableElement; diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.js index 8052476f9bd6..92ffa0d6decf 100644 --- a/superset-frontend/src/SqlLab/reducers/getInitialState.js +++ b/superset-frontend/src/SqlLab/reducers/getInitialState.js @@ -117,6 +117,8 @@ export default function getInitialState({ foreignKeys, indexes, dataPreviewQueryId, + partitions, + metadata, } = tableSchema.description; const table = { dbId: tableSchema.database_id, @@ -133,6 +135,8 @@ export default function getInitialState({ primaryKey, foreignKeys, indexes, + partitions, + metadata, }; tables.push(table); }); diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js index daa06a97c88d..9e423ba7c65b 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js @@ -499,7 +499,10 @@ export default function sqlLabReducer(state = {}, action) { [actions.SET_DATABASES]() { const databases = {}; action.databases.forEach(db => { - databases[db.id] = db; + databases[db.id] = { + ...db, + extra_json: JSON.parse(db.extra || ''), + }; }); return { ...state, databases }; }, diff --git a/superset-frontend/src/SqlLab/types.ts b/superset-frontend/src/SqlLab/types.ts index 593695919c58..a50d66401971 100644 --- a/superset-frontend/src/SqlLab/types.ts +++ b/superset-frontend/src/SqlLab/types.ts @@ -30,6 +30,7 @@ export type QueryState = | 'running' | 'scheduled' | 'success' + | 'fetching' | 'timed_out'; export type Query = { diff --git a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx index c3b8bac8a4b9..00e7276a5864 100644 --- a/superset-frontend/src/addSlice/AddSliceContainer.test.tsx +++ b/superset-frontend/src/addSlice/AddSliceContainer.test.tsx @@ -19,19 +19,18 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import Button from 'src/components/Button'; -import Select from 'src/components/Select'; +import { Select } from 'src/components'; import AddSliceContainer, { AddSliceContainerProps, AddSliceContainerState, } from 'src/addSlice/AddSliceContainer'; -import VizTypeControl from 'src/explore/components/controls/VizTypeControl'; +import VizTypeGallery from 'src/explore/components/controls/VizTypeControl/VizTypeGallery'; import { styledMount as mount } from 'spec/helpers/theming'; +import { act } from 'spec/helpers/testing-library'; -const defaultProps = { - datasources: [ - { label: 'my first table', value: '1__table' }, - { label: 'another great table', value: '2__table' }, - ], +const datasource = { + value: '1', + label: 'table', }; describe('AddSliceContainer', () => { @@ -41,21 +40,19 @@ describe('AddSliceContainer', () => { AddSliceContainer >; - beforeEach(() => { - wrapper = mount(<AddSliceContainer {...defaultProps} />) as ReactWrapper< + beforeEach(async () => { + wrapper = mount(<AddSliceContainer />) as ReactWrapper< AddSliceContainerProps, AddSliceContainerState, AddSliceContainer >; - }); - - it('uses table as default visType', () => { - expect(wrapper.state().visType).toBe('table'); + // suppress a warning caused by some unusual async behavior in Icon + await act(() => new Promise(resolve => setTimeout(resolve, 0))); }); it('renders a select and a VizTypeControl', () => { expect(wrapper.find(Select)).toExist(); - expect(wrapper.find(VizTypeControl)).toExist(); + expect(wrapper.find(VizTypeGallery)).toExist(); }); it('renders a button', () => { @@ -68,12 +65,10 @@ describe('AddSliceContainer', () => { ).toHaveLength(1); }); - it('renders an enabled button if datasource is selected', () => { - const datasourceValue = defaultProps.datasources[0].value; + it('renders an enabled button if datasource and viz type is selected', () => { wrapper.setState({ - datasourceValue, - datasourceId: datasourceValue.split('__')[0], - datasourceType: datasourceValue.split('__')[1], + datasource, + visType: 'table', }); expect( wrapper.find(Button).find({ disabled: true }).hostNodes(), @@ -81,14 +76,12 @@ describe('AddSliceContainer', () => { }); it('formats explore url', () => { - const datasourceValue = defaultProps.datasources[0].value; wrapper.setState({ - datasourceValue, - datasourceId: datasourceValue.split('__')[0], - datasourceType: datasourceValue.split('__')[1], + datasource, + visType: 'table', }); const formattedUrl = - '/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221__table%22%7D'; + '/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221%22%7D'; expect(wrapper.instance().exploreUrl()).toBe(formattedUrl); }); }); diff --git a/superset-frontend/src/addSlice/AddSliceContainer.tsx b/superset-frontend/src/addSlice/AddSliceContainer.tsx index 22230cd0a2d6..8440cecf5a3c 100644 --- a/superset-frontend/src/addSlice/AddSliceContainer.tsx +++ b/superset-frontend/src/addSlice/AddSliceContainer.tsx @@ -17,36 +17,165 @@ * under the License. */ import React from 'react'; +import rison from 'rison'; +import { styled, t, SupersetClient, JsonResponse } from '@superset-ui/core'; +import { Steps } from 'src/common/components'; import Button from 'src/components/Button'; -import Select from 'src/components/Select'; -import { styled, t } from '@superset-ui/core'; +import { Select } from 'src/components'; +import { FormLabel } from 'src/components/Form'; +import { Tooltip } from 'src/components/Tooltip'; -import VizTypeControl from '../explore/components/controls/VizTypeControl'; +import VizTypeGallery, { + MAX_ADVISABLE_VIZ_GALLERY_WIDTH, +} from 'src/explore/components/controls/VizTypeControl/VizTypeGallery'; -interface Datasource { - label: string; - value: string; -} - -export type AddSliceContainerProps = { - datasources: Datasource[]; +type Dataset = { + id: number; + table_name: string; + description: string; + datasource_type: string; }; +export type AddSliceContainerProps = {}; + export type AddSliceContainerState = { - datasourceId?: string; - datasourceType?: string; - datasourceValue?: string; - visType: string; + datasource?: { label: string; value: string }; + visType: string | null; }; -const styleSelectContainer = { width: 600, marginBottom: '10px' }; +const ESTIMATED_NAV_HEIGHT = 56; +const ELEMENTS_EXCEPT_VIZ_GALLERY = ESTIMATED_NAV_HEIGHT + 250; + const StyledContainer = styled.div` - border-radius: ${({ theme }) => theme.gridUnit}px; - background-color: ${({ theme }) => theme.colors.grayscale.light5}; - padding: ${({ theme }) => theme.gridUnit * 6}px; - h3 { - padding-bottom: ${({ theme }) => theme.gridUnit * 3}px; - } + ${({ theme }) => ` + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + max-width: ${MAX_ADVISABLE_VIZ_GALLERY_WIDTH}px; + max-height: calc(100vh - ${ESTIMATED_NAV_HEIGHT}px); + border-radius: ${theme.gridUnit}px; + background-color: ${theme.colors.grayscale.light5}; + margin-left: auto; + margin-right: auto; + padding-left: ${theme.gridUnit * 4}px; + padding-right: ${theme.gridUnit * 4}px; + padding-bottom: ${theme.gridUnit * 4}px; + + h3 { + padding-bottom: ${theme.gridUnit * 3}px; + } + + & .dataset { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: ${theme.gridUnit * 2}px; + + & > div { + min-width: 200px; + width: 300px; + } + + & > span { + color: ${theme.colors.grayscale.light1}; + margin-left: ${theme.gridUnit * 4}px; + } + } + + & .viz-gallery { + border: 1px solid ${theme.colors.grayscale.light2}; + border-radius: ${theme.gridUnit}px; + margin: ${theme.gridUnit}px 0px; + max-height: calc(100vh - ${ELEMENTS_EXCEPT_VIZ_GALLERY}px); + flex: 1; + } + + & .footer { + flex: 1; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + + & > span { + color: ${theme.colors.grayscale.light1}; + margin-right: ${theme.gridUnit * 4}px; + } + } + + /* The following extra ampersands (&&&&) are used to boost selector specificity */ + + &&&& .ant-steps-item-tail { + display: none; + } + + &&&& .ant-steps-item-icon { + margin-right: ${theme.gridUnit * 2}px; + width: ${theme.gridUnit * 5}px; + height: ${theme.gridUnit * 5}px; + line-height: ${theme.gridUnit * 5}px; + } + + &&&& .ant-steps-item-title { + line-height: ${theme.gridUnit * 5}px; + } + + &&&& .ant-steps-item-content { + overflow: unset; + + .ant-steps-item-description { + margin-top: ${theme.gridUnit}px; + } + } + + &&&& .ant-tooltip-open { + display: inline; + } + + &&&& .ant-select-selector { + padding: 0; + } + + &&&& .ant-select-selection-placeholder { + padding-left: ${theme.gridUnit * 3}px; + } + `} +`; + +const TooltipContent = styled.div<{ hasDescription: boolean }>` + ${({ theme, hasDescription }) => ` + .tooltip-header { + font-size: ${ + hasDescription ? theme.typography.sizes.l : theme.typography.sizes.s + }px; + font-weight: ${ + hasDescription + ? theme.typography.weights.bold + : theme.typography.weights.normal + }; + } + + .tooltip-description { + margin-top: ${theme.gridUnit * 2}px; + display: -webkit-box; + -webkit-line-clamp: 20; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + `} +`; + +const StyledLabel = styled.span` + ${({ theme }) => ` + position: absolute; + left: ${theme.gridUnit * 3}px; + right: ${theme.gridUnit * 3}px; + overflow: hidden; + text-overflow: ellipsis; + `} `; export default class AddSliceContainer extends React.PureComponent< @@ -56,19 +185,22 @@ export default class AddSliceContainer extends React.PureComponent< constructor(props: AddSliceContainerProps) { super(props); this.state = { - visType: 'table', + visType: null, }; this.changeDatasource = this.changeDatasource.bind(this); this.changeVisType = this.changeVisType.bind(this); this.gotoSlice = this.gotoSlice.bind(this); + this.newLabel = this.newLabel.bind(this); + this.loadDatasources = this.loadDatasources.bind(this); + this.handleFilterOption = this.handleFilterOption.bind(this); } exploreUrl() { const formData = encodeURIComponent( JSON.stringify({ viz_type: this.state.visType, - datasource: this.state.datasourceValue, + datasource: this.state.datasource?.value, }), ); return `/superset/explore/?form_data=${formData}`; @@ -78,79 +210,140 @@ export default class AddSliceContainer extends React.PureComponent< window.location.href = this.exploreUrl(); } - changeDatasource(option: { value: string }) { - this.setState({ - datasourceValue: option.value, - datasourceId: option.value.split('__')[0], - }); + changeDatasource(datasource: { label: string; value: string }) { + this.setState({ datasource }); } - changeVisType(visType: string) { + changeVisType(visType: string | null) { this.setState({ visType }); } isBtnDisabled() { - return !(this.state.datasourceId && this.state.visType); + return !(this.state.datasource?.value && this.state.visType); + } + + newLabel(item: Dataset) { + return ( + <Tooltip + mouseEnterDelay={1} + placement="right" + title={ + <TooltipContent hasDescription={!!item.description}> + <div className="tooltip-header">{item.table_name}</div> + {item.description && ( + <div className="tooltip-description">{item.description}</div> + )} + </TooltipContent> + } + > + <StyledLabel>{item.table_name}</StyledLabel> + </Tooltip> + ); + } + + loadDatasources(search: string, page: number, pageSize: number) { + const query = rison.encode({ + columns: ['id', 'table_name', 'description', 'datasource_type'], + filters: [{ col: 'table_name', opr: 'ct', value: search }], + page, + page_size: pageSize, + order_column: 'table_name', + order_direction: 'asc', + }); + return SupersetClient.get({ + endpoint: `/api/v1/dataset/?q=${query}`, + }).then((response: JsonResponse) => { + const list: { + label: string; + value: string; + }[] = response.json.result + .map((item: Dataset) => ({ + value: `${item.id}__${item.datasource_type}`, + label: this.newLabel(item), + labelText: item.table_name, + })) + .sort((a: { labelText: string }, b: { labelText: string }) => + a.labelText.localeCompare(b.labelText), + ); + return { + data: list, + totalCount: response.json.count, + }; + }); + } + + handleFilterOption( + search: string, + option: { label: string; value: number; labelText: string }, + ) { + const searchValue = search.trim().toLowerCase(); + const { labelText } = option; + return labelText.toLowerCase().includes(searchValue); } render() { + const isButtonDisabled = this.isBtnDisabled(); return ( - <StyledContainer className="container"> + <StyledContainer> <h3>{t('Create a new chart')}</h3> - <div> - <p>{t('Choose a dataset')}</p> - <div style={styleSelectContainer}> - <Select - clearable={false} - ignoreAccents={false} - name="select-datasource" - onChange={this.changeDatasource} - options={this.props.datasources} - placeholder={t('Choose a dataset')} - value={ - this.state.datasourceValue - ? { - value: this.state.datasourceValue, - } - : undefined - } - width={600} - /> - </div> - <span className="text-muted"> - {t( - 'If the dataset you are looking for is not available in the list, follow the instructions on how to add it in the Superset tutorial.', - )}{' '} - <a - href="https://superset.apache.org/docs/creating-charts-dashboards/first-dashboard#adding-a-new-table" - rel="noopener noreferrer" - target="_blank" - > - <i className="fa fa-external-link" /> - </a> - </span> - </div> - <br /> - <div> - <p>{t('Choose a visualization type')}</p> - <VizTypeControl - name="select-vis-type" - onChange={this.changeVisType} - value={this.state.visType} - labelType="primary" + <Steps direction="vertical" size="small"> + <Steps.Step + title={<FormLabel>{t('Choose a dataset')}</FormLabel>} + status={this.state.datasource?.value ? 'finish' : 'process'} + description={ + <div className="dataset"> + <Select + autoFocus + ariaLabel={t('Dataset')} + name="select-datasource" + filterOption={this.handleFilterOption} + onChange={this.changeDatasource} + options={this.loadDatasources} + placeholder={t('Choose a dataset')} + showSearch + value={this.state.datasource} + /> + <span> + {t( + 'Instructions to add a dataset are available in the Superset tutorial.', + )}{' '} + <a + href="https://superset.apache.org/docs/creating-charts-dashboards/first-dashboard#adding-a-new-table" + rel="noopener noreferrer" + target="_blank" + > + <i className="fa fa-external-link" /> + </a> + </span> + </div> + } + /> + <Steps.Step + title={<FormLabel>{t('Choose chart type')}</FormLabel>} + status={this.state.visType ? 'finish' : 'process'} + description={ + <VizTypeGallery + className="viz-gallery" + onChange={this.changeVisType} + selectedViz={this.state.visType} + /> + } /> + </Steps> + <div className="footer"> + {isButtonDisabled && ( + <span> + {t('Please select both a Dataset and a Chart type to proceed')} + </span> + )} + <Button + buttonStyle="primary" + disabled={isButtonDisabled} + onClick={this.gotoSlice} + > + {t('Create new chart')} + </Button> </div> - <br /> - <hr /> - <Button - buttonStyle="primary" - disabled={this.isBtnDisabled()} - onClick={this.gotoSlice} - > - {t('Create new chart')} - </Button> - <br /> - <br /> </StyledContainer> ); } diff --git a/superset-frontend/src/addSlice/App.tsx b/superset-frontend/src/addSlice/App.tsx index 9602670a0890..900cc36f446c 100644 --- a/superset-frontend/src/addSlice/App.tsx +++ b/superset-frontend/src/addSlice/App.tsx @@ -39,7 +39,7 @@ initFeatureFlags(bootstrapData.common.feature_flags); const App = () => ( <ThemeProvider theme={theme}> <DynamicPluginProvider> - <AddSliceContainer datasources={bootstrapData.datasources} /> + <AddSliceContainer /> </DynamicPluginProvider> </ThemeProvider> ); diff --git a/superset-frontend/src/chart/Chart.jsx b/superset-frontend/src/chart/Chart.jsx index d9f7e93ccf7b..d66dd86b15e0 100644 --- a/superset-frontend/src/chart/Chart.jsx +++ b/superset-frontend/src/chart/Chart.jsx @@ -22,8 +22,9 @@ import Alert from 'src/components/Alert'; import { styled, logging, t } from '@superset-ui/core'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants'; import Button from 'src/components/Button'; -import Loading from '../components/Loading'; +import Loading from 'src/components/Loading'; import ErrorBoundary from '../components/ErrorBoundary'; import ChartRenderer from './ChartRenderer'; import { ChartErrorMessage } from './ChartErrorMessage'; @@ -33,7 +34,7 @@ const propTypes = { annotationData: PropTypes.object, actions: PropTypes.object, chartId: PropTypes.number.isRequired, - datasource: PropTypes.object.isRequired, + datasource: PropTypes.object, // current chart is included by dashboard dashboardId: PropTypes.number, // original selected values for FilterBox viz @@ -164,10 +165,32 @@ class Chart extends React.PureComponent { } renderErrorMessage(queryResponse) { - const { chartId, chartAlert, chartStackTrace, dashboardId } = this.props; + const { + chartId, + chartAlert, + chartStackTrace, + datasource, + dashboardId, + height, + } = this.props; const error = queryResponse?.errors?.[0]; const message = chartAlert || queryResponse?.message; + + // if datasource is still loading, don't render JS errors + if (chartAlert && datasource === PLACEHOLDER_DATASOURCE) { + return ( + <Styles + data-ui-anchor="chart" + className="chart-container" + data-test="chart-container" + height={height} + > + <Loading /> + </Styles> + ); + } + return ( <ChartErrorMessage chartId={chartId} @@ -198,6 +221,7 @@ class Chart extends React.PureComponent { if (chartStatus === 'failed') { return queriesResponse.map(item => this.renderErrorMessage(item)); } + if (errorMessage) { return ( <Alert diff --git a/superset-frontend/src/chart/ChartRenderer.jsx b/superset-frontend/src/chart/ChartRenderer.jsx index f40e46e83cba..bbc4213817c3 100644 --- a/superset-frontend/src/chart/ChartRenderer.jsx +++ b/superset-frontend/src/chart/ChartRenderer.jsx @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { snakeCase } from 'lodash'; +import { snakeCase, isEqual } from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; import { SuperChart, logging, Behavior } from '@superset-ui/core'; -import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger/LogUtils'; +import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils'; const propTypes = { annotationData: PropTypes.object, actions: PropTypes.object, chartId: PropTypes.number.isRequired, - datasource: PropTypes.object.isRequired, + datasource: PropTypes.object, initialValues: PropTypes.object, formData: PropTypes.object.isRequired, height: PropTypes.number, @@ -93,6 +93,7 @@ class ChartRenderer extends React.Component { nextProps.queriesResponse !== this.props.queriesResponse; return ( this.hasQueryResponseChange || + !isEqual(nextProps.datasource, this.props.datasource) || nextProps.annotationData !== this.props.annotationData || nextProps.ownState !== this.props.ownState || nextProps.filterState !== this.props.filterState || @@ -182,8 +183,8 @@ class ChartRenderer extends React.Component { const { width, height, - annotationData, datasource, + annotationData, initialValues, ownState, filterState, diff --git a/superset-frontend/src/chart/chartAction.js b/superset-frontend/src/chart/chartAction.js index 9be1b6a6c6d1..5052416df37e 100644 --- a/superset-frontend/src/chart/chartAction.js +++ b/superset-frontend/src/chart/chartAction.js @@ -145,11 +145,12 @@ const legacyChartDataRequest = async ( 'GET' && isFeatureEnabled(FeatureFlag.CLIENT_CACHE) ? SupersetClient.get : SupersetClient.post; - return clientMethod(querySettings).then(({ json }) => + return clientMethod(querySettings).then(({ json, response }) => // Make the legacy endpoint return a payload that corresponds to the // V1 chart data endpoint response signature. ({ - result: [json], + response, + json: { result: [json] }, }), ); }; @@ -196,7 +197,8 @@ const v1ChartDataRequest = async ( headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }; - return SupersetClient.post(querySettings).then(({ json }) => json); + + return SupersetClient.post(querySettings); }; export async function getChartDataRequest({ @@ -390,14 +392,25 @@ export function exploreJSON( dispatch(chartUpdateStarted(controller, formData, key)); const chartDataRequestCaught = chartDataRequest - .then(response => { - const queriesResponse = response.result; + .then(({ response, json }) => { if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) { // deal with getChartDataRequest transforming the response data - const result = 'result' in response ? response.result[0] : response; - return waitForAsyncData(result); + const result = 'result' in json ? json.result[0] : json; + switch (response.status) { + case 200: + // Query results returned synchronously, meaning query was already cached. + return Promise.resolve([result]); + case 202: + // Query is running asynchronously and we must await the results + return waitForAsyncData(result); + default: + throw new Error( + `Received unexpected response status (${response.status}) while fetching chart data`, + ); + } } - return queriesResponse; + + return json.result; }) .then(queriesResponse => { queriesResponse.forEach(resultItem => @@ -541,11 +554,11 @@ export function postChartFormData( export function redirectSQLLab(formData) { return dispatch => { getChartDataRequest({ formData, resultFormat: 'json', resultType: 'query' }) - .then(({ result }) => { + .then(({ json }) => { const redirectUrl = '/superset/sqllab/'; const payload = { datasourceKey: formData.datasource, - sql: result[0].query, + sql: json.result[0].query, }; postForm(redirectUrl, payload); }) diff --git a/superset-frontend/src/chart/chartReducer.ts b/superset-frontend/src/chart/chartReducer.ts index d6e42dfa87f6..b0de1a668053 100644 --- a/superset-frontend/src/chart/chartReducer.ts +++ b/superset-frontend/src/chart/chartReducer.ts @@ -19,6 +19,7 @@ /* eslint camelcase: 0 */ import { t } from '@superset-ui/core'; import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate'; +import { DatasourcesAction } from 'src/dashboard/actions/datasources'; import { ChartState } from 'src/explore/types'; import { getFormDataFromControls } from 'src/explore/controlUtils'; import { now } from 'src/modules/dates'; @@ -196,6 +197,24 @@ export default function chartReducer( if (action.type === HYDRATE_DASHBOARD) { return { ...action.data.charts }; } + if (action.type === DatasourcesAction.SET_DATASOURCES) { + return Object.fromEntries( + Object.entries(charts).map(([chartId, chart]) => [ + chartId, + // if render has failed, clear error message, + // which will trigger a re-render + chart.chartStatus === 'failed' + ? { + ...chart, + chartStatus: '', + chartStackTrace: null, + chartAlert: null, + } + : chart, + ]), + ); + } + if (action.type in actionHandlers) { return { ...charts, diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx index c98099645a7b..04c3142c7f70 100644 --- a/superset-frontend/src/common/components/index.tsx +++ b/superset-frontend/src/common/components/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { RefObject } from 'react'; import { styled } from '@superset-ui/core'; import { Dropdown, @@ -42,7 +42,6 @@ export { Dropdown, Form, Empty, - Modal, Typography, Tree, Popover, @@ -50,13 +49,16 @@ export { Row, Space, Skeleton, + Steps, Switch, Tag, Tabs, Tooltip, + Upload, Input as AntdInput, } from 'antd'; export { Card as AntdCard } from 'antd'; +export { default as Modal, ModalProps } from 'antd/lib/modal'; export { FormInstance } from 'antd/lib/form'; export { RadioChangeEvent } from 'antd/lib/radio'; export { TreeProps } from 'antd/lib/tree'; @@ -180,11 +182,14 @@ export const StyledSubMenu = styled(AntdMenu.SubMenu)` & > .ant-menu-submenu-title { padding: 0 ${({ theme }) => theme.gridUnit * 6}px 0 ${({ theme }) => theme.gridUnit * 3}px !important; - svg { + span[role='img'] { position: absolute; - top: ${({ theme }) => theme.gridUnit * 4 + 7}px; - right: ${({ theme }) => theme.gridUnit}px; - width: ${({ theme }) => theme.gridUnit * 6}px; + right: ${({ theme }) => -theme.gridUnit + -2}px; + top: ${({ theme }) => theme.gridUnit * 5.25}px; + svg { + font-size: ${({ theme }) => theme.gridUnit * 6}px; + color: ${({ theme }) => theme.colors.grayscale.base}; + } } & > span { position: relative; @@ -213,6 +218,10 @@ export const MainNav = Object.assign(StyledNav, { ItemGroup: AntdMenu.ItemGroup, }); +interface ExtendedDropDownProps extends DropDownProps { + ref?: RefObject<HTMLDivElement>; +} + export const Input = styled(AntdInput)` border: 1px solid ${({ theme }) => theme.colors.secondary.light3}; border-radius: ${({ theme }) => theme.borderRadius}px; @@ -228,11 +237,11 @@ export const TextArea = styled(AntdInput.TextArea)` border-radius: ${({ theme }) => theme.borderRadius}px; `; -export const NoAnimationDropdown = (props: DropDownProps) => ( - <Dropdown - overlayStyle={{ zIndex: 4000, animationDuration: '0s' }} - {...props} - /> +// @z-index-below-dashboard-header (100) - 1 = 99 +export const NoAnimationDropdown = ( + props: ExtendedDropDownProps & { children?: React.ReactNode }, +) => ( + <Dropdown overlayStyle={{ zIndex: 99, animationDuration: '0s' }} {...props} /> ); export const ThinSkeleton = styled(Skeleton)` diff --git a/superset-frontend/src/common/hooks/apiResources/dashboards.ts b/superset-frontend/src/common/hooks/apiResources/dashboards.ts index 553049aac6a7..99707c19f6f8 100644 --- a/superset-frontend/src/common/hooks/apiResources/dashboards.ts +++ b/superset-frontend/src/common/hooks/apiResources/dashboards.ts @@ -17,7 +17,8 @@ * under the License. */ -import Dashboard from 'src/types/Dashboard'; +import { Dashboard, Datasource } from 'src/dashboard/types'; +import { Chart } from 'src/types/Chart'; import { useApiV1Resource, useTransformedResource } from './apiResources'; export const useDashboard = (idOrSlug: string | number) => @@ -33,10 +34,10 @@ export const useDashboard = (idOrSlug: string | number) => // gets the chart definitions for a dashboard export const useDashboardCharts = (idOrSlug: string | number) => - useApiV1Resource(`/api/v1/dashboard/${idOrSlug}/charts`); + useApiV1Resource<Chart[]>(`/api/v1/dashboard/${idOrSlug}/charts`); // gets the datasets for a dashboard // important: this endpoint only returns the fields in the dataset // that are necessary for rendering the given dashboard export const useDashboardDatasets = (idOrSlug: string | number) => - useApiV1Resource(`/api/v1/dashboard/${idOrSlug}/datasets`); + useApiV1Resource<Datasource[]>(`/api/v1/dashboard/${idOrSlug}/datasets`); diff --git a/superset-frontend/src/common/hooks/useComponentDidUpdate/index.ts b/superset-frontend/src/common/hooks/useComponentDidUpdate/index.ts new file mode 100644 index 000000000000..fa0105807cdd --- /dev/null +++ b/superset-frontend/src/common/hooks/useComponentDidUpdate/index.ts @@ -0,0 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './useComponentDidUpdate'; diff --git a/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.test.ts b/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.test.ts new file mode 100644 index 000000000000..a1615ec2793c --- /dev/null +++ b/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.test.ts @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { renderHook } from '@testing-library/react-hooks'; +import { useComponentDidUpdate } from './useComponentDidUpdate'; + +test('the effect should not be executed on the first render', () => { + const effect = jest.fn(); + const hook = renderHook(props => useComponentDidUpdate(props.effect), { + initialProps: { effect }, + }); + expect(effect).toBeCalledTimes(0); + const changedEffect = jest.fn(); + hook.rerender({ effect: changedEffect }); + expect(changedEffect).toBeCalledTimes(1); +}); diff --git a/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.ts b/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.ts new file mode 100644 index 000000000000..aa84568e293c --- /dev/null +++ b/superset-frontend/src/common/hooks/useComponentDidUpdate/useComponentDidUpdate.ts @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EffectCallback, useEffect, useRef } from 'react'; + +export const useComponentDidUpdate = (effect: EffectCallback) => { + const isMountedRef = useRef(false); + useEffect(() => { + if (isMountedRef.current) { + effect(); + } else { + isMountedRef.current = true; + } + }, [effect]); +}; diff --git a/superset-frontend/src/common/hooks/useElementOnScreen/index.ts b/superset-frontend/src/common/hooks/useElementOnScreen/index.ts new file mode 100644 index 000000000000..6d2c66eb457d --- /dev/null +++ b/superset-frontend/src/common/hooks/useElementOnScreen/index.ts @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useEffect, useRef, useState, RefObject } from 'react'; + +export function useElementOnScreen<T extends Element>( + options: IntersectionObserverInit, +): [RefObject<T>, boolean] { + const containerRef = useRef<T>(null); + const [isSticky, setIsSticky] = useState(false); + + const callback = (entries: IntersectionObserverEntry[]) => { + const [entry] = entries; + setIsSticky(entry.intersectionRatio < 1); + }; + + useEffect(() => { + const observer = new IntersectionObserver(callback, options); + const element = containerRef.current; + if (element) { + observer.observe(element); + } + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, [containerRef, options]); + + return [containerRef, isSticky]; +} diff --git a/superset-frontend/src/components/Alert/index.tsx b/superset-frontend/src/components/Alert/index.tsx index 89a782f75b09..6ed9003cef45 100644 --- a/superset-frontend/src/components/Alert/index.tsx +++ b/superset-frontend/src/components/Alert/index.tsx @@ -22,7 +22,6 @@ import { AlertProps as AntdAlertProps, } from 'src/common/components'; import { useTheme } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; import Icons from 'src/components/Icons'; export type AlertProps = PropsWithChildren< @@ -61,7 +60,7 @@ export default function Alert(props: AlertProps) { role="alert" showIcon={showIcon} icon={<AlertIcon aria-label={`${type} icon`} />} - closeText={closable && <Icon name="x-small" aria-label="close icon" />} + closeText={closable && <Icons.XSmall aria-label="close icon" />} css={{ marginBottom: roomBelow ? gridUnit * 4 : 0, padding: `${gridUnit * 2}px ${gridUnit * 3}px`, diff --git a/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx b/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx index 17941445c9ac..6f3d5e33ab2a 100644 --- a/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx +++ b/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx @@ -128,8 +128,8 @@ describe('AlteredSliceTag', () => { ); const th = getTableWrapperFromModalBody(modalBody).find('th'); expect(th).toHaveLength(3); - ['Control', 'Before', 'After'].forEach((v, i) => { - expect(th.find('span').get(i).props.children[0]).toBe(v); + ['Control', 'Before', 'After'].forEach(async (v, i) => { + await expect(th.find('span').get(i).props.children[0]).toBe(v); }); }); diff --git a/superset-frontend/src/components/AnchorLink/index.jsx b/superset-frontend/src/components/AnchorLink/index.jsx index 42f93c35371d..16be622bdc03 100644 --- a/superset-frontend/src/components/AnchorLink/index.jsx +++ b/superset-frontend/src/components/AnchorLink/index.jsx @@ -80,11 +80,11 @@ class AnchorLink extends React.PureComponent { <span className="anchor-link-container" id={anchorLinkId}> {showShortLinkButton && ( <URLShortLinkButton - url={getDashboardUrl( - window.location.pathname, + url={getDashboardUrl({ + pathname: window.location.pathname, filters, - anchorLinkId, - )} + hash: anchorLinkId, + })} emailSubject={t('Superset chart')} emailContent={t('Check out this chart in dashboard:')} placement={placement} diff --git a/superset-frontend/src/components/Button/index.tsx b/superset-frontend/src/components/Button/index.tsx index 1f9d737836b0..62e1214d3dff 100644 --- a/superset-frontend/src/components/Button/index.tsx +++ b/superset-frontend/src/components/Button/index.tsx @@ -62,6 +62,7 @@ export interface ButtonProps { href?: string; htmlType?: 'button' | 'submit' | 'reset'; cta?: boolean; + loading?: boolean | { delay?: number | undefined } | undefined; } export default function Button(props: ButtonProps) { diff --git a/superset-frontend/src/components/CertifiedIcon/index.tsx b/superset-frontend/src/components/CertifiedIcon/index.tsx index 49278a6cbce5..4aa0dad236b1 100644 --- a/superset-frontend/src/components/CertifiedIcon/index.tsx +++ b/superset-frontend/src/components/CertifiedIcon/index.tsx @@ -18,19 +18,19 @@ */ import React from 'react'; import { t, supersetTheme } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; +import Icons, { IconType } from 'src/components/Icons'; import { Tooltip } from 'src/components/Tooltip'; export interface CertifiedIconProps { certifiedBy?: string; details?: string; - size?: number; + size?: IconType['iconSize']; } function CertifiedIcon({ certifiedBy, details, - size = 24, + size = 'l', }: CertifiedIconProps) { return ( <Tooltip @@ -46,11 +46,9 @@ function CertifiedIcon({ </> } > - <Icon - color={supersetTheme.colors.primary.base} - height={size} - width={size} - name="certified" + <Icons.Certified + iconColor={supersetTheme.colors.primary.base} + iconSize={size} /> </Tooltip> ); diff --git a/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx index e932b4593859..875d5f734d87 100644 --- a/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx +++ b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx @@ -17,8 +17,9 @@ * under the License. */ import React from 'react'; +import { useTheme } from '@superset-ui/core'; import Button from 'src/components/Button'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import ToastPresenter from 'src/messageToasts/containers/ToastPresenter'; import CopyToClipboard from '.'; @@ -28,9 +29,10 @@ export default { }; export const InteractiveCopyToClipboard = ({ copyNode, ...rest }: any) => { + const theme = useTheme(); let node = <Button>Copy</Button>; if (copyNode === 'Icon') { - node = <Icon name="copy" />; + node = <Icons.Copy iconColor={theme.colors.grayscale.base} />; } else if (copyNode === 'Text') { node = <span role="button">Copy</span>; } diff --git a/superset-frontend/src/components/CronPicker/CronPicker.tsx b/superset-frontend/src/components/CronPicker/CronPicker.tsx index d85964064408..631f7f24b430 100644 --- a/superset-frontend/src/components/CronPicker/CronPicker.tsx +++ b/superset-frontend/src/components/CronPicker/CronPicker.tsx @@ -31,7 +31,7 @@ export const LOCALE: Locale = { emptyWeekDays: t('every day of the week'), emptyWeekDaysShort: t('day of the week'), emptyHours: t('every hour'), - emptyMinutes: t('every minute UTC'), + emptyMinutes: t('every minute'), emptyMinutesForHourPeriod: t('every'), yearOption: t('year'), monthOption: t('month'), @@ -48,7 +48,7 @@ export const LOCALE: Locale = { prefixHours: t('at'), prefixMinutes: t(':'), prefixMinutesForHourPeriod: t('at'), - suffixMinutesForHourPeriod: t('minute(s) UTC'), + suffixMinutesForHourPeriod: t('minute(s)'), errorInvalidCron: t('Invalid cron expression'), clearButtonText: t('Clear'), weekDays: [ diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 32c2bce0b8d1..6d4abb3fd963 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -26,11 +26,11 @@ import DatabaseSelector from '.'; const SupersetClientGet = jest.spyOn(SupersetClient, 'get'); const createProps = () => ({ - dbId: 1, + db: { id: 1, database_name: 'test', backend: 'postgresql' }, formMode: false, isDatabaseSelectEnabled: true, readOnly: false, - schema: 'public', + schema: undefined, sqlLabMode: true, getDbList: jest.fn(), getTableList: jest.fn(), @@ -38,7 +38,7 @@ const createProps = () => ({ onDbChange: jest.fn(), onSchemaChange: jest.fn(), onSchemasLoad: jest.fn(), - onChange: jest.fn(), + onUpdate: jest.fn(), }); beforeEach(() => { @@ -129,7 +129,7 @@ beforeEach(() => { changed_on: '2021-03-09T19:02:07.141095', changed_on_delta_humanized: 'a day ago', created_by: null, - database_name: 'examples', + database_name: 'test', explore_database_id: 1, expose_in_sqllab: true, force_ctas_schema: null, @@ -153,50 +153,62 @@ test('Refresh should work', async () => { render(<DatabaseSelector {...props} />); + const select = screen.getByRole('combobox', { + name: 'Select a schema', + }); + + userEvent.click(select); + await waitFor(() => { - expect(SupersetClientGet).toBeCalledTimes(2); - expect(props.getDbList).toBeCalledTimes(1); + expect(SupersetClientGet).toBeCalledTimes(1); + expect(props.getDbList).toBeCalledTimes(0); expect(props.getTableList).toBeCalledTimes(0); expect(props.handleError).toBeCalledTimes(0); expect(props.onDbChange).toBeCalledTimes(0); expect(props.onSchemaChange).toBeCalledTimes(0); - expect(props.onSchemasLoad).toBeCalledTimes(1); - expect(props.onChange).toBeCalledTimes(0); + expect(props.onSchemasLoad).toBeCalledTimes(0); + expect(props.onUpdate).toBeCalledTimes(0); }); - userEvent.click(screen.getByRole('button')); + userEvent.click(screen.getByRole('button', { name: 'refresh' })); await waitFor(() => { - expect(SupersetClientGet).toBeCalledTimes(3); - expect(props.getDbList).toBeCalledTimes(1); + expect(SupersetClientGet).toBeCalledTimes(2); + expect(props.getDbList).toBeCalledTimes(0); expect(props.getTableList).toBeCalledTimes(0); expect(props.handleError).toBeCalledTimes(0); - expect(props.onDbChange).toBeCalledTimes(1); - expect(props.onSchemaChange).toBeCalledTimes(1); + expect(props.onDbChange).toBeCalledTimes(0); + expect(props.onSchemaChange).toBeCalledTimes(0); expect(props.onSchemasLoad).toBeCalledTimes(2); - expect(props.onChange).toBeCalledTimes(1); + expect(props.onUpdate).toBeCalledTimes(0); }); }); test('Should database select display options', async () => { const props = createProps(); render(<DatabaseSelector {...props} />); - const selector = await screen.findByText('Database:'); - expect(selector).toBeInTheDocument(); - expect(selector.parentElement).toHaveTextContent( - 'Database:postgresql examples', - ); + const select = screen.getByRole('combobox', { + name: 'Select a database', + }); + expect(select).toBeInTheDocument(); + userEvent.click(select); + expect( + await screen.findByRole('option', { name: 'postgresql: test' }), + ).toBeInTheDocument(); }); test('Should schema select display options', async () => { const props = createProps(); render(<DatabaseSelector {...props} />); - - const selector = await screen.findByText('Schema:'); - expect(selector).toBeInTheDocument(); - expect(selector.parentElement).toHaveTextContent('Schema: public'); - - userEvent.click(screen.getByRole('button')); - - expect(await screen.findByText('Select a schema (2)')).toBeInTheDocument(); + const select = screen.getByRole('combobox', { + name: 'Select a schema', + }); + expect(select).toBeInTheDocument(); + userEvent.click(select); + expect( + await screen.findByRole('option', { name: 'public' }), + ).toBeInTheDocument(); + expect( + await screen.findByRole('option', { name: 'information_schema' }), + ).toBeInTheDocument(); }); diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 75103aac3ce0..c96fba7c81e3 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -16,63 +16,56 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode, useState, useMemo } from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; import rison from 'rison'; -import { Select } from 'src/components/Select'; -import Label from 'src/components/Label'; +import { Select } from 'src/components'; +import { FormLabel } from 'src/components/Form'; import RefreshLabel from 'src/components/RefreshLabel'; -import SupersetAsyncSelect from 'src/components/AsyncSelect'; - -const FieldTitle = styled.p` - color: ${({ theme }) => theme.colors.secondary.light2}; - font-size: ${({ theme }) => theme.typography.sizes.s}px; - margin: 20px 0 10px 0; - text-transform: uppercase; -`; const DatabaseSelectorWrapper = styled.div` - .fa-refresh { - padding-left: 9px; - } + ${({ theme }) => ` + .refresh { + display: flex; + align-items: center; + width: 30px; + margin-left: ${theme.gridUnit}px; + margin-top: ${theme.gridUnit * 5}px; + } - .refresh-col { - display: flex; - align-items: center; - width: 30px; - margin-left: ${({ theme }) => theme.gridUnit}px; - } + .section { + display: flex; + flex-direction: row; + align-items: center; + } - .section { - padding-bottom: 5px; - display: flex; - flex-direction: row; - } + .select { + flex: 1; + } - .select { - flex-grow: 1; - } + & > div { + margin-bottom: ${theme.gridUnit * 4}px; + } + `} `; -const DatabaseOption = styled.span` - display: inline-flex; - align-items: center; -`; +type DatabaseValue = { label: string; value: number }; + +type SchemaValue = { label: string; value: string }; interface DatabaseSelectorProps { - dbId: number; + db?: { id: number; database_name: string; backend: string }; formMode?: boolean; getDbList?: (arg0: any) => {}; - getTableList?: (dbId: number, schema: string, force: boolean) => {}; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; onDbChange?: (db: any) => void; - onSchemaChange?: (arg0?: any) => {}; + onSchemaChange?: (schema?: string) => void; onSchemasLoad?: (schemas: Array<object>) => void; readOnly?: boolean; schema?: string; sqlLabMode?: boolean; - onChange?: ({ + onUpdate?: ({ dbId, schema, }: { @@ -83,13 +76,12 @@ interface DatabaseSelectorProps { } export default function DatabaseSelector({ - dbId, + db, formMode = false, getDbList, - getTableList, handleError, isDatabaseSelectEnabled = true, - onChange, + onUpdate, onDbChange, onSchemaChange, onSchemasLoad, @@ -97,193 +89,189 @@ export default function DatabaseSelector({ schema, sqlLabMode = false, }: DatabaseSelectorProps) { - const [currentDbId, setCurrentDbId] = useState(dbId); - const [currentSchema, setCurrentSchema] = useState<string | undefined>( - schema, + const [currentDb, setCurrentDb] = useState( + db + ? { label: `${db.backend}: ${db.database_name}`, value: db.id } + : undefined, + ); + const [currentSchema, setCurrentSchema] = useState<SchemaValue | undefined>( + schema ? { label: schema, value: schema } : undefined, ); - const [schemaLoading, setSchemaLoading] = useState(false); - const [schemaOptions, setSchemaOptions] = useState([]); + const [refresh, setRefresh] = useState(0); - function fetchSchemas(databaseId: number, forceRefresh = false) { - const actualDbId = databaseId || dbId; - if (actualDbId) { - setSchemaLoading(true); - const queryParams = rison.encode({ - force: Boolean(forceRefresh), - }); - const endpoint = `/api/v1/database/${actualDbId}/schemas/?q=${queryParams}`; - return SupersetClient.get({ endpoint }) - .then(({ json }) => { + const loadSchemas = useMemo( + () => async (): Promise<{ + data: SchemaValue[]; + totalCount: number; + }> => { + if (currentDb) { + const queryParams = rison.encode({ force: refresh > 0 }); + const endpoint = `/api/v1/database/${currentDb.value}/schemas/?q=${queryParams}`; + + // TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes. + return SupersetClient.get({ endpoint }).then(({ json }) => { const options = json.result.map((s: string) => ({ value: s, label: s, title: s, })); - setSchemaOptions(options); - setSchemaLoading(false); if (onSchemasLoad) { onSchemasLoad(options); } - }) - .catch(() => { - setSchemaOptions([]); - setSchemaLoading(false); - handleError(t('Error while fetching schema list')); + return { + data: options, + totalCount: options.length, + }; }); - } - return Promise.resolve(); - } - - useEffect(() => { - if (currentDbId) { - fetchSchemas(currentDbId); - } - }, [currentDbId]); + } + return { + data: [], + totalCount: 0, + }; + }, + [currentDb, refresh, onSchemasLoad], + ); - function onSelectChange({ dbId, schema }: { dbId: number; schema?: string }) { - setCurrentDbId(dbId); + function onSelectChange({ + db, + schema, + }: { + db: DatabaseValue; + schema?: SchemaValue; + }) { + setCurrentDb(db); setCurrentSchema(schema); - if (onChange) { - onChange({ dbId, schema, tableName: undefined }); - } - } - - function dbMutator(data: any) { - if (getDbList) { - getDbList(data.result); - } - if (data.result.length === 0) { - handleError(t("It seems you don't have access to any database")); + if (onUpdate) { + onUpdate({ + dbId: db.value, + schema: schema?.value, + tableName: undefined, + }); } - return data.result.map((row: any) => ({ - ...row, - // label is used for the typeahead - label: `${row.backend} ${row.database_name}`, - })); } - function changeDataBase(db: any, force = false) { - const dbId = db ? db.id : null; - setSchemaOptions([]); + function changeDataBase(selectedValue: DatabaseValue) { + const actualDb = selectedValue || db; if (onSchemaChange) { - onSchemaChange(null); + onSchemaChange(undefined); } if (onDbChange) { onDbChange(db); } - fetchSchemas(dbId, force); - onSelectChange({ dbId, schema: undefined }); + onSelectChange({ db: actualDb, schema: undefined }); } - function changeSchema(schemaOpt: any, force = false) { - const schema = schemaOpt ? schemaOpt.value : null; + function changeSchema(schema: SchemaValue) { if (onSchemaChange) { - onSchemaChange(schema); + onSchemaChange(schema.value); } - setCurrentSchema(schema); - onSelectChange({ dbId: currentDbId, schema }); - if (getTableList) { - getTableList(currentDbId, schema, force); + if (currentDb) { + onSelectChange({ db: currentDb, schema }); } } - function renderDatabaseOption(db: any) { - return ( - <DatabaseOption title={db.database_name}> - <Label type="default">{db.backend}</Label> {db.database_name} - </DatabaseOption> - ); - } - function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) { return ( <div className="section"> <span className="select">{select}</span> - <span className="refresh-col">{refreshBtn}</span> + <span className="refresh">{refreshBtn}</span> </div> ); } - function renderDatabaseSelect() { - const queryParams = rison.encode({ - order_columns: 'database_name', - order_direction: 'asc', - page: 0, - page_size: -1, - ...(formMode || !sqlLabMode - ? {} - : { - filters: [ - { - col: 'expose_in_sqllab', - opr: 'eq', - value: true, - }, - ], + const loadDatabases = useMemo( + () => async ( + search: string, + page: number, + pageSize: number, + ): Promise<{ + data: DatabaseValue[]; + totalCount: number; + }> => { + const queryParams = rison.encode({ + order_columns: 'database_name', + order_direction: 'asc', + page, + page_size: pageSize, + ...(formMode || !sqlLabMode + ? { filters: [{ col: 'database_name', opr: 'ct', value: search }] } + : { + filters: [ + { col: 'database_name', opr: 'ct', value: search }, + { + col: 'expose_in_sqllab', + opr: 'eq', + value: true, + }, + ], + }), + }); + const endpoint = `/api/v1/database/?q=${queryParams}`; + return SupersetClient.get({ endpoint }).then(({ json }) => { + const { result } = json; + if (getDbList) { + getDbList(result); + } + if (result.length === 0) { + handleError(t("It seems you don't have access to any database")); + } + const options = result.map( + (row: { backend: string; database_name: string; id: number }) => ({ + label: `${row.backend}: ${row.database_name}`, + value: row.id, }), - }); + ); + return { + data: options, + totalCount: options.length, + }; + }); + }, + [formMode, getDbList, handleError, sqlLabMode], + ); + function renderDatabaseSelect() { return renderSelectRow( - <SupersetAsyncSelect + <Select + ariaLabel={t('Select a database')} data-test="select-database" - dataEndpoint={`/api/v1/database/?q=${queryParams}`} - onChange={(db: any) => changeDataBase(db)} - onAsyncError={() => - handleError(t('Error while fetching database list')) - } - clearable={false} - value={currentDbId} - valueKey="id" - valueRenderer={(db: any) => ( - <div> - <span className="text-muted m-r-5">{t('Database:')}</span> - {renderDatabaseOption(db)} - </div> - )} - optionRenderer={renderDatabaseOption} - mutator={dbMutator} + header={<FormLabel>{t('Database')}</FormLabel>} + onChange={changeDataBase} + value={currentDb} placeholder={t('Select a database')} - autoSelect - isDisabled={!isDatabaseSelectEnabled || readOnly} + disabled={!isDatabaseSelectEnabled || readOnly} + options={loadDatabases} />, null, ); } function renderSchemaSelect() { - const value = schemaOptions.filter(({ value }) => currentSchema === value); - const refresh = !formMode && !readOnly && ( + const refreshIcon = !formMode && !readOnly && ( <RefreshLabel - onClick={() => changeDataBase({ id: dbId }, true)} + onClick={() => setRefresh(refresh + 1)} tooltipContent={t('Force refresh schema list')} /> ); return renderSelectRow( <Select + ariaLabel={t('Select a schema')} + disabled={readOnly} + header={<FormLabel>{t('Schema')}</FormLabel>} name="select-schema" - placeholder={t('Select a schema (%s)', schemaOptions.length)} - options={schemaOptions} - value={value} - valueRenderer={o => ( - <div> - <span className="text-muted">{t('Schema:')}</span> {o.label} - </div> - )} - isLoading={schemaLoading} - autosize={false} - onChange={item => changeSchema(item)} - isDisabled={readOnly} + placeholder={t('Select a schema')} + onChange={item => changeSchema(item as SchemaValue)} + options={loadSchemas} + value={currentSchema} />, - refresh, + refreshIcon, ); } return ( <DatabaseSelectorWrapper data-test="DatabaseSelector"> - {formMode && <FieldTitle>{t('datasource')}</FieldTitle>} {renderDatabaseSelect()} - {formMode && <FieldTitle>{t('schema')}</FieldTitle>} {renderSchemaSelect()} </DatabaseSelectorWrapper> ); diff --git a/superset-frontend/src/components/DynamicPlugins/index.tsx b/superset-frontend/src/components/DynamicPlugins/index.tsx index 90f4ba7fb546..b11798fbd87c 100644 --- a/superset-frontend/src/components/DynamicPlugins/index.tsx +++ b/superset-frontend/src/components/DynamicPlugins/index.tsx @@ -17,30 +17,45 @@ * under the License. */ import React, { useContext, useEffect, useReducer } from 'react'; -import { defineSharedModules, logging, makeApi } from '@superset-ui/core'; +import { + ChartMetadata, + defineSharedModules, + getChartMetadataRegistry, + logging, + makeApi, +} from '@superset-ui/core'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; +import { omitBy } from 'lodash'; + +const metadataRegistry = getChartMetadataRegistry(); export type PluginContextType = { loading: boolean; - plugins: { + /** These are actually only the dynamic plugins */ + dynamicPlugins: { [key: string]: { key: string; - loading: boolean; + mounting: boolean; error: null | Error; }; }; + keys: string[]; + /** Mounted means the plugin's js bundle has been imported */ + mountedPluginMetadata: Record<string, ChartMetadata>; fetchAll: () => void; }; const dummyPluginContext: PluginContextType = { loading: true, - plugins: {}, + dynamicPlugins: {}, + keys: [], + mountedPluginMetadata: {}, fetchAll: () => {}, }; /** - * It is highly recommended to use the useDynamicPluginContext hook instead. - * @see useDynamicPluginContext + * It is highly recommended to use the usePluginContext hook instead. + * @see usePluginContext */ export const PluginContext = React.createContext(dummyPluginContext); @@ -52,7 +67,7 @@ export const PluginContext = React.createContext(dummyPluginContext); * Those are compiled into the Superset bundle at build time. * Dynamic plugins are added by the end user and can be any webhosted javascript. */ -export const useDynamicPluginContext = () => useContext(PluginContext); +export const usePluginContext = () => useContext(PluginContext); // the plugin returned from the API type Plugin = { @@ -75,38 +90,60 @@ type BeginAction = { keys: string[]; }; +type ChangedKeysAction = { + type: 'changed keys'; +}; + +type PluginAction = BeginAction | CompleteAction | ChangedKeysAction; + +function getRegistryData() { + return { + keys: metadataRegistry.keys(), + mountedPluginMetadata: omitBy( + metadataRegistry.getMap(), + value => value === undefined, + ) as Record<string, ChartMetadata>, // cast required to get rid of undefined values + }; +} + function pluginContextReducer( state: PluginContextType, - action: BeginAction | CompleteAction, + action: PluginAction, ): PluginContextType { switch (action.type) { case 'begin': { - const plugins = { ...state.plugins }; + const plugins = { ...state.dynamicPlugins }; action.keys.forEach(key => { - plugins[key] = { key, error: null, loading: true }; + plugins[key] = { key, error: null, mounting: true }; }); return { ...state, loading: action.keys.length > 0, - plugins, + dynamicPlugins: plugins, }; } case 'complete': { return { ...state, - loading: Object.values(state.plugins).some( - plugin => plugin.loading && plugin.key !== action.key, + loading: Object.values(state.dynamicPlugins).some( + plugin => plugin.mounting && plugin.key !== action.key, ), - plugins: { - ...state.plugins, + dynamicPlugins: { + ...state.dynamicPlugins, [action.key]: { key: action.key, - loading: false, + mounting: false, error: action.error, }, }, }; } + case 'changed keys': { + return { + ...state, + ...getRegistryData(), + }; + } default: return state; } @@ -126,14 +163,18 @@ const sharedModules = { }; export const DynamicPluginProvider: React.FC = ({ children }) => { - const [pluginState, dispatch] = useReducer(pluginContextReducer, { - // use the dummy plugin context, and override the methods - ...dummyPluginContext, - // eslint-disable-next-line @typescript-eslint/no-use-before-define - fetchAll, - loading: isFeatureEnabled(FeatureFlag.DYNAMIC_PLUGINS), - // TODO: Write fetchByKeys - }); + const [pluginState, dispatch] = useReducer( + pluginContextReducer, + dummyPluginContext, + state => ({ + ...state, + ...getRegistryData(), + // eslint-disable-next-line @typescript-eslint/no-use-before-define + fetchAll, + loading: isFeatureEnabled(FeatureFlag.DYNAMIC_PLUGINS), + // TODO: Write fetchByKeys + }), + ); // For now, we fetch all the plugins at the same time. // In the future it would be nice to fetch on an as-needed basis. @@ -171,6 +212,13 @@ export const DynamicPluginProvider: React.FC = ({ children }) => { if (isFeatureEnabled(FeatureFlag.DYNAMIC_PLUGINS)) { fetchAll(); } + const registryListener = () => { + dispatch({ type: 'changed keys' }); + }; + metadataRegistry.addListener(registryListener); + return () => { + metadataRegistry.removeListener(registryListener); + }; }, []); return ( diff --git a/superset-frontend/src/components/EditableTitle/index.tsx b/superset-frontend/src/components/EditableTitle/index.tsx index ec2245b05ef8..86c0350615ff 100644 --- a/superset-frontend/src/components/EditableTitle/index.tsx +++ b/superset-frontend/src/components/EditableTitle/index.tsx @@ -70,6 +70,13 @@ export default function EditableTitle({ useEffect(() => { if (isEditing) { contentRef.current.focus(); + // move cursor and scroll to the end + if (contentRef.current.setSelectionRange) { + const { length } = contentRef.current.value; + contentRef.current.setSelectionRange(length, length); + contentRef.current.scrollLeft = contentRef.current.scrollWidth; + contentRef.current.scrollTop = contentRef.current.scrollHeight; + } } }, [isEditing]); diff --git a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx index 99db37d92796..b6be9a1b9f8c 100644 --- a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx @@ -29,13 +29,6 @@ const mockedProps = { title: 'Error title', }; -jest.mock('../Icon', () => ({ - __esModule: true, - default: ({ name }: { name: string }) => ( - <div data-test="icon" data-name={name} /> - ), -})); - test('should render', () => { const { container } = render(<BasicErrorAlert {...mockedProps} />); expect(container).toBeInTheDocument(); @@ -43,11 +36,9 @@ test('should render', () => { test('should render warning icon', () => { render(<BasicErrorAlert {...mockedProps} />); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'warning-solid', - ); + expect( + screen.getByRole('img', { name: 'warning-solid' }), + ).toBeInTheDocument(); }); test('should render error icon', () => { @@ -56,11 +47,7 @@ test('should render error icon', () => { level: 'error' as ErrorLevel, }; render(<BasicErrorAlert {...errorProps} />); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'error-solid', - ); + expect(screen.getByRole('img', { name: 'error-solid' })).toBeInTheDocument(); }); test('should render the error title', () => { diff --git a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx index 6c460096ad7d..0d5d99305dc6 100644 --- a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { styled, supersetTheme } from '@superset-ui/core'; -import Icon from '../Icon'; +import Icons from 'src/components/Icons'; import { ErrorLevel } from './types'; const StyledContainer = styled.div<{ level: ErrorLevel }>` @@ -56,10 +56,11 @@ export default function BasicErrorAlert({ }: BasicErrorAlertProps) { return ( <StyledContainer level={level} role="alert"> - <Icon - name={level === 'error' ? 'error-solid' : 'warning-solid'} - color={supersetTheme.colors[level].base} - /> + {level === 'error' ? ( + <Icons.ErrorSolid iconColor={supersetTheme.colors[level].base} /> + ) : ( + <Icons.WarningSolid iconColor={supersetTheme.colors[level].base} /> + )} <StyledContent> <StyledTitle>{title}</StyledTitle> <p>{body}</p> diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx index 0b2386635214..6959c5351a7e 100644 --- a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx @@ -44,6 +44,7 @@ const mockedProps = { message: 'Error message', }, source: 'dashboard' as ErrorSource, + subtitle: 'Error message', }; test('should render', () => { diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx index 0454aca59ef0..9a131f1e2112 100644 --- a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx @@ -35,12 +35,13 @@ interface DatabaseErrorExtra { function DatabaseErrorMessage({ error, source = 'dashboard', + subtitle, }: ErrorMessageComponentProps<DatabaseErrorExtra>) { const { extra, level, message } = error; const isVisualization = ['dashboard', 'explore'].includes(source); - const body = ( + const body = extra && ( <> <p> {t('This may be triggered by:')} @@ -74,14 +75,17 @@ function DatabaseErrorMessage({ </> ); - const copyText = `${message} + const copyText = + extra && extra.issue_codes + ? `${message} ${t('This may be triggered by:')} -${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`; +${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}` + : message; return ( <ErrorAlert - title={t('%s Error', extra.engine_name || t('DB engine'))} - subtitle={message} + title={t('%s Error', (extra && extra.engine_name) || t('DB engine'))} + subtitle={subtitle} level={level} source={source} copyText={copyText} diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx index f9c54e9be744..7133f25c9e5e 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx @@ -33,13 +33,6 @@ const mockedProps = { source: 'dashboard' as ErrorSource, }; -jest.mock('../Icon', () => ({ - __esModule: true, - default: ({ name }: { name: string }) => ( - <div data-test="icon" data-name={name} /> - ), -})); - test('should render', () => { const { container } = render(<ErrorAlert {...mockedProps} />); expect(container).toBeInTheDocument(); @@ -47,11 +40,9 @@ test('should render', () => { test('should render warning icon', () => { render(<ErrorAlert {...mockedProps} />); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'warning-solid', - ); + expect( + screen.getByRole('img', { name: 'warning-solid' }), + ).toBeInTheDocument(); }); test('should render error icon', () => { @@ -60,11 +51,7 @@ test('should render error icon', () => { level: 'error' as ErrorLevel, }; render(<ErrorAlert {...errorProps} />); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'error-solid', - ); + expect(screen.getByRole('img', { name: 'error-solid' })).toBeInTheDocument(); }); test('should render the error title', () => { diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index bd5ec4c2cb4f..3298fcf0561a 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -22,7 +22,7 @@ import { noOp } from 'src/utils/common'; import Modal from 'src/components/Modal'; import Button from 'src/components/Button'; -import Icon from '../Icon'; +import Icons from 'src/components/Icons'; import { ErrorLevel, ErrorSource } from './types'; import CopyToClipboard from '../CopyToClipboard'; @@ -106,11 +106,17 @@ export default function ErrorAlert({ <ErrorAlertDiv level={level} role="alert"> <div className="top-row"> <LeftSideContent> - <Icon - className="icon" - name={level === 'error' ? 'error-solid' : 'warning-solid'} - color={supersetTheme.colors[level].base} - /> + {level === 'error' ? ( + <Icons.ErrorSolid + className="icon" + iconColor={supersetTheme.colors[level].base} + /> + ) : ( + <Icons.WarningSolid + className="icon" + iconColor={supersetTheme.colors[level].base} + /> + )} <strong>{title}</strong> </LeftSideContent> {!isExpandable && ( @@ -163,11 +169,17 @@ export default function ErrorAlert({ onHide={() => setIsModalOpen(false)} title={ <div className="header"> - <Icon - className="icon" - name={level === 'error' ? 'error-solid' : 'warning-solid'} - color={supersetTheme.colors[level].base} - /> + {level === 'error' ? ( + <Icons.ErrorSolid + className="icon" + iconColor={supersetTheme.colors[level].base} + /> + ) : ( + <Icons.WarningSolid + className="icon" + iconColor={supersetTheme.colors[level].base} + /> + )} <div className="title">{title}</div> </div> } diff --git a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx index 29bd73cb3ccb..c073cf0461b4 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx @@ -50,7 +50,13 @@ export default function ErrorMessageWithStackTrace({ error.error_type, ); if (ErrorMessageComponent) { - return <ErrorMessageComponent error={error} source={source} />; + return ( + <ErrorMessageComponent + error={error} + source={source} + subtitle={subtitle} + /> + ); } } diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx index d4664d53c62e..17f38c4d23c5 100644 --- a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx @@ -44,6 +44,7 @@ const mockedProps = { message: 'Error message', }, source: 'dashboard' as ErrorSource, + subtitle: 'Error message', }; test('should render', () => { diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx index 1eb44371ed7e..666ad32169ad 100644 --- a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.tsx @@ -54,6 +54,7 @@ const findMatches = (undefinedParameters: string[], templateKeys: string[]) => { function ParameterErrorMessage({ error, source = 'sqllab', + subtitle, }: ErrorMessageComponentProps<ParameterErrorExtra>) { const { extra, level, message } = error; @@ -112,7 +113,7 @@ ${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`; return ( <ErrorAlert title={t('Parameter error')} - subtitle={message} + subtitle={subtitle} level={level} source={source} copyText={copyText} diff --git a/superset-frontend/src/components/ErrorMessage/types.ts b/superset-frontend/src/components/ErrorMessage/types.ts index a2ce66b594b1..496a9b4cefe5 100644 --- a/superset-frontend/src/components/ErrorMessage/types.ts +++ b/superset-frontend/src/components/ErrorMessage/types.ts @@ -33,12 +33,15 @@ export const ErrorTypeEnum = { CONNECTION_INVALID_PASSWORD_ERROR: 'CONNECTION_INVALID_PASSWORD_ERROR', CONNECTION_INVALID_HOSTNAME_ERROR: 'CONNECTION_INVALID_HOSTNAME_ERROR', CONNECTION_PORT_CLOSED_ERROR: 'CONNECTION_PORT_CLOSED_ERROR', + CONNECTION_INVALID_PORT_ERROR: 'CONNECTION_INVALID_PORT_ERROR', CONNECTION_HOST_DOWN_ERROR: 'CONNECTION_HOST_DOWN_ERROR', CONNECTION_ACCESS_DENIED_ERROR: 'CONNECTION_ACCESS_DENIED_ERROR', CONNECTION_UNKNOWN_DATABASE_ERROR: 'CONNECTION_UNKNOWN_DATABASE_ERROR', CONNECTION_DATABASE_PERMISSIONS_ERROR: 'CONNECTION_DATABASE_PERMISSIONS_ERROR', CONNECTION_MISSING_PARAMETERS_ERRORS: 'CONNECTION_MISSING_PARAMETERS_ERRORS', + OBJECT_DOES_NOT_EXIST_ERROR: 'OBJECT_DOES_NOT_EXIST_ERROR', + SYNTAX_ERROR: 'SYNTAX_ERROR', // Viz errors VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR', @@ -50,13 +53,23 @@ export const ErrorTypeEnum = { TABLE_SECURITY_ACCESS_ERROR: 'TABLE_SECURITY_ACCESS_ERROR', DATASOURCE_SECURITY_ACCESS_ERROR: 'DATASOURCE_SECURITY_ACCESS_ERROR', DATABASE_SECURITY_ACCESS_ERROR: 'DATABASE_SECURITY_ACCESS_ERROR', + QUERY_SECURITY_ACCESS_ERROR: 'QUERY_SECURITY_ACCESS_ERROR', MISSING_OWNERSHIP_ERROR: 'MISSING_OWNERSHIP_ERROR', // Other errors BACKEND_TIMEOUT_ERROR: 'BACKEND_TIMEOUT_ERROR', + DATABASE_NOT_FOUND_ERROR: 'DATABASE_NOT_FOUND_ERROR', // Sqllab error MISSING_TEMPLATE_PARAMS_ERROR: 'MISSING_TEMPLATE_PARAMS_ERROR', + INVALID_TEMPLATE_PARAMS_ERROR: 'INVALID_TEMPLATE_PARAMS_ERROR', + RESULTS_BACKEND_NOT_CONFIGURED_ERROR: 'RESULTS_BACKEND_NOT_CONFIGURED_ERROR', + DML_NOT_ALLOWED_ERROR: 'DML_NOT_ALLOWED_ERROR', + INVALID_CTAS_QUERY_ERROR: 'INVALID_CTAS_QUERY_ERROR', + INVALID_CVAS_QUERY_ERROR: 'INVALID_CVAS_QUERY_ERROR', + SQLLAB_TIMEOUT_ERROR: 'SQLLAB_TIMEOUT_ERROR', + RESULTS_BACKEND_ERROR: 'RESULTS_BACKEND_ERROR', + ASYNC_WORKERS_ERROR: 'ASYNC_WORKERS_ERROR', // Generic errors GENERIC_COMMAND_ERROR: 'GENERIC_COMMAND_ERROR', @@ -88,6 +101,7 @@ export type ErrorMessageComponentProps< > = { error: SupersetError<ExtraType>; source?: ErrorSource; + subtitle?: React.ReactNode; }; export type ErrorMessageComponent = React.ComponentType<ErrorMessageComponentProps>; diff --git a/superset-frontend/src/components/FaveStar/FaveStar.test.tsx b/superset-frontend/src/components/FaveStar/FaveStar.test.tsx index 443cb1e41c43..68433db96ea4 100644 --- a/superset-frontend/src/components/FaveStar/FaveStar.test.tsx +++ b/superset-frontend/src/components/FaveStar/FaveStar.test.tsx @@ -22,13 +22,6 @@ import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import FaveStar from '.'; -jest.mock('../Icon', () => ({ - __esModule: true, - default: ({ name }: { name: string }) => ( - <div data-test="icon" data-name={name} /> - ), -})); - jest.mock('src/components/Tooltip', () => ({ Tooltip: (props: any) => <div data-test="tooltip" {...props} />, })); @@ -41,11 +34,9 @@ test('render right content', () => { const { rerender } = render(<FaveStar {...props} isStarred />); expect(screen.getByRole('button')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'favorite-selected', - ); + expect( + screen.getByRole('img', { name: 'favorite-selected' }), + ).toBeInTheDocument(); expect(props.saveFaveStar).toBeCalledTimes(0); userEvent.click(screen.getByRole('button')); @@ -53,11 +44,9 @@ test('render right content', () => { expect(props.saveFaveStar).toBeCalledWith(props.itemId, true); rerender(<FaveStar {...props} />); - expect(screen.getByTestId('icon')).toBeInTheDocument(); - expect(screen.getByTestId('icon')).toHaveAttribute( - 'data-name', - 'favorite-unselected', - ); + expect( + screen.getByRole('img', { name: 'favorite-unselected' }), + ).toBeInTheDocument(); expect(props.saveFaveStar).toBeCalledTimes(1); userEvent.click(screen.getByRole('button')); diff --git a/superset-frontend/src/components/FaveStar/index.tsx b/superset-frontend/src/components/FaveStar/index.tsx index 8a0903372f0a..1f7a74b1acfb 100644 --- a/superset-frontend/src/components/FaveStar/index.tsx +++ b/superset-frontend/src/components/FaveStar/index.tsx @@ -21,7 +21,7 @@ import React, { useCallback } from 'react'; import { t, styled } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; import { useComponentDidMount } from 'src/common/hooks/useComponentDidMount'; -import Icon from '../Icon'; +import Icons from 'src/components/Icons'; interface FaveStarProps { itemId: number; @@ -66,7 +66,11 @@ const FaveStar = ({ data-test="fave-unfave-icon" role="button" > - <Icon name={isStarred ? 'favorite-selected' : 'favorite-unselected'} /> + {isStarred ? ( + <Icons.FavoriteSelected iconSize="xxl" /> + ) : ( + <Icons.FavoriteUnselected iconSize="xxl" /> + )} </StyledLink> ); diff --git a/superset-frontend/src/components/FilterableTable/FilterableTable.tsx b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx index ed85daef6e44..0e7f7e349e46 100644 --- a/superset-frontend/src/components/FilterableTable/FilterableTable.tsx +++ b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx @@ -396,7 +396,7 @@ export default class FilterableTable extends PureComponent< }} className={`${className} grid-cell grid-header-cell`} > - {label} + <div>{label}</div> </div> </Tooltip> ); @@ -428,7 +428,7 @@ export default class FilterableTable extends PureComponent< }} className={`grid-cell ${this.rowClassName({ index: rowIndex })}`} > - {content} + <div>{content}</div> </div> ); diff --git a/superset-frontend/src/components/Form/Form.tsx b/superset-frontend/src/components/Form/Form.tsx index 946324549ad3..1fc25e6c498b 100644 --- a/superset-frontend/src/components/Form/Form.tsx +++ b/superset-frontend/src/components/Form/Form.tsx @@ -32,3 +32,5 @@ const StyledForm = styled(AntDForm)` export default function Form(props: FormProps) { return <StyledForm {...props} />; } + +export { FormProps }; diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx index 6061848d8501..d1c81b9af3a6 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.stories.tsx @@ -32,6 +32,7 @@ export const InteractiveLabeledErrorBoundInput = ({ placeholder, type, id, + tooltipText, }: LabeledErrorBoundInputProps) => { const [currentValue, setCurrentValue] = useState(value); @@ -58,6 +59,8 @@ export const InteractiveLabeledErrorBoundInput = ({ placeholder={placeholder} type={type} required + hasTooltip + tooltipText={tooltipText} /> ); }; @@ -66,6 +69,7 @@ InteractiveLabeledErrorBoundInput.args = { name: 'Username', placeholder: 'Example placeholder text...', id: 1, + tooltipText: 'This is a tooltip', }; InteractiveLabeledErrorBoundInput.argTypes = { diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx index 15f956fd5773..ebfd2b30e42b 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.test.jsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, fireEvent, screen } from 'spec/helpers/testing-library'; import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput'; const defaultProps = { @@ -27,6 +27,8 @@ const defaultProps = { validationMethods: () => {}, errorMessage: '', helpText: 'This is a line of example help text', + hasTooltip: false, + tooltipText: 'This is a tooltip', value: '', placeholder: 'Example placeholder text...', type: 'textbox', @@ -58,4 +60,19 @@ describe('LabeledErrorBoundInput', () => { expect(textboxInput).toBeVisible(); expect(errorText).toBeVisible(); }); + it('renders a LabledErrorBoundInput with a InfoTooltip', async () => { + defaultProps.hasTooltip = true; + render(<LabeledErrorBoundInput {...defaultProps} />); + + const label = screen.getByText(/username/i); + const textboxInput = screen.getByRole('textbox'); + const tooltipIcon = screen.getByRole('img'); + + fireEvent.mouseOver(tooltipIcon); + + expect(tooltipIcon).toBeVisible(); + expect(label).toBeVisible(); + expect(textboxInput).toBeVisible(); + expect(await screen.findByText('This is a tooltip')).toBeInTheDocument(); + }); }); diff --git a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx index 75df2bb088cb..10138cd5593a 100644 --- a/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx +++ b/superset-frontend/src/components/Form/LabeledErrorBoundInput.tsx @@ -19,6 +19,8 @@ import React from 'react'; import { Input } from 'antd'; import { styled, css, SupersetTheme } from '@superset-ui/core'; +import InfoTooltip from 'src/components/InfoTooltip'; +import errorIcon from 'images/icons/error.svg'; import FormItem from './FormItem'; import FormLabel from './FormLabel'; @@ -30,6 +32,8 @@ export interface LabeledErrorBoundInputProps { errorMessage: string | null; helpText?: string; required?: boolean; + hasTooltip?: boolean; + tooltipText?: string | null; id?: string; classname?: string; [x: string]: any; @@ -46,42 +50,63 @@ const alertIconStyles = (theme: SupersetTheme, hasError: boolean) => css` ${hasError && `.ant-form-item-control-input-content { position: relative; - &:after { content: ' '; display: inline-block; background: ${theme.colors.error.base}; - mask: url('/images/icons/error.svg'); + mask: url(${errorIcon}); mask-size: cover; width: ${theme.gridUnit * 4}px; height: ${theme.gridUnit * 4}px; position: absolute; - right: 7px; - top: 15px; + right: ${theme.gridUnit * 1.25}px; + top: ${theme.gridUnit * 2.75}px; } }`} `; + const StyledFormGroup = styled('div')` - margin-bottom: ${({ theme }) => theme.gridUnit * 5}px; + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + margin-bottom: ${({ theme }) => theme.gridUnit * 3}px; .ant-form-item { margin-bottom: 0; } `; +const StyledAlignment = styled.div` + display: flex; + align-items: center; +`; + +const StyledFormLabel = styled(FormLabel)` + margin-bottom: 0; +`; + const LabeledErrorBoundInput = ({ label, validationMethods, errorMessage, helpText, required = false, + hasTooltip = false, + tooltipText, id, className, ...props }: LabeledErrorBoundInputProps) => ( <StyledFormGroup className={className}> - <FormLabel htmlFor={id} required={required}> - {label} - </FormLabel> + <StyledAlignment> + <StyledFormLabel htmlFor={id} required={required}> + {label} + </StyledFormLabel> + {hasTooltip && ( + <InfoTooltip tooltip={`${tooltipText}`} viewBox="0 -1 24 24" /> + )} + </StyledAlignment> <FormItem css={(theme: SupersetTheme) => alertIconStyles(theme, !!errorMessage)} validateTrigger={Object.keys(validationMethods)} diff --git a/superset-frontend/src/components/Form/index.tsx b/superset-frontend/src/components/Form/index.tsx index 7d7a60745df8..1aa62ae56708 100644 --- a/superset-frontend/src/components/Form/index.tsx +++ b/superset-frontend/src/components/Form/index.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import Form from './Form'; +import Form, { FormProps } from './Form'; import FormItem from './FormItem'; import FormLabel from './FormLabel'; import LabeledErrorBoundInput from './LabeledErrorBoundInput'; -export { Form, FormItem, FormLabel, LabeledErrorBoundInput }; +export { Form, FormItem, FormLabel, LabeledErrorBoundInput, FormProps }; diff --git a/superset-frontend/src/components/Icon/icon.stories.jsx b/superset-frontend/src/components/Icon/icon.stories.jsx deleted file mode 100644 index fbe09516c173..000000000000 --- a/superset-frontend/src/components/Icon/icon.stories.jsx +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import { withKnobs, select } from '@storybook/addon-knobs'; -import { styled, supersetTheme } from '@superset-ui/core'; -import Icon, { iconsRegistry } from '.'; - -export default { - title: 'Icon', - component: Icon, - decorators: [withKnobs], -}; - -const palette = {}; -Object.entries(supersetTheme.colors).forEach(([familyName, family]) => { - Object.entries(family).forEach(([colorName, colorValue]) => { - palette[`${familyName} / ${colorName}`] = colorValue; - }); -}); - -const colorKnob = { - label: 'Color', - options: { - Default: null, - ...palette, - }, - defaultValue: null, -}; - -const IconSet = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; -`; - -const IconBlock = styled.div` - flex-grow: 0; - flex-shrink: 0; - flex-basis: 10%; - text-align: center; - padding: ${({ theme }) => theme.gridUnit * 2}px; - div { - white-space: nowrap; - font-size: ${({ theme }) => theme.typography.sizes.s}px; - } -`; - -export const SupersetIcon = () => ( - <IconSet> - {Object.keys(iconsRegistry) - .sort() - .map(iconName => ( - <IconBlock key={iconName}> - <Icon - name={iconName} - key={iconName} - color={select( - colorKnob.label, - colorKnob.options, - colorKnob.defaultValue, - colorKnob.groupId, - )} - /> - <div>{iconName}</div> - </IconBlock> - ))} - </IconSet> -); diff --git a/superset-frontend/src/components/Icon/index.tsx b/superset-frontend/src/components/Icon/index.tsx deleted file mode 100644 index 693354d9a736..000000000000 --- a/superset-frontend/src/components/Icon/index.tsx +++ /dev/null @@ -1,437 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React, { SVGProps } from 'react'; - -import { ReactComponent as AlertIcon } from 'images/icons/alert.svg'; -import { ReactComponent as AlertSolidIcon } from 'images/icons/alert_solid.svg'; -import { ReactComponent as AlertSolidSmallIcon } from 'images/icons/alert_solid_small.svg'; -import { ReactComponent as BinocularsIcon } from 'images/icons/binoculars.svg'; -import { ReactComponent as BoltIcon } from 'images/icons/bolt.svg'; -import { ReactComponent as BoltSmallIcon } from 'images/icons/bolt_small.svg'; -import { ReactComponent as CrossFilterBadge } from 'images/icons/cross-filter-badge.svg'; -import { ReactComponent as BoltSmallRunIcon } from 'images/icons/bolt_small_run.svg'; -import { ReactComponent as CalendarIcon } from 'images/icons/calendar.svg'; -import { ReactComponent as CancelIcon } from 'images/icons/cancel.svg'; -import { ReactComponent as CancelSolidIcon } from 'images/icons/cancel_solid.svg'; -import { ReactComponent as CancelXIcon } from 'images/icons/cancel-x.svg'; -import { ReactComponent as CardViewIcon } from 'images/icons/card_view.svg'; -import { ReactComponent as CardsIcon } from 'images/icons/cards.svg'; -import { ReactComponent as CardsLockedIcon } from 'images/icons/cards_locked.svg'; -import { ReactComponent as CaretDownIcon } from 'images/icons/caret_down.svg'; -import { ReactComponent as CaretLeftIcon } from 'images/icons/caret_left.svg'; -import { ReactComponent as CaretRightIcon } from 'images/icons/caret_right.svg'; -import { ReactComponent as CaretUpIcon } from 'images/icons/caret_up.svg'; -import { ReactComponent as CertifiedIcon } from 'images/icons/certified.svg'; -import { ReactComponent as CheckIcon } from 'images/icons/check.svg'; -import { ReactComponent as CheckboxHalfIcon } from 'images/icons/checkbox-half.svg'; -import { ReactComponent as CheckboxOffIcon } from 'images/icons/checkbox-off.svg'; -import { ReactComponent as CheckboxOnIcon } from 'images/icons/checkbox-on.svg'; -import { ReactComponent as CircleCheckIcon } from 'images/icons/circle_check.svg'; -import { ReactComponent as CircleCheckSolidIcon } from 'images/icons/circle_check_solid.svg'; -import { ReactComponent as CircleIcon } from 'images/icons/circle.svg'; -import { ReactComponent as ClockIcon } from 'images/icons/clock.svg'; -import { ReactComponent as CloseIcon } from 'images/icons/close.svg'; -import { ReactComponent as CodeIcon } from 'images/icons/code.svg'; -import { ReactComponent as CogIcon } from 'images/icons/cog.svg'; -import { ReactComponent as CollapseIcon } from 'images/icons/collapse.svg'; -import { ReactComponent as ColorPaletteIcon } from 'images/icons/color_palette.svg'; -import { ReactComponent as ComponentsIcon } from 'images/icons/components.svg'; -import { ReactComponent as CopyIcon } from 'images/icons/copy.svg'; -import { ReactComponent as CursorTargeIcon } from 'images/icons/cursor_target.svg'; -import { ReactComponent as DatabaseIcon } from 'images/icons/database.svg'; -import { ReactComponent as DatasetPhysicalIcon } from 'images/icons/dataset_physical.svg'; -import { ReactComponent as DatasetVirtualGreyscaleIcon } from 'images/icons/dataset_virtual_greyscale.svg'; -import { ReactComponent as DatasetVirtualIcon } from 'images/icons/dataset_virtual.svg'; -import { ReactComponent as DownloadIcon } from 'images/icons/download.svg'; -import { ReactComponent as EditAltIcon } from 'images/icons/edit_alt.svg'; -import { ReactComponent as EditIcon } from 'images/icons/edit.svg'; -import { ReactComponent as EmailIcon } from 'images/icons/email.svg'; -import { ReactComponent as ErrorIcon } from 'images/icons/error.svg'; -import { ReactComponent as ErrorSolidIcon } from 'images/icons/error_solid.svg'; -import { ReactComponent as ErrorSolidSmallIcon } from 'images/icons/error_solid_small.svg'; -import { ReactComponent as ExclamationIcon } from 'images/icons/exclamation.svg'; -import { ReactComponent as ExpandIcon } from 'images/icons/expand.svg'; -import { ReactComponent as EyeIcon } from 'images/icons/eye.svg'; -import { ReactComponent as EyeSlashIcon } from 'images/icons/eye_slash.svg'; -import { ReactComponent as FavoriteSelectedIcon } from 'images/icons/favorite-selected.svg'; -import { ReactComponent as FavoriteSmallSelectedIcon } from 'images/icons/favorite_small_selected.svg'; -import { ReactComponent as FavoriteUnselectedIcon } from 'images/icons/favorite-unselected.svg'; -import { ReactComponent as FieldABCIcon } from 'images/icons/field_abc.svg'; -import { ReactComponent as FieldBooleanIcon } from 'images/icons/field_boolean.svg'; -import { ReactComponent as FieldDateIcon } from 'images/icons/field_date.svg'; -import { ReactComponent as FieldDerivedIcon } from 'images/icons/field_derived.svg'; -import { ReactComponent as FieldNumIcon } from 'images/icons/field_num.svg'; -import { ReactComponent as FieldStructIcon } from 'images/icons/field_struct.svg'; -import { ReactComponent as FileIcon } from 'images/icons/file.svg'; -import { ReactComponent as FilterIcon } from 'images/icons/filter.svg'; -import { ReactComponent as FilterSmallIcon } from 'images/icons/filter_small.svg'; -import { ReactComponent as FolderIcon } from 'images/icons/folder.svg'; -import { ReactComponent as FullIcon } from 'images/icons/full.svg'; -import { ReactComponent as FunctionIcon } from 'images/icons/function_x.svg'; -import { ReactComponent as GearIcon } from 'images/icons/gear.svg'; -import { ReactComponent as GridIcon } from 'images/icons/grid.svg'; -import { ReactComponent as ImageIcon } from 'images/icons/image.svg'; -import { ReactComponent as ImportIcon } from 'images/icons/import.svg'; -import { ReactComponent as InfoIcon } from 'images/icons/info.svg'; -import { ReactComponent as InfoSolidIcon } from 'images/icons/info-solid.svg'; -import { ReactComponent as InfoSolidSmallIcon } from 'images/icons/info_solid_small.svg'; -import { ReactComponent as JoinIcon } from 'images/icons/join.svg'; -import { ReactComponent as KeyboardIcon } from 'images/icons/keyboard.svg'; -import { ReactComponent as LayersIcon } from 'images/icons/layers.svg'; -import { ReactComponent as LightbulbIcon } from 'images/icons/lightbulb.svg'; -import { ReactComponent as LinkIcon } from 'images/icons/link.svg'; -import { ReactComponent as ListIcon } from 'images/icons/list.svg'; -import { ReactComponent as ListViewIcon } from 'images/icons/list_view.svg'; -import { ReactComponent as LocationIcon } from 'images/icons/location.svg'; -import { ReactComponent as LockLockedIcon } from 'images/icons/lock_locked.svg'; -import { ReactComponent as LockUnlockedIcon } from 'images/icons/lock_unlocked.svg'; -import { ReactComponent as MapIcon } from 'images/icons/map.svg'; -import { ReactComponent as MessageIcon } from 'images/icons/message.svg'; -import { ReactComponent as MinusIcon } from 'images/icons/minus.svg'; -import { ReactComponent as MinusSolidIcon } from 'images/icons/minus_solid.svg'; -import { ReactComponent as MoreHorizIcon } from 'images/icons/more_horiz.svg'; -import { ReactComponent as MoveIcon } from 'images/icons/move.svg'; -import { ReactComponent as NavChartsIcon } from 'images/icons/nav_charts.svg'; -import { ReactComponent as NavDashboardIcon } from 'images/icons/nav_dashboard.svg'; -import { ReactComponent as NavDataIcon } from 'images/icons/nav_data.svg'; -import { ReactComponent as NavExploreIcon } from 'images/icons/nav_explore.svg'; -import { ReactComponent as NavHomeIcon } from 'images/icons/nav_home.svg'; -import { ReactComponent as NavLabIcon } from 'images/icons/nav_lab.svg'; -import { ReactComponent as NoteIcon } from 'images/icons/note.svg'; -import { ReactComponent as OfflineIcon } from 'images/icons/offline.svg'; -import { ReactComponent as PaperclipIcon } from 'images/icons/paperclip.svg'; -import { ReactComponent as PlaceholderIcon } from 'images/icons/placeholder.svg'; -import { ReactComponent as PlusIcon } from 'images/icons/plus.svg'; -import { ReactComponent as PlusLargeIcon } from 'images/icons/plus_large.svg'; -import { ReactComponent as PlusSmallIcon } from 'images/icons/plus_small.svg'; -import { ReactComponent as PlusSolidIcon } from 'images/icons/plus_solid.svg'; -import { ReactComponent as QueuedIcon } from 'images/icons/queued.svg'; -import { ReactComponent as RefreshIcon } from 'images/icons/refresh.svg'; -import { ReactComponent as RunningIcon } from 'images/icons/running.svg'; -import { ReactComponent as SaveIcon } from 'images/icons/save.svg'; -import { ReactComponent as SQLIcon } from 'images/icons/sql.svg'; -import { ReactComponent as SearchIcon } from 'images/icons/search.svg'; -import { ReactComponent as ServerIcon } from 'images/icons/server.svg'; -import { ReactComponent as ShareIcon } from 'images/icons/share.svg'; -import { ReactComponent as SlackIcon } from 'images/icons/slack.svg'; -import { ReactComponent as SortAscIcon } from 'images/icons/sort_asc.svg'; -import { ReactComponent as SortDescIcon } from 'images/icons/sort_desc.svg'; -import { ReactComponent as SortIcon } from 'images/icons/sort.svg'; -import { ReactComponent as TableIcon } from 'images/icons/table.svg'; -import { ReactComponent as TagIcon } from 'images/icons/tag.svg'; -import { ReactComponent as TrashIcon } from 'images/icons/trash.svg'; -import { ReactComponent as TriangleChangeIcon } from 'images/icons/triangle_change.svg'; -import { ReactComponent as TriangleDownIcon } from 'images/icons/triangle_down.svg'; -import { ReactComponent as TriangleUpIcon } from 'images/icons/triangle_up.svg'; -import { ReactComponent as UpLevelIcon } from 'images/icons/up-level.svg'; -import { ReactComponent as UserIcon } from 'images/icons/user.svg'; -import { ReactComponent as WarningIcon } from 'images/icons/warning.svg'; -import { ReactComponent as WarningSolidIcon } from 'images/icons/warning_solid.svg'; -import { ReactComponent as XLargeIcon } from 'images/icons/x-large.svg'; -import { ReactComponent as XSmallIcon } from 'images/icons/x-small.svg'; - -export type IconName = - | 'alert' - | 'alert-solid' - | 'alert-solid-small' - | 'binoculars' - | 'bolt' - | 'bolt-small' - | 'bolt-small-run' - | 'calendar' - | 'cancel' - | 'cancel-solid' - | 'cancel-x' - | 'card-view' - | 'cards' - | 'cards-locked' - | 'caret-down' - | 'caret-left' - | 'caret-right' - | 'caret-up' - | 'certified' - | 'cross-filter-badge' - | 'check' - | 'checkbox-half' - | 'checkbox-off' - | 'checkbox-on' - | 'circle' - | 'circle-check' - | 'circle-check-solid' - | 'clock' - | 'close' - | 'code' - | 'cog' - | 'collapse' - | 'color-palette' - | 'components' - | 'copy' - | 'cursor-target' - | 'database' - | 'dataset-physical' - | 'dataset-virtual' - | 'dataset-virtual-greyscale' - | 'download' - | 'edit' - | 'edit-alt' - | 'email' - | 'error' - | 'error-solid' - | 'error-solid-small' - | 'exclamation' - | 'expand' - | 'eye' - | 'eye-slash' - | 'favorite-selected' - | 'favorite-small-selected' - | 'favorite-unselected' - | 'field-abc' - | 'field-boolean' - | 'field-date' - | 'field-derived' - | 'field-num' - | 'field-struct' - | 'file' - | 'filter' - | 'filter-small' - | 'folder' - | 'full' - | 'function' - | 'gear' - | 'grid' - | 'image' - | 'import' - | 'info' - | 'info-solid' - | 'info-solid-small' - | 'join' - | 'keyboard' - | 'layers' - | 'link' - | 'lightbulb' - | 'list' - | 'list-view' - | 'location' - | 'lock-locked' - | 'lock-unlocked' - | 'map' - | 'message' - | 'minus' - | 'minus-solid' - | 'more-horiz' - | 'move' - | 'nav-charts' - | 'nav-dashboard' - | 'nav-data' - | 'nav-explore' - | 'nav-home' - | 'nav-lab' - | 'note' - | 'offline' - | 'paperclip' - | 'placeholder' - | 'plus' - | 'plus-large' - | 'plus-small' - | 'plus-solid' - | 'queued' - | 'refresh' - | 'running' - | 'save' - | 'search' - | 'server' - | 'share' - | 'slack' - | 'sort' - | 'sort-asc' - | 'sort-desc' - | 'sql' - | 'table' - | 'tag' - | 'trash' - | 'triangle-change' - | 'triangle-down' - | 'triangle-up' - | 'up-level' - | 'user' - | 'warning' - | 'warning-solid' - | 'x-large' - | 'x-small'; - -export const iconsRegistry: Record< - IconName, - React.ComponentType<SVGProps<SVGSVGElement>> -> = { - 'alert-solid': AlertSolidIcon, - 'alert-solid-small': AlertSolidSmallIcon, - 'bolt-small': BoltSmallIcon, - 'bolt-small-run': BoltSmallRunIcon, - 'cross-filter-badge': CrossFilterBadge, - 'cancel-solid': CancelSolidIcon, - 'cancel-x': CancelXIcon, - 'card-view': CardViewIcon, - 'cards-locked': CardsLockedIcon, - 'caret-down': CaretDownIcon, - 'caret-left': CaretLeftIcon, - 'caret-right': CaretRightIcon, - 'caret-up': CaretUpIcon, - 'checkbox-half': CheckboxHalfIcon, - 'checkbox-off': CheckboxOffIcon, - 'checkbox-on': CheckboxOnIcon, - 'circle-check': CircleCheckIcon, - 'circle-check-solid': CircleCheckSolidIcon, - 'color-palette': ColorPaletteIcon, - 'cursor-target': CursorTargeIcon, - 'dataset-physical': DatasetPhysicalIcon, - 'dataset-virtual': DatasetVirtualIcon, - 'dataset-virtual-greyscale': DatasetVirtualGreyscaleIcon, - 'edit-alt': EditAltIcon, - 'error-solid': ErrorSolidIcon, - 'error-solid-small': ErrorSolidSmallIcon, - 'eye-slash': EyeSlashIcon, - 'favorite-selected': FavoriteSelectedIcon, - 'favorite-small-selected': FavoriteSmallSelectedIcon, - 'favorite-unselected': FavoriteUnselectedIcon, - 'field-abc': FieldABCIcon, - 'field-boolean': FieldBooleanIcon, - 'field-date': FieldDateIcon, - 'field-derived': FieldDerivedIcon, - 'field-num': FieldNumIcon, - 'field-struct': FieldStructIcon, - 'filter-small': FilterSmallIcon, - 'info-solid': InfoSolidIcon, - 'info-solid-small': InfoSolidSmallIcon, - 'list-view': ListViewIcon, - 'lock-locked': LockLockedIcon, - 'lock-unlocked': LockUnlockedIcon, - 'minus-solid': MinusSolidIcon, - 'more-horiz': MoreHorizIcon, - 'nav-charts': NavChartsIcon, - 'nav-dashboard': NavDashboardIcon, - 'nav-data': NavDataIcon, - 'nav-explore': NavExploreIcon, - 'nav-home': NavHomeIcon, - 'nav-lab': NavLabIcon, - 'plus-large': PlusLargeIcon, - 'plus-small': PlusSmallIcon, - 'plus-solid': PlusSolidIcon, - 'sort-asc': SortAscIcon, - 'sort-desc': SortDescIcon, - 'triangle-change': TriangleChangeIcon, - 'triangle-down': TriangleDownIcon, - 'triangle-up': TriangleUpIcon, - 'up-level': UpLevelIcon, - 'warning-solid': WarningSolidIcon, - 'x-large': XLargeIcon, - 'x-small': XSmallIcon, - alert: AlertIcon, - binoculars: BinocularsIcon, - bolt: BoltIcon, - calendar: CalendarIcon, - cancel: CancelIcon, - cards: CardsIcon, - certified: CertifiedIcon, - check: CheckIcon, - circle: CircleIcon, - clock: ClockIcon, - close: CloseIcon, - code: CodeIcon, - cog: CogIcon, - collapse: CollapseIcon, - components: ComponentsIcon, - copy: CopyIcon, - database: DatabaseIcon, - download: DownloadIcon, - edit: EditIcon, - email: EmailIcon, - error: ErrorIcon, - exclamation: ExclamationIcon, - expand: ExpandIcon, - eye: EyeIcon, - file: FileIcon, - filter: FilterIcon, - folder: FolderIcon, - full: FullIcon, - function: FunctionIcon, - gear: GearIcon, - grid: GridIcon, - image: ImageIcon, - import: ImportIcon, - info: InfoIcon, - join: JoinIcon, - keyboard: KeyboardIcon, - layers: LayersIcon, - link: LinkIcon, - lightbulb: LightbulbIcon, - list: ListIcon, - location: LocationIcon, - map: MapIcon, - message: MessageIcon, - minus: MinusIcon, - move: MoveIcon, - note: NoteIcon, - offline: OfflineIcon, - paperclip: PaperclipIcon, - placeholder: PlaceholderIcon, - plus: PlusIcon, - queued: QueuedIcon, - refresh: RefreshIcon, - running: RunningIcon, - save: SaveIcon, - search: SearchIcon, - server: ServerIcon, - share: ShareIcon, - slack: SlackIcon, - sort: SortIcon, - sql: SQLIcon, - table: TableIcon, - tag: TagIcon, - trash: TrashIcon, - user: UserIcon, - warning: WarningIcon, -}; - -export interface IconProps extends SVGProps<SVGSVGElement> { - name: IconName; - 'data-test'?: string; -} - -const Icon = ({ - name, - color = '#666666', - viewBox = '0 0 24 24', - 'data-test': dataTest, - ...rest -}: IconProps) => { - const Component = iconsRegistry[name]; - - return ( - <Component - role="img" - aria-label={name} - color={color} - viewBox={viewBox} - data-test={dataTest ?? name} - {...rest} - /> - ); -}; - -export default Icon; diff --git a/superset-frontend/src/components/IconButton/index.tsx b/superset-frontend/src/components/IconButton/index.tsx index e7f9c2d89d52..0d80911bf7e9 100644 --- a/superset-frontend/src/components/IconButton/index.tsx +++ b/superset-frontend/src/components/IconButton/index.tsx @@ -20,6 +20,8 @@ import React from 'react'; import { styled } from '@superset-ui/core'; import Button from 'src/components/Button'; import { ButtonProps as AntdButtonProps } from 'antd/lib/button'; +import Icons from 'src/components/Icons'; +import LinesEllipsis from 'react-lines-ellipsis'; export interface IconButtonProps extends AntdButtonProps { buttonText: string; @@ -33,17 +35,34 @@ const StyledButton = styled(Button)` flex-direction: column; padding: 0; `; + const StyledImage = styled.div` - margin: ${({ theme }) => theme.gridUnit * 8}px 0; padding: ${({ theme }) => theme.gridUnit * 4}px; + height: ${({ theme }) => theme.gridUnit * 18}px; + margin: ${({ theme }) => theme.gridUnit * 3}px 0; + + .default-db-icon { + font-size: 36px; + color: ${({ theme }) => theme.colors.grayscale.base}; + margin-right: 0; + span:first-of-type { + margin-right: 0; + } + } &:first-of-type { margin-right: 0; } img { - width: fit-content; - + width: ${({ theme }) => theme.gridUnit * 10}px; + height: ${({ theme }) => theme.gridUnit * 10}px; + margin: 0; + &:first-of-type { + margin-right: 0; + } + } + svg { &:first-of-type { margin-right: 0; } @@ -52,32 +71,21 @@ const StyledImage = styled.div` const StyledInner = styled.div` max-height: calc(1.5em * 2); - overflow: hidden; - padding-right: 1rem; - position: relative; white-space: break-spaces; - &::before { - content: '...'; - inset-block-end: 0; /* "bottom" */ - inset-inline-end: 8px; /* "right" */ - position: absolute; + &:first-of-type { + margin-right: 0; } - &::after { - background-color: ${({ theme }) => theme.colors.grayscale.light4}; - content: ''; - height: 1rem; - inset-inline-end: 8px; /* "right" */ - position: absolute; - top: 4px; - width: 1rem; + .LinesEllipsis { + &:first-of-type { + margin-right: 0; + } } `; const StyledBottom = styled.div` - padding: ${({ theme }) => theme.gridUnit * 6}px - ${({ theme }) => theme.gridUnit * 4}px; + padding: ${({ theme }) => theme.gridUnit * 4}px 0; border-radius: 0 0 ${({ theme }) => theme.borderRadius}px ${({ theme }) => theme.borderRadius}px; background-color: ${({ theme }) => theme.colors.grayscale.light4}; @@ -96,10 +104,24 @@ const IconButton = styled( ({ icon, altText, buttonText, ...props }: IconButtonProps) => ( <StyledButton {...props}> <StyledImage> - <img src={icon} alt={altText} /> + {icon && <img src={icon} alt={altText} />} + {!icon && ( + <Icons.DatabaseOutlined + className="default-db-icon" + aria-label="default-icon" + /> + )} </StyledImage> + <StyledBottom> - <StyledInner>{buttonText}</StyledInner> + <StyledInner> + <LinesEllipsis + text={buttonText} + maxLine="2" + basedOn="words" + trimRight + /> + </StyledInner> </StyledBottom> </StyledButton> ), @@ -117,6 +139,7 @@ const IconButton = styled( background-color: ${({ theme }) => theme.colors.grayscale.light5}; color: ${({ theme }) => theme.colors.grayscale.dark2}; border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + box-shadow: 4px 4px 20px ${({ theme }) => theme.colors.grayscale.light2}; } `; diff --git a/superset-frontend/src/components/IconTooltip/IconTooltip.stories.tsx b/superset-frontend/src/components/IconTooltip/IconTooltip.stories.tsx index 99e55f06050b..a2f2109bb0b8 100644 --- a/superset-frontend/src/components/IconTooltip/IconTooltip.stories.tsx +++ b/superset-frontend/src/components/IconTooltip/IconTooltip.stories.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { IconTooltip, Props } from '.'; export default { @@ -42,7 +42,7 @@ const PLACEMENTS = [ export const InteractiveIconTooltip = (args: Props) => ( <div css={{ margin: '40px 70px' }}> <IconTooltip {...args}> - <Icon name="info" /> + <Icons.Info /> </IconTooltip> </div> ); diff --git a/superset-frontend/src/components/Icons/Icon.tsx b/superset-frontend/src/components/Icons/Icon.tsx index 9e3d0e1878ec..efb78dc65cc1 100644 --- a/superset-frontend/src/components/Icons/Icon.tsx +++ b/superset-frontend/src/components/Icons/Icon.tsx @@ -53,15 +53,21 @@ export const Icon = (props: IconProps) => { const name = fileName.replace('_', '-'); useEffect(() => { + let cancelled = false; async function importIcon(): Promise<void> { ImportedSVG.current = ( await import( `!!@svgr/webpack?-svgo,+titleProp,+ref!images/icons/${fileName}.svg` ) ).default; - setLoaded(true); + if (!cancelled) { + setLoaded(true); + } } importIcon(); + return () => { + cancelled = true; + }; }, [fileName, ImportedSVG]); return ( diff --git a/superset-frontend/src/components/Icons/Icons.stories.tsx b/superset-frontend/src/components/Icons/Icons.stories.tsx index c40dbad295de..e2a3944eb4f1 100644 --- a/superset-frontend/src/components/Icons/Icons.stories.tsx +++ b/superset-frontend/src/components/Icons/Icons.stories.tsx @@ -72,7 +72,7 @@ InteractiveIcons.argTypes = { }, iconSize: { defaultValue: 'xl', - control: { type: 'inline-radio' }, + control: { type: 'inline-radio', options: ['s', 'l', 'm', 'xl', 'xxl'] }, }, iconColor: { defaultValue: null, diff --git a/superset-frontend/src/components/Icons/index.tsx b/superset-frontend/src/components/Icons/index.tsx index 312891ca8acd..f281e9018254 100644 --- a/superset-frontend/src/components/Icons/index.tsx +++ b/superset-frontend/src/components/Icons/index.tsx @@ -111,6 +111,7 @@ const IconFileNames = [ 'minus', 'minus_solid', 'more_horiz', + 'more_vert', 'move', 'nav_charts', 'nav_dashboard', @@ -150,6 +151,9 @@ const IconFileNames = [ 'warning_solid', 'x-large', 'x-small', + 'tags', + 'ballot', + 'category', ]; const iconOverrides: Record<string, React.FC> = {}; @@ -160,6 +164,8 @@ IconFileNames.forEach(fileName => { ); }); +export { IconType }; + export default { ...AntdEnhancedIcons, ...iconOverrides, diff --git a/superset-frontend/src/components/ImportModal/ImportModal.test.tsx b/superset-frontend/src/components/ImportModal/ImportModal.test.tsx index 478fd599b377..d4e3723b4495 100644 --- a/superset-frontend/src/components/ImportModal/ImportModal.test.tsx +++ b/superset-frontend/src/components/ImportModal/ImportModal.test.tsx @@ -17,10 +17,12 @@ * under the License. */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import { styledMount as mount } from 'spec/helpers/theming'; import { ReactWrapper } from 'enzyme'; +import { Upload } from 'src/common/components'; import Button from 'src/components/Button'; import { ImportResourceName } from 'src/views/CRUD/types'; import ImportModelsModal from 'src/components/ImportModal'; @@ -66,29 +68,37 @@ describe('ImportModelsModal', () => { expect(wrapper.find('h4').text()).toEqual('Import database'); }); - it('renders a label and a file input field', () => { + it('renders a file input field', () => { expect(wrapper.find('input[type="file"]')).toExist(); - expect(wrapper.find('label')).toExist(); }); - it('should attach the label to the input field', () => { - const id = 'modelFile'; - expect(wrapper.find('label').prop('htmlFor')).toBe(id); - expect(wrapper.find('input').prop('id')).toBe(id); - }); - - it('should render the close, import and cancel buttons', () => { - expect(wrapper.find('button')).toHaveLength(3); + it('should render the close, file, import and cancel buttons', () => { + expect(wrapper.find('button')).toHaveLength(4); }); it('should render the import button initially disabled', () => { - expect(wrapper.find(Button).at(1).prop('disabled')).toBe(true); + expect(wrapper.find(Button).at(2).prop('disabled')).toBe(true); }); it('should render the import button enabled when a file is selected', () => { const file = new File([new ArrayBuffer(1)], 'model_export.zip'); - wrapper.find('input').simulate('change', { target: { files: [file] } }); - expect(wrapper.find(Button).at(1).prop('disabled')).toBe(false); + act(() => { + const handler = wrapper.find(Upload).prop('onChange'); + if (handler) { + handler({ + fileList: [], + file: { + name: 'model_export.zip', + originFileObj: file, + uid: '-1', + size: 0, + type: 'zip', + }, + }); + } + }); + wrapper.update(); + expect(wrapper.find(Button).at(2).prop('disabled')).toBe(false); }); it('should render password fields when needed for import', () => { diff --git a/superset-frontend/src/components/ImportModal/index.tsx b/superset-frontend/src/components/ImportModal/index.tsx index f9dffcbab9ec..58b6724778e9 100644 --- a/superset-frontend/src/components/ImportModal/index.tsx +++ b/superset-frontend/src/components/ImportModal/index.tsx @@ -16,18 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { UploadChangeParam, UploadFile } from 'antd/lib/upload/interface'; import { styled, t } from '@superset-ui/core'; -import Icon from 'src//components/Icon'; +import Button from 'src/components/Button'; import Modal from 'src/components/Modal'; +import { Upload } from 'src/common/components'; import { useImportResource } from 'src/views/CRUD/hooks'; import { ImportResourceName } from 'src/views/CRUD/types'; -export const StyledIcon = styled(Icon)` - margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0; -`; - const HelperMessage = styled.div` display: block; color: ${({ theme }) => theme.colors.grayscale.base}; @@ -127,24 +125,21 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ setPasswordFields = () => {}, }) => { const [isHidden, setIsHidden] = useState<boolean>(true); - const [uploadFile, setUploadFile] = useState<File | null>(null); const [passwords, setPasswords] = useState<Record<string, string>>({}); const [needsOverwriteConfirm, setNeedsOverwriteConfirm] = useState<boolean>( false, ); const [confirmedOverwrite, setConfirmedOverwrite] = useState<boolean>(false); - - const fileInputRef = useRef<HTMLInputElement>(null); + const [fileList, setFileList] = useState<UploadFile[]>([]); + const [importingModel, setImportingModel] = useState<boolean>(false); const clearModal = () => { - setUploadFile(null); + setFileList([]); setPasswordFields([]); setPasswords({}); setNeedsOverwriteConfirm(false); setConfirmedOverwrite(false); - if (fileInputRef && fileInputRef.current) { - fileInputRef.current.value = ''; - } + setImportingModel(false); }; const handleErrorMsg = (msg: string) => { @@ -159,10 +154,16 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ useEffect(() => { setPasswordFields(passwordsNeeded); + if (passwordsNeeded.length > 0) { + setImportingModel(false); + } }, [passwordsNeeded, setPasswordFields]); useEffect(() => { setNeedsOverwriteConfirm(alreadyExists.length > 0); + if (alreadyExists.length > 0) { + setImportingModel(false); + } }, [alreadyExists, setNeedsOverwriteConfirm]); // Functions @@ -173,11 +174,16 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ }; const onUpload = () => { - if (uploadFile === null) { + if (!(fileList[0]?.originFileObj instanceof File)) { return; } - importResource(uploadFile, passwords, confirmedOverwrite).then(result => { + setImportingModel(true); + importResource( + fileList[0].originFileObj, + passwords, + confirmedOverwrite, + ).then(result => { if (result) { addSuccessToast(t('The import was successful')); clearModal(); @@ -186,9 +192,18 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ }); }; - const changeFile = (event: React.ChangeEvent<HTMLInputElement>) => { - const { files } = event.target as HTMLInputElement; - setUploadFile((files && files[0]) || null); + const changeFile = (info: UploadChangeParam) => { + setFileList([ + { + ...info.file, + status: 'done', + }, + ]); + }; + + const removeFile = (removedFile: UploadFile) => { + setFileList(fileList.filter(file => file.uid !== removedFile.uid)); + return false; }; const confirmOverwrite = (event: React.ChangeEvent<HTMLInputElement>) => { @@ -259,7 +274,9 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ name="model" className="import-model-modal" disablePrimaryButton={ - uploadFile === null || (needsOverwriteConfirm && !confirmedOverwrite) + fileList.length === 0 || + (needsOverwriteConfirm && !confirmedOverwrite) || + importingModel } onHandledPrimaryAction={onUpload} onHide={hide} @@ -270,21 +287,19 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({ title={<h4>{t('Import %s', resourceLabel)}</h4>} > <StyledInputContainer> - <div className="control-label"> - <label htmlFor="modelFile"> - {t('File')} - <span className="required">*</span> - </label> - </div> - <input - ref={fileInputRef} - data-test="model-file-input" + <Upload name="modelFile" id="modelFile" - type="file" + data-test="model-file-input" accept=".yaml,.json,.yml,.zip" + fileList={fileList} onChange={changeFile} - /> + onRemove={removeFile} + // upload is handled by hook + customRequest={() => {}} + > + <Button loading={importingModel}>Select file</Button> + </Upload> </StyledInputContainer> {renderPasswordFields()} {renderOverwriteConfirmation()} diff --git a/superset-frontend/src/components/IndeterminateCheckbox/index.tsx b/superset-frontend/src/components/IndeterminateCheckbox/index.tsx index 4056cac14b67..b29b243775c8 100644 --- a/superset-frontend/src/components/IndeterminateCheckbox/index.tsx +++ b/superset-frontend/src/components/IndeterminateCheckbox/index.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { styled } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface IndeterminateCheckboxProps { indeterminate: boolean; @@ -35,8 +35,18 @@ const CheckboxLabel = styled.label` margin-bottom: 0; `; -const IconWithColor = styled(Icon)` - color: ${({ theme }) => theme.colors.primary.dark1}; +const CheckboxHalf = styled(Icons.CheckboxHalf)` + color: ${({ theme }) => theme.colors.primary.base}; + cursor: pointer; +`; + +const CheckboxOff = styled(Icons.CheckboxOff)` + color: ${({ theme }) => theme.colors.grayscale.base}; + cursor: pointer; +`; + +const CheckboxOn = styled(Icons.CheckboxOn)` + color: ${({ theme }) => theme.colors.primary.base}; cursor: pointer; `; @@ -79,9 +89,9 @@ const IndeterminateCheckbox = React.forwardRef( return ( <> <InputContainer> - {indeterminate && <IconWithColor name="checkbox-half" />} - {!indeterminate && checked && <IconWithColor name="checkbox-on" />} - {!indeterminate && !checked && <Icon name="checkbox-off" />} + {indeterminate && <CheckboxHalf />} + {!indeterminate && checked && <CheckboxOn />} + {!indeterminate && !checked && <CheckboxOff />} <HiddenInput name={id} id={id} diff --git a/superset-frontend/src/components/InfoTooltip/index.tsx b/superset-frontend/src/components/InfoTooltip/index.tsx index b3558593973f..e27c3729a4e9 100644 --- a/superset-frontend/src/components/InfoTooltip/index.tsx +++ b/superset-frontend/src/components/InfoTooltip/index.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { styled } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface InfoTooltipProps { className?: string; @@ -42,16 +42,24 @@ export interface InfoTooltipProps { trigger?: string | Array<string>; overlayStyle?: any; bgColor?: string; + viewBox?: string; } const StyledTooltip = styled(Tooltip)` cursor: pointer; - path:first-of-type { fill: #999999; } `; +const StyledTooltipTitle = styled.span` + display: -webkit-box; + -webkit-line-clamp: 20; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +`; + const defaultOverlayStyle = { fontSize: '12px', lineHeight: '16px', @@ -65,16 +73,17 @@ export default function InfoTooltip({ trigger = 'hover', overlayStyle = defaultOverlayStyle, bgColor = defaultColor, + viewBox = '0 -2 24 24', }: InfoTooltipProps) { return ( <StyledTooltip - title={tooltip} + title={<StyledTooltipTitle>{tooltip}</StyledTooltipTitle>} placement={placement} trigger={trigger} overlayStyle={overlayStyle} color={bgColor} > - <Icon name="info-solid-small" /> + <Icons.InfoSolidSmall className="info-solid-small" viewBox={viewBox} /> </StyledTooltip> ); } diff --git a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx index 18014d49cec3..830695918057 100644 --- a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx +++ b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx @@ -34,7 +34,7 @@ describe('LastUpdated', () => { it('renders a refresh action', () => { const mockAction = jest.fn(); wrapper = mount(<LastUpdated updatedAt={updatedAt} update={mockAction} />); - const props = wrapper.find('[data-test="refresh"]').first().props(); + const props = wrapper.find('[aria-label="refresh"]').first().props(); if (props.onClick) { props.onClick({} as React.MouseEvent); } diff --git a/superset-frontend/src/components/LastUpdated/index.tsx b/superset-frontend/src/components/LastUpdated/index.tsx index dc55957300ed..1b838b5034b5 100644 --- a/superset-frontend/src/components/LastUpdated/index.tsx +++ b/superset-frontend/src/components/LastUpdated/index.tsx @@ -19,13 +19,13 @@ import React, { useEffect, useState, FunctionComponent } from 'react'; import moment, { Moment, MomentInput } from 'moment'; import { t, styled } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; const REFRESH_INTERVAL = 60000; // every minute interface LastUpdatedProps { updatedAt: MomentInput; - update?: React.MouseEventHandler<SVGSVGElement>; + update?: React.MouseEventHandler<HTMLSpanElement>; } moment.updateLocale('en', { calendar: { @@ -42,7 +42,7 @@ const TextStyles = styled.span` color: ${({ theme }) => theme.colors.grayscale.base}; `; -const Refresh = styled(Icon)` +const Refresh = styled(Icons.Refresh)` color: ${({ theme }) => theme.colors.primary.base}; width: auto; height: ${({ theme }) => theme.gridUnit * 5}px; @@ -72,7 +72,7 @@ export const LastUpdated: FunctionComponent<LastUpdatedProps> = ({ return ( <TextStyles> {t('Last Updated %s', timeSince.isValid() ? timeSince.calendar() : '--')} - {update && <Refresh name="refresh" onClick={update} />} + {update && <Refresh onClick={update} />} </TextStyles> ); }; diff --git a/superset-frontend/src/components/ListView/ActionsBar.tsx b/superset-frontend/src/components/ListView/ActionsBar.tsx index ad4553e89f8f..d4e1e675c805 100644 --- a/superset-frontend/src/components/ListView/ActionsBar.tsx +++ b/superset-frontend/src/components/ListView/ActionsBar.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { styled } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; -import { IconName } from 'src/components/Icon'; import Icons from 'src/components/Icons'; import { TooltipPlacement } from 'antd/lib/tooltip'; @@ -27,7 +26,7 @@ export type ActionProps = { label: string; tooltip?: string | React.ReactElement; placement?: TooltipPlacement; - icon: IconName; + icon: string; onClick: () => void; }; diff --git a/superset-frontend/src/components/ListView/CardCollection.tsx b/superset-frontend/src/components/ListView/CardCollection.tsx index a0a0a98ce9a1..d91498c91474 100644 --- a/superset-frontend/src/components/ListView/CardCollection.tsx +++ b/superset-frontend/src/components/ListView/CardCollection.tsx @@ -27,12 +27,21 @@ interface CardCollectionProps { prepareRow: TableInstance['prepareRow']; renderCard?: (row: any) => React.ReactNode; rows: TableInstance['rows']; + showThumbnails?: boolean; } -const CardContainer = styled.div` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(459px, 1fr)); - grid-gap: ${({ theme }) => theme.gridUnit * 8}px; +const CardContainer = styled.div<{ showThumbnails?: boolean }>` + ${({ theme, showThumbnails }) => ` + display: grid; + grid-gap: ${theme.gridUnit * 12}px ${theme.gridUnit * 4}px; + grid-template-columns: repeat(auto-fit, 300px); + margin-top: ${theme.gridUnit * -6}px; + padding: ${ + showThumbnails + ? `${theme.gridUnit * 8 + 3}px ${theme.gridUnit * 9}px` + : `${theme.gridUnit * 8 + 1}px ${theme.gridUnit * 9}px` + }; + `} `; const CardWrapper = styled.div` @@ -51,6 +60,7 @@ export default function CardCollection({ prepareRow, renderCard, rows, + showThumbnails, }: CardCollectionProps) { function handleClick( event: React.MouseEvent<HTMLDivElement, MouseEvent>, @@ -65,7 +75,7 @@ export default function CardCollection({ if (!renderCard) return null; return ( - <CardContainer> + <CardContainer showThumbnails={showThumbnails}> {loading && rows.length === 0 && [...new Array(25)].map((e, i) => ( diff --git a/superset-frontend/src/components/ListView/Filters/Search.tsx b/superset-frontend/src/components/ListView/Filters/Search.tsx index 329cf7949972..23a8e6900a41 100644 --- a/superset-frontend/src/components/ListView/Filters/Search.tsx +++ b/superset-frontend/src/components/ListView/Filters/Search.tsx @@ -35,7 +35,7 @@ export default function SearchFilter({ const [value, setValue] = useState(initialValue || ''); const handleSubmit = () => { if (value) { - onSubmit(value); + onSubmit(value.trim()); } }; const onClear = () => { diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 8d9182ac70e4..195cae2d465f 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -23,7 +23,7 @@ import Alert from 'src/components/Alert'; import { ReactComponent as EmptyImage } from 'images/empty.svg'; import cx from 'classnames'; import Button from 'src/components/Button'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox'; import { TableCollection, Pagination } from 'src/components/dataViewCommon'; import CardCollection from './CardCollection'; @@ -114,6 +114,10 @@ const BulkSelectWrapper = styled(Alert)` vertical-align: middle; position: relative; } + + .ant-alert-close-icon { + margin-top: ${({ theme }) => theme.gridUnit * 1.5}px; + } `; const bulkSelectColumnConfig = { @@ -138,7 +142,7 @@ const ViewModeContainer = styled.div` display: inline-block; border-radius: ${({ theme }) => theme.gridUnit / 2}px; padding: ${({ theme }) => theme.gridUnit}px; - padding-bottom: 0; + padding-bottom: ${({ theme }) => theme.gridUnit * 0.5}px; &:first-of-type { margin-right: ${({ theme }) => theme.gridUnit * 2}px; @@ -178,7 +182,7 @@ const ViewModeToggle = ({ }} className={cx('toggle-button', { active: mode === 'card' })} > - <Icon name="card-view" /> + <Icons.CardView /> </div> <div role="button" @@ -189,7 +193,7 @@ const ViewModeToggle = ({ }} className={cx('toggle-button', { active: mode === 'table' })} > - <Icon name="list-view" /> + <Icons.ListView /> </div> </ViewModeContainer> ); @@ -217,6 +221,7 @@ export interface ListViewProps<T extends object = any> { cardSortSelectOptions?: Array<CardSortSelectOption>; defaultViewMode?: ViewModeType; highlightRowId?: number; + showThumbnails?: boolean; emptyState?: { message?: string; slot?: React.ReactNode; @@ -238,6 +243,7 @@ function ListView<T extends object = any>({ disableBulkSelect = () => {}, renderBulkSelectCopy = selected => t('%s Selected', selected.length), renderCard, + showThumbnails, cardSortSelectOptions, defaultViewMode = 'card', highlightRowId, @@ -372,6 +378,7 @@ function ListView<T extends object = any>({ renderCard={renderCard} rows={rows} loading={loading} + showThumbnails={showThumbnails} /> )} {viewMode === 'table' && ( diff --git a/superset-frontend/src/components/ListViewCard/ListViewCard.stories.tsx b/superset-frontend/src/components/ListViewCard/ListViewCard.stories.tsx index fdd3778d5dc4..27857ce1fbdf 100644 --- a/superset-frontend/src/components/ListViewCard/ListViewCard.stories.tsx +++ b/superset-frontend/src/components/ListViewCard/ListViewCard.stories.tsx @@ -22,7 +22,6 @@ import { withKnobs, boolean, select, text } from '@storybook/addon-knobs'; import DashboardImg from 'images/dashboard-card-fallback.svg'; import ChartImg from 'images/chart-card-fallback.svg'; import { Dropdown, Menu } from 'src/common/components'; -import Icon from 'src/components/Icon'; import Icons from 'src/components/Icons'; import FaveStar from 'src/components/FaveStar'; import ListViewCard from '.'; @@ -76,7 +75,7 @@ export const SupersetListViewCard = () => ( </Menu> } > - <Icon name="more-horiz" /> + <Icons.MoreHoriz /> </Dropdown> </ListViewCard.Actions> } diff --git a/superset-frontend/src/components/ListViewCard/index.tsx b/superset-frontend/src/components/ListViewCard/index.tsx index fad515110c09..bfd6d6a82da3 100644 --- a/superset-frontend/src/components/ListViewCard/index.tsx +++ b/superset-frontend/src/components/ListViewCard/index.tsx @@ -18,7 +18,6 @@ */ import React from 'react'; import { styled, useTheme } from '@superset-ui/core'; -import Icon, { IconName } from 'src/components/Icon'; import { AntdCard, Skeleton, ThinSkeleton } from 'src/common/components'; import { Tooltip } from 'src/components/Tooltip'; import ImageLoader, { BackgroundPosition } from './ImageLoader'; @@ -93,22 +92,28 @@ const TitleContainer = styled.div` .card-actions { margin-left: auto; align-self: flex-end; - padding-left: ${({ theme }) => theme.gridUnit * 8}px; + padding-left: ${({ theme }) => theme.gridUnit}px; + span[role='img'] { + display: flex; + align-items: center; + } } `; const TitleLink = styled.span` + overflow: hidden; + text-overflow: ellipsis; & a { color: ${({ theme }) => theme.colors.grayscale.dark1} !important; - overflow: hidden; - text-overflow: ellipsis; - - & + .title-right { - margin-left: ${({ theme }) => theme.gridUnit * 2}px; - } } `; +const TitleRight = styled.span` + position: absolute; + right: -1px; + bottom: ${({ theme }) => theme.gridUnit}px; +`; + const CoverFooter = styled.div` display: flex; flex-wrap: nowrap; @@ -154,7 +159,7 @@ interface CardProps { coverRight?: React.ReactNode; actions?: React.ReactNode | null; rows?: number | string; - avatar?: string; + avatar?: React.ReactElement | null; cover?: React.ReactNode | null; } @@ -247,14 +252,14 @@ function ListViewCard({ <Link to={url!}>{title}</Link> </TitleLink> </Tooltip> - {titleRight && <div className="title-right"> {titleRight}</div>} + {titleRight && <TitleRight>{titleRight}</TitleRight>} <div className="card-actions" data-test="card-actions"> {actions} </div> </TitleContainer> } description={description} - avatar={avatar ? <Icon name={avatar as IconName} /> : null} + avatar={avatar || null} /> )} </StyledCard> diff --git a/superset-frontend/src/components/Menu/LanguagePicker.tsx b/superset-frontend/src/components/Menu/LanguagePicker.tsx index 1f0f49d9bbc5..faba09a87ffa 100644 --- a/superset-frontend/src/components/Menu/LanguagePicker.tsx +++ b/superset-frontend/src/components/Menu/LanguagePicker.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { MainNav as Menu } from 'src/common/components'; import { styled } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; const { SubMenu } = Menu; @@ -66,7 +66,7 @@ export default function LanguagePicker(props: LanguagePickerProps) { <StyledFlag className={`flag ${languages[locale].flag}`} /> </div> } - icon={<Icon name="triangle-down" />} + icon={<Icons.TriangleDown />} {...rest} > {Object.keys(languages).map(langKey => ( diff --git a/superset-frontend/src/components/Menu/Menu.test.tsx b/superset-frontend/src/components/Menu/Menu.test.tsx index daeb5262f726..212521bd4e82 100644 --- a/superset-frontend/src/components/Menu/Menu.test.tsx +++ b/superset-frontend/src/components/Menu/Menu.test.tsx @@ -75,8 +75,11 @@ const mockedProps = { icon: '/static/assets/images/superset-logo-horiz.png', alt: 'Superset', width: '126', + tooltip: '', + text: '', }, navbar_right: { + show_watermark: false, bug_report_url: '/report/', documentation_url: '/docs/', languages: { diff --git a/superset-frontend/src/components/Menu/Menu.tsx b/superset-frontend/src/components/Menu/Menu.tsx index 0875b85f8a66..098aa3acdd5b 100644 --- a/superset-frontend/src/components/Menu/Menu.tsx +++ b/superset-frontend/src/components/Menu/Menu.tsx @@ -22,21 +22,25 @@ import { debounce } from 'lodash'; import { Global } from '@emotion/react'; import { getUrlParam } from 'src/utils/urlUtils'; import { MainNav as DropdownMenu, MenuMode } from 'src/common/components'; +import { Tooltip } from 'src/components/Tooltip'; import { Link } from 'react-router-dom'; import { Row, Col, Grid } from 'antd'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; +import { URL_PARAMS } from 'src/constants'; import RightMenu from './MenuRight'; import { Languages } from './LanguagePicker'; -import { URL_PARAMS } from '../../constants'; interface BrandProps { path: string; icon: string; alt: string; width: string | number; + tooltip: string; + text: string; } export interface NavBarProps { + show_watermark: boolean; bug_report_url?: string; version_string?: string; version_sha?: string; @@ -78,7 +82,6 @@ export interface MenuObjectProps extends MenuObjectChildProps { const StyledHeader = styled.header` background-color: white; margin-bottom: 2px; - border-bottom: 2px solid ${({ theme }) => theme.colors.grayscale.light4}px; &:nth-last-of-type(2) nav { margin-bottom: 2px; } @@ -91,6 +94,33 @@ const StyledHeader = styled.header` flex-direction: column; justify-content: center; } + .navbar-brand-text { + border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + height: 100%; + color: ${({ theme }) => theme.colors.grayscale.dark1}; + padding-left: ${({ theme }) => theme.gridUnit * 4}px; + padding-right: ${({ theme }) => theme.gridUnit * 4}px; + margin-right: ${({ theme }) => theme.gridUnit * 6}px; + font-size: ${({ theme }) => theme.gridUnit * 4}px; + float: left; + display: flex; + flex-direction: column; + justify-content: center; + + span { + max-width: ${({ theme }) => theme.gridUnit * 58}px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + @media (max-width: 1127px) { + display: none; + } + } + .main-nav .ant-menu-submenu-title > svg { + top: ${({ theme }) => theme.gridUnit * 5.25}px; + } @media (max-width: 767px) { .navbar-brand { float: none; @@ -186,7 +216,11 @@ export function Menu({ ); } return ( - <SubMenu key={index} title={label} icon={<Icon name="triangle-down" />}> + <SubMenu + key={index} + title={label} + icon={showMenu === 'inline' ? <></> : <Icons.TriangleDown />} + > {childs?.map((child: MenuObjectChildProps | string, index1: number) => { if (typeof child === 'string' && child === '-') { return <DropdownMenu.Divider key={`$${index1}`} />; @@ -221,9 +255,21 @@ export function Menu({ /> <Row> <Col md={16} xs={24}> - <a className="navbar-brand" href={brand.path}> - <img width={brand.width} src={brand.icon} alt={brand.alt} /> - </a> + <Tooltip + id="brand-tooltip" + placement="bottomLeft" + title={brand.tooltip} + arrowPointAtCenter + > + <a className="navbar-brand" href={brand.path}> + <img width={brand.width} src={brand.icon} alt={brand.alt} /> + </a> + </Tooltip> + {brand.text && ( + <div className="navbar-brand-text"> + <span>{brand.text}</span> + </div> + )} <DropdownMenu mode={showMenu} data-test="navbar-top" diff --git a/superset-frontend/src/components/Menu/MenuRight.tsx b/superset-frontend/src/components/Menu/MenuRight.tsx index a722834bdf44..35d2cb0fcf3c 100644 --- a/superset-frontend/src/components/Menu/MenuRight.tsx +++ b/superset-frontend/src/components/Menu/MenuRight.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { MainNav as Menu } from 'src/common/components'; import { t, styled, css, SupersetTheme } from '@superset-ui/core'; import { Link } from 'react-router-dom'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import LanguagePicker from './LanguagePicker'; import { NavBarProps, MenuObjectProps } from './Menu'; @@ -59,6 +59,9 @@ const StyledDiv = styled.div<{ align: string }>` justify-content: ${({ align }) => align}; align-items: center; margin-right: ${({ theme }) => theme.gridUnit}px; + .ant-menu-submenu-title > svg { + top: ${({ theme }) => theme.gridUnit * 5.25}px; + } `; const StyledAnchor = styled.a` @@ -89,7 +92,7 @@ const RightMenu = ({ title={ <StyledI data-test="new-dropdown-icon" className="fa fa-plus" /> } - icon={<Icon name="triangle-down" />} + icon={<Icons.TriangleDown />} > {dropdownItems.map(menu => ( <Menu.Item key={menu.label}> @@ -104,7 +107,7 @@ const RightMenu = ({ ))} </SubMenu> )} - <SubMenu title="Settings" icon={<Icon name="triangle-down" />}> + <SubMenu title="Settings" icon={<Icons.TriangleDown iconSize="xl" />}> {settings.map((section, index) => [ <Menu.ItemGroup key={`${section.label}`} title={section.label}> {section.childs?.map(child => { @@ -133,9 +136,11 @@ const RightMenu = ({ <a href={navbarRight.user_profile_url}>{t('Profile')}</a> </Menu.Item> )} - <Menu.Item key="info"> - <a href={navbarRight.user_info_url}>{t('Info')}</a> - </Menu.Item> + {navbarRight.user_info_url && ( + <Menu.Item key="info"> + <a href={navbarRight.user_info_url}>{t('Info')}</a> + </Menu.Item> + )} <Menu.Item key="logout"> <a href={navbarRight.user_logout_url}>{t('Logout')}</a> </Menu.Item> @@ -145,6 +150,11 @@ const RightMenu = ({ <Menu.Divider key="version-info-divider" />, <Menu.ItemGroup key="about-section" title={t('About')}> <div className="about-section"> + {navbarRight.show_watermark && ( + <div css={versionInfoStyles}> + {t('Powered by Apache Superset')} + </div> + )} {navbarRight.version_string && ( <div css={versionInfoStyles}> Version: {navbarRight.version_string} diff --git a/superset-frontend/src/components/Modal/Modal.tsx b/superset-frontend/src/components/Modal/Modal.tsx index 661d216ce4f8..a23f40a5360f 100644 --- a/superset-frontend/src/components/Modal/Modal.tsx +++ b/superset-frontend/src/components/Modal/Modal.tsx @@ -20,7 +20,10 @@ import React from 'react'; import { isNil } from 'lodash'; import { styled, t } from '@superset-ui/core'; import { css } from '@emotion/react'; -import { Modal as BaseModal } from 'src/common/components'; +import { + Modal as AntdModal, + ModalProps as AntdModalProps, +} from 'src/common/components'; import Button from 'src/components/Button'; export interface ModalProps { @@ -52,6 +55,12 @@ interface StyledModalProps { hideFooter?: boolean; } +const BaseModal = (props: AntdModalProps) => ( + // Removes mask animation. Fixed in 4.6.0. + // https://github.com/ant-design/ant-design/issues/27192 + <AntdModal {...props} maskTransitionName="" /> +); + export const StyledModal = styled(BaseModal)<StyledModalProps>` ${({ theme, responsive, maxWidth }) => responsive && @@ -188,10 +197,10 @@ CustomModal.displayName = 'Modal'; // and demote it as the default export. // We should start using AntD component interfaces going forward. const Modal = Object.assign(CustomModal, { - error: BaseModal.error, - warning: BaseModal.warning, - confirm: BaseModal.confirm, - useModal: BaseModal.useModal, + error: AntdModal.error, + warning: AntdModal.warning, + confirm: AntdModal.confirm, + useModal: AntdModal.useModal, }); export default Modal; diff --git a/superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx b/superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx index 0e46993f45eb..61dd7be394a4 100644 --- a/superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx +++ b/superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx @@ -84,40 +84,6 @@ test('Open Omnibar with ctrl + k with featureflag enabled', () => { ).not.toBeVisible(); }); -test('Open Omnibar with ctrl + s with featureflag enabled', () => { - (isFeatureEnabled as jest.Mock).mockImplementation( - (ff: string) => ff === 'OMNIBAR', - ); - const logEvent = jest.fn(); - render( - <div data-test="test"> - <OmniContainer logEvent={logEvent} /> - </div>, - ); - - expect( - screen.queryByPlaceholderText('Search all dashboards'), - ).not.toBeInTheDocument(); - - // show Omnibar - fireEvent.keyDown(screen.getByTestId('test'), { - ctrlKey: true, - code: 'KeyS', - }); - expect( - screen.queryByPlaceholderText('Search all dashboards'), - ).toBeInTheDocument(); - - // hide Omnibar - fireEvent.keyDown(screen.getByTestId('test'), { - ctrlKey: true, - code: 'KeyS', - }); - expect( - screen.queryByPlaceholderText('Search all dashboards'), - ).not.toBeVisible(); -}); - test('Open Omnibar with Command + k with featureflag enabled', () => { (isFeatureEnabled as jest.Mock).mockImplementation( (ff: string) => ff === 'OMNIBAR', @@ -152,7 +118,7 @@ test('Open Omnibar with Command + k with featureflag enabled', () => { ).not.toBeVisible(); }); -test('Open Omnibar with Command + s with featureflag enabled', () => { +test('Open Omnibar with Cmd/Ctrl-K and close with ESC', () => { (isFeatureEnabled as jest.Mock).mockImplementation( (ff: string) => ff === 'OMNIBAR', ); @@ -169,17 +135,17 @@ test('Open Omnibar with Command + s with featureflag enabled', () => { // show Omnibar fireEvent.keyDown(screen.getByTestId('test'), { - metaKey: true, - code: 'KeyS', + ctrlKey: true, + code: 'KeyK', }); expect( screen.queryByPlaceholderText('Search all dashboards'), ).toBeInTheDocument(); - // hide Omnibar + // Close Omnibar fireEvent.keyDown(screen.getByTestId('test'), { - metaKey: true, - code: 'KeyS', + key: 'Escape', + code: 'Escape', }); expect( screen.queryByPlaceholderText('Search all dashboards'), diff --git a/superset-frontend/src/components/OmniContainer/index.tsx b/superset-frontend/src/components/OmniContainer/index.tsx index 338661f7a43b..5a18d5e4e44d 100644 --- a/superset-frontend/src/components/OmniContainer/index.tsx +++ b/superset-frontend/src/components/OmniContainer/index.tsx @@ -31,6 +31,7 @@ const OmniModal = styled(Modal)` .ant-modal-body { padding: 0; + overflow: visible; } `; @@ -47,7 +48,16 @@ export default function OmniContainer({ logEvent }: Props) { function handleKeydown(event: KeyboardEvent) { if (!isFeatureEnabled(FeatureFlag.OMNIBAR)) return; const controlOrCommand = event.ctrlKey || event.metaKey; - const isOk = ['KeyK', 'KeyS'].includes(event.code); // valid keys "s" or "k" + const isOk = ['KeyK'].includes(event.code); + const isEsc = event.key === 'Escape'; + if (isEsc && showOmni.current) { + logEvent(LOG_ACTIONS_OMNIBAR_TRIGGERED, { + show_omni: false, + }); + showOmni.current = false; + setShowModal(false); + return; + } if (controlOrCommand && isOk) { logEvent(LOG_ACTIONS_OMNIBAR_TRIGGERED, { show_omni: !!showOmni.current, diff --git a/superset-frontend/src/components/PopoverDropdown/index.tsx b/superset-frontend/src/components/PopoverDropdown/index.tsx index 1bef4c65218f..1f3f1747bae1 100644 --- a/superset-frontend/src/components/PopoverDropdown/index.tsx +++ b/superset-frontend/src/components/PopoverDropdown/index.tsx @@ -20,7 +20,7 @@ import React from 'react'; import cx from 'classnames'; import { styled, useTheme } from '@superset-ui/core'; import { Dropdown, Menu } from 'src/common/components'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface OptionProps { value: string; @@ -109,7 +109,10 @@ const PopoverDropdown = (props: PopoverDropdownProps) => { > <div role="button" css={{ display: 'flex', alignItems: 'center' }}> {selected && renderButton(selected)} - <Icon name="caret-down" css={{ marginTop: theme.gridUnit }} /> + <Icons.CaretDown + iconColor={theme.colors.grayscale.base} + css={{ marginTop: theme.gridUnit * 0.5 }} + /> </div> </Dropdown> ); diff --git a/superset-frontend/src/components/PopoverSection/index.tsx b/superset-frontend/src/components/PopoverSection/index.tsx index ec45f178614a..b2877e1efcd6 100644 --- a/superset-frontend/src/components/PopoverSection/index.tsx +++ b/superset-frontend/src/components/PopoverSection/index.tsx @@ -19,7 +19,7 @@ import React, { MouseEventHandler, ReactNode } from 'react'; import { useTheme } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface PopoverSectionProps { title: string; @@ -57,19 +57,17 @@ export default function PopoverSection({ <strong data-test="popover-title">{title}</strong> {info && ( <Tooltip title={info} css={{ marginLeft: theme.gridUnit }}> - <Icon + <Icons.InfoSolidSmall role="img" - name="info-solid" width={14} height={14} - color={theme.colors.grayscale.light1} + iconColor={theme.colors.grayscale.light1} /> </Tooltip> )} - <Icon + <Icons.Check role="img" - name="check" - color={ + iconColor={ isSelected ? theme.colors.primary.base : theme.colors.grayscale.base } /> diff --git a/superset-frontend/src/components/RefreshLabel/index.tsx b/superset-frontend/src/components/RefreshLabel/index.tsx index 69bb213a3ae9..40b3215eb6d2 100644 --- a/superset-frontend/src/components/RefreshLabel/index.tsx +++ b/superset-frontend/src/components/RefreshLabel/index.tsx @@ -19,17 +19,17 @@ import React, { MouseEventHandler } from 'react'; import { SupersetTheme } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; -import Icon, { IconProps } from 'src/components/Icon'; +import Icons, { IconType } from 'src/components/Icons'; export interface RefreshLabelProps { - onClick: MouseEventHandler<SVGSVGElement>; + onClick: MouseEventHandler<HTMLSpanElement>; tooltipContent: string; } const RefreshLabel = ({ onClick, tooltipContent }: RefreshLabelProps) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const IconWithoutRef = React.forwardRef((props: IconProps, ref: any) => ( - <Icon {...props} /> + const IconWithoutRef = React.forwardRef((props: IconType, ref: any) => ( + <Icons.Refresh {...props} /> )); return ( @@ -37,7 +37,6 @@ const RefreshLabel = ({ onClick, tooltipContent }: RefreshLabelProps) => { <IconWithoutRef role="button" onClick={onClick} - name="refresh" css={(theme: SupersetTheme) => ({ cursor: 'pointer', color: theme.colors.grayscale.base, diff --git a/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx b/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx new file mode 100644 index 000000000000..2ffc6f28600e --- /dev/null +++ b/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx @@ -0,0 +1,117 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { t, SupersetTheme, css, useTheme } from '@superset-ui/core'; +import Icons from 'src/components/Icons'; +import { Switch } from 'src/components/Switch'; +import { AlertObject } from 'src/views/CRUD/alert/types'; +import { Menu, NoAnimationDropdown } from 'src/common/components'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; + +import DeleteModal from 'src/components/DeleteModal'; + +const deleteColor = (theme: SupersetTheme) => css` + color: ${theme.colors.error.base}; +`; + +export default function HeaderReportActionsDropDown({ + showReportModal, + toggleActive, + deleteActiveReport, +}: { + showReportModal: () => void; + toggleActive: (data: AlertObject, checked: boolean) => void; + deleteActiveReport: (data: AlertObject) => void; +}) { + const reports = useSelector<any, AlertObject>(state => state.reports); + const reportsIds = Object.keys(reports); + const report = reports[reportsIds[0]]; + const [ + currentReportDeleting, + setCurrentReportDeleting, + ] = useState<AlertObject | null>(null); + const theme = useTheme(); + + const toggleActiveKey = async (data: AlertObject, checked: boolean) => { + if (data?.id) { + toggleActive(data, checked); + } + }; + + const handleReportDelete = (report: AlertObject) => { + deleteActiveReport(report); + setCurrentReportDeleting(null); + }; + + const menu = () => ( + <Menu selectable={false} css={{ width: '200px' }}> + <Menu.Item> + {t('Email reports active')} + <Switch + data-test="toggle-active" + checked={report?.active} + onClick={(checked: boolean) => toggleActiveKey(report, checked)} + size="small" + css={{ marginLeft: theme.gridUnit * 2 }} + /> + </Menu.Item> + <Menu.Item onClick={showReportModal}>{t('Edit email report')}</Menu.Item> + <Menu.Item + onClick={() => setCurrentReportDeleting(report)} + css={deleteColor} + > + {t('Delete email report')} + </Menu.Item> + </Menu> + ); + + return isFeatureEnabled(FeatureFlag.ALERT_REPORTS) ? ( + <> + <NoAnimationDropdown + // ref={ref} + overlay={menu()} + trigger={['click']} + getPopupContainer={(triggerNode: any) => + triggerNode.closest('.action-button') + } + > + <span role="button" className="action-button" tabIndex={0}> + <Icons.Calendar /> + </span> + </NoAnimationDropdown> + {currentReportDeleting && ( + <DeleteModal + description={t( + 'This action will permanently delete %s.', + currentReportDeleting.name, + )} + onConfirm={() => { + if (currentReportDeleting) { + handleReportDelete(currentReportDeleting); + } + }} + onHide={() => setCurrentReportDeleting(null)} + open + title={t('Delete Report?')} + /> + )} + </> + ) : null; +} diff --git a/superset-frontend/src/components/ReportModal/index.test.tsx b/superset-frontend/src/components/ReportModal/index.test.tsx new file mode 100644 index 000000000000..44e3d0ef65c5 --- /dev/null +++ b/superset-frontend/src/components/ReportModal/index.test.tsx @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from 'spec/helpers/testing-library'; +import * as featureFlags from 'src/featureFlags'; +import { FeatureFlag } from '@superset-ui/core'; +import ReportModal from '.'; + +let isFeatureEnabledMock: jest.MockInstance<boolean, [string]>; + +const NOOP = () => {}; + +const defaultProps = { + addDangerToast: NOOP, + addSuccessToast: NOOP, + addReport: NOOP, + onHide: NOOP, + onReportAdd: NOOP, + show: true, + userId: 1, + userEmail: 'test@test.com', + dashboardId: 1, + creationMethod: 'charts_dashboards', + props: { + chart: { + sliceFormData: { + viz_type: 'table', + }, + }, + }, +}; + +describe('Email Report Modal', () => { + beforeAll(() => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation( + (featureFlag: FeatureFlag) => featureFlag === FeatureFlag.ALERT_REPORTS, + ); + }); + + beforeEach(() => { + render(<ReportModal {...defaultProps} />, { useRedux: true }); + }); + + afterAll(() => { + // @ts-ignore + isFeatureEnabledMock.restore(); + }); + + it('inputs respond correctly', () => { + // ----- Report name textbox + // Initial value + const reportNameTextbox = screen.getByTestId('report-name-test'); + expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); + // Type in the textbox and assert that it worked + userEvent.type(reportNameTextbox, 'Report name text test'); + expect(reportNameTextbox).toHaveDisplayValue('Report name text test'); + + // ----- Report description textbox + // Initial value + const reportDescriptionTextbox = screen.getByTestId( + 'report-description-test', + ); + expect(reportDescriptionTextbox).toHaveDisplayValue(''); + // Type in the textbox and assert that it worked + userEvent.type(reportDescriptionTextbox, 'Report description text test'); + expect(reportDescriptionTextbox).toHaveDisplayValue( + 'Report description text test', + ); + + // ----- Crontab + const crontabInputs = screen.getAllByRole('combobox'); + expect(crontabInputs).toHaveLength(5); + }); + + it('does not allow user to create a report without a name', () => { + // Grab name textbox and add button + const reportNameTextbox = screen.getByTestId('report-name-test'); + const addButton = screen.getByRole('button', { name: /add/i }); + + // Add button should be enabled while name textbox has text + expect(reportNameTextbox).toHaveDisplayValue('Weekly Report'); + expect(addButton).toBeEnabled(); + + // Clear the text from the name textbox + userEvent.clear(reportNameTextbox); + + // Add button should now be disabled, blocking user from creation + expect(reportNameTextbox).toHaveDisplayValue(''); + expect(addButton).toBeDisabled(); + }); +}); diff --git a/superset-frontend/src/components/ReportModal/index.tsx b/superset-frontend/src/components/ReportModal/index.tsx new file mode 100644 index 000000000000..e24d1d756c63 --- /dev/null +++ b/superset-frontend/src/components/ReportModal/index.tsx @@ -0,0 +1,398 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { + useState, + useEffect, + useCallback, + useReducer, + Reducer, + FunctionComponent, +} from 'react'; +import { t, SupersetTheme } from '@superset-ui/core'; +import { bindActionCreators } from 'redux'; +import { connect, useDispatch, useSelector } from 'react-redux'; +import { addReport, editReport } from 'src/reports/actions/reports'; +import { AlertObject } from 'src/views/CRUD/alert/types'; + +import TimezoneSelector from 'src/components/TimezoneSelector'; +import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput'; +import Icons from 'src/components/Icons'; +import withToasts from 'src/messageToasts/enhancers/withToasts'; +import { CronError } from 'src/components/CronPicker'; +import { RadioChangeEvent } from 'src/common/components'; +import { + StyledModal, + StyledTopSection, + StyledBottomSection, + StyledIconWrapper, + StyledScheduleTitle, + StyledCronPicker, + StyledCronError, + noBottomMargin, + StyledFooterButton, + TimezoneHeaderStyle, + SectionHeaderStyle, + StyledMessageContentTitle, + StyledRadio, + StyledRadioGroup, +} from './styles'; + +export interface ReportObject { + id?: number; + active: boolean; + crontab: string; + dashboard?: number; + chart?: number; + description?: string; + log_retention: number; + name: string; + owners: number[]; + recipients: [{ recipient_config_json: { target: string }; type: string }]; + report_format: string; + timezone: string; + type: string; + validator_config_json: {} | null; + validator_type: string; + working_timeout: number; + creation_method: string; +} + +interface ChartObject { + id: number; + chartAlert: string; + chartStatus: string; + chartUpdateEndTime: number; + chartUpdateStartTime: number; + latestQueryFormData: object; + queryController: { abort: () => {} }; + queriesResponse: object; + triggerQuery: boolean; + lastRendered: number; +} + +interface ReportProps { + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; + addReport: (report?: ReportObject) => {}; + onHide: () => {}; + onReportAdd: (report?: ReportObject) => {}; + show: boolean; + userId: number; + userEmail: string; + dashboardId?: number; + chart?: ChartObject; + creationMethod: string; + props: any; +} + +interface ReportPayloadType { + name: string; + value: string; +} + +enum ActionType { + inputChange, + fetched, + reset, +} + +type ReportActionType = + | { + type: ActionType.inputChange; + payload: ReportPayloadType; + } + | { + type: ActionType.fetched; + payload: Partial<ReportObject>; + } + | { + type: ActionType.reset; + }; + +const TEXT_BASED_VISUALIZATION_TYPES = [ + 'pivot_table', + 'pivot_table_v2', + 'table', + 'paired_ttest', +]; + +const NOTIFICATION_FORMATS = { + TEXT: 'TEXT', + PNG: 'PNG', + CSV: 'CSV', +}; + +const reportReducer = ( + state: Partial<ReportObject> | null, + action: ReportActionType, +): Partial<ReportObject> | null => { + const initialState = { + name: 'Weekly Report', + }; + + switch (action.type) { + case ActionType.inputChange: + return { + ...initialState, + ...state, + [action.payload.name]: action.payload.value, + }; + case ActionType.fetched: + return { + ...initialState, + ...action.payload, + }; + case ActionType.reset: + return { ...initialState }; + default: + return state; + } +}; + +const ReportModal: FunctionComponent<ReportProps> = ({ + onReportAdd, + onHide, + show = false, + ...props +}) => { + const vizType = props.props.chart?.sliceFormData?.viz_type; + const isChart = !!props.props.chart; + const defaultNotificationFormat = + isChart && TEXT_BASED_VISUALIZATION_TYPES.includes(vizType) + ? NOTIFICATION_FORMATS.TEXT + : NOTIFICATION_FORMATS.PNG; + const [currentReport, setCurrentReport] = useReducer< + Reducer<Partial<ReportObject> | null, ReportActionType> + >(reportReducer, null); + const onChange = useCallback((type: any, payload: any) => { + setCurrentReport({ type, payload }); + }, []); + const [error, setError] = useState<CronError>(); + // const [isLoading, setLoading] = useState<boolean>(false); + const dispatch = useDispatch(); + // Report fetch logic + const reports = useSelector<any, AlertObject>(state => state.reports); + const isEditMode = reports && Object.keys(reports).length; + + useEffect(() => { + if (isEditMode) { + const reportsIds = Object.keys(reports); + const report = reports[reportsIds[0]]; + setCurrentReport({ + type: ActionType.fetched, + payload: report, + }); + } else { + setCurrentReport({ + type: ActionType.reset, + }); + } + }, [reports]); + const onClose = () => { + onHide(); + }; + const onSave = async () => { + // Create new Report + const newReportValues: Partial<ReportObject> = { + crontab: currentReport?.crontab, + dashboard: props.props.dashboardId, + chart: props.props.chart?.id, + description: currentReport?.description, + name: currentReport?.name, + owners: [props.props.userId], + recipients: [ + { + recipient_config_json: { target: props.props.userEmail }, + type: 'Email', + }, + ], + type: 'Report', + creation_method: props.props.creationMethod, + active: true, + report_format: currentReport?.report_format || defaultNotificationFormat, + timezone: currentReport?.timezone, + }; + + if (isEditMode) { + await dispatch( + editReport(currentReport?.id, newReportValues as ReportObject), + ); + } else { + await dispatch(addReport(newReportValues as ReportObject)); + } + + if (onReportAdd) { + onReportAdd(); + } + + onClose(); + }; + + const wrappedTitle = ( + <StyledIconWrapper> + <Icons.Calendar /> + <span className="text"> + {isEditMode ? t('Edit Email Report') : t('New Email Report')} + </span> + </StyledIconWrapper> + ); + + const renderModalFooter = ( + <> + <StyledFooterButton key="back" onClick={onClose}> + {t('Cancel')} + </StyledFooterButton> + <StyledFooterButton + key="submit" + buttonStyle="primary" + onClick={onSave} + disabled={!currentReport?.name} + > + {isEditMode ? t('Save') : t('Add')} + </StyledFooterButton> + </> + ); + + const renderMessageContentSection = ( + <> + <StyledMessageContentTitle> + <h4>{t('Message Content')}</h4> + </StyledMessageContentTitle> + <div className="inline-container"> + <StyledRadioGroup + onChange={(event: RadioChangeEvent) => { + onChange(ActionType.inputChange, { + name: 'report_format', + value: event.target.value, + }); + }} + value={currentReport?.report_format || defaultNotificationFormat} + > + {TEXT_BASED_VISUALIZATION_TYPES.includes(vizType) && ( + <StyledRadio value={NOTIFICATION_FORMATS.TEXT}> + {t('Text embedded in email')} + </StyledRadio> + )} + <StyledRadio value={NOTIFICATION_FORMATS.PNG}> + {t('Image (PNG) embedded in email')} + </StyledRadio> + <StyledRadio value={NOTIFICATION_FORMATS.CSV}> + {t('Formatted CSV attached in email')} + </StyledRadio> + </StyledRadioGroup> + </div> + </> + ); + + return ( + <StyledModal + show={show} + onHide={onClose} + title={wrappedTitle} + footer={renderModalFooter} + width="432" + centered + > + <StyledTopSection> + <LabeledErrorBoundInput + id="name" + name="name" + value={currentReport?.name || ''} + placeholder="Weekly Report" + required + validationMethods={{ + onChange: ({ target }: { target: HTMLInputElement }) => + onChange(ActionType.inputChange, { + name: target.name, + value: target.value, + }), + }} + errorMessage={ + currentReport?.name === 'error' ? t('REPORT NAME ERROR') : '' + } + label="Report Name" + data-test="report-name-test" + /> + + <LabeledErrorBoundInput + id="description" + name="description" + value={currentReport?.description || ''} + validationMethods={{ + onChange: ({ target }: { target: HTMLInputElement }) => + onChange(ActionType.inputChange, { + name: target.name, + value: target.value, + }), + }} + errorMessage={ + currentReport?.description === 'error' ? t('DESCRIPTION ERROR') : '' + } + label="Description" + placeholder="Include a description that will be sent with your report" + css={noBottomMargin} + data-test="report-description-test" + /> + </StyledTopSection> + + <StyledBottomSection> + <StyledScheduleTitle> + <h4 css={(theme: SupersetTheme) => SectionHeaderStyle(theme)}> + {t('Schedule')} + </h4> + <p>{t('Scheduled reports will be sent to your email as a PNG')}</p> + </StyledScheduleTitle> + + <StyledCronPicker + clearButton={false} + value={currentReport?.crontab || '0 12 * * 1'} + setValue={(newValue: string) => { + onChange(ActionType.inputChange, { + name: 'crontab', + value: newValue, + }); + }} + onError={setError} + /> + <StyledCronError>{error}</StyledCronError> + <div + className="control-label" + css={(theme: SupersetTheme) => TimezoneHeaderStyle(theme)} + > + {t('Timezone')} + </div> + <TimezoneSelector + onTimezoneChange={value => { + setCurrentReport({ + type: ActionType.inputChange, + payload: { name: 'timezone', value }, + }); + }} + timezone={currentReport?.timezone} + /> + {isChart && renderMessageContentSection} + </StyledBottomSection> + </StyledModal> + ); +}; + +const mapDispatchToProps = (dispatch: any) => + bindActionCreators({ addReport, editReport }, dispatch); + +export default connect(null, mapDispatchToProps)(withToasts(ReportModal)); diff --git a/superset-frontend/src/components/ReportModal/styles.tsx b/superset-frontend/src/components/ReportModal/styles.tsx new file mode 100644 index 000000000000..cd68b271ebb4 --- /dev/null +++ b/superset-frontend/src/components/ReportModal/styles.tsx @@ -0,0 +1,113 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { styled, css, SupersetTheme } from '@superset-ui/core'; +import Modal from 'src/components/Modal'; +import Button from 'src/components/Button'; +import { Radio } from 'src/components/Radio'; +import { CronPicker } from 'src/components/CronPicker'; + +export const StyledModal = styled(Modal)` + .ant-modal-body { + padding: 0; + } + + h4 { + font-weight: 600; + } +`; + +export const StyledTopSection = styled.div` + padding: ${({ theme }) => + `${theme.gridUnit * 3}px ${theme.gridUnit * 4}px ${theme.gridUnit * 2}px`}; + label { + font-size: ${({ theme }) => theme.typography.sizes.s - 1}px; + color: ${({ theme }) => theme.colors.grayscale.light1}; + } +`; + +export const StyledBottomSection = styled.div` + border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + padding: ${({ theme }) => + `${theme.gridUnit * 4}px ${theme.gridUnit * 4}px ${theme.gridUnit * 6}px`}; + .ant-select { + width: 100%; + } + .control-label { + font-size: ${({ theme }) => theme.typography.sizes.s - 1}px; + color: ${({ theme }) => theme.colors.grayscale.light1}; + } +`; + +export const StyledIconWrapper = styled.span` + span { + margin-right: ${({ theme }) => theme.gridUnit * 2}px; + vertical-align: middle; + } + .text { + vertical-align: middle; + } +`; + +export const StyledScheduleTitle = styled.div` + margin-bottom: ${({ theme }) => theme.gridUnit * 7}px; + + h4 { + margin-bottom: ${({ theme }) => theme.gridUnit * 3}px; + } +`; + +export const StyledCronPicker = styled(CronPicker)` + margin-bottom: ${({ theme }) => theme.gridUnit * 3}px; +`; + +export const StyledCronError = styled.p` + color: ${({ theme }) => theme.colors.error.base}; +`; + +export const noBottomMargin = css` + margin-bottom: 0; +`; + +export const StyledFooterButton = styled(Button)` + width: ${({ theme }) => theme.gridUnit * 40}px; +`; + +export const TimezoneHeaderStyle = (theme: SupersetTheme) => css` + margin: ${theme.gridUnit * 3}px 0 ${theme.gridUnit * 2}px; +`; + +export const SectionHeaderStyle = (theme: SupersetTheme) => css` + margin: ${theme.gridUnit * 3}px 0; + font-weight: ${theme.typography.weights.bold}; +`; + +export const StyledMessageContentTitle = styled.div` + margin: ${({ theme }) => theme.gridUnit * 8}px 0 + ${({ theme }) => theme.gridUnit * 4}px; +`; + +export const StyledRadio = styled(Radio)` + display: block; + line-height: ${({ theme }) => theme.gridUnit * 8}px; +`; + +export const StyledRadioGroup = styled(Radio.Group)` + margin-left: ${({ theme }) => theme.gridUnit * 0.5}px; +`; diff --git a/superset-frontend/src/components/SearchInput/SearchInput.test.jsx b/superset-frontend/src/components/SearchInput/SearchInput.test.jsx index a5d324d1005b..8de2794d4e09 100644 --- a/superset-frontend/src/components/SearchInput/SearchInput.test.jsx +++ b/superset-frontend/src/components/SearchInput/SearchInput.test.jsx @@ -17,7 +17,8 @@ * under the License. */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import SearchInput from 'src/components/SearchInput'; @@ -31,7 +32,11 @@ describe('SearchInput', () => { const factory = overrideProps => { const props = { ...defaultProps, ...(overrideProps || {}) }; - return shallow(<SearchInput {...props} />); + return mount( + <ThemeProvider theme={supersetTheme}> + <SearchInput {...props} /> + </ThemeProvider>, + ); }; let wrapper; @@ -53,6 +58,7 @@ describe('SearchInput', () => { const typeSearchInput = value => { wrapper .find('[data-test="search-input"]') + .first() .props() .onChange({ currentTarget: { value } }); }; @@ -62,6 +68,7 @@ describe('SearchInput', () => { wrapper .find('[data-test="search-input"]') + .first() .props() .onKeyDown({ key: 'Enter' }); @@ -72,14 +79,14 @@ describe('SearchInput', () => { it('submits on search icon click', () => { typeSearchInput('bar'); - wrapper.find('[data-test="search-submit"]').props().onClick(); + wrapper.find('[data-test="search-submit"]').first().props().onClick(); expect(defaultProps.onSubmit).toHaveBeenCalled(); }); it('clears on clear icon click', () => { const wrapper2 = factory({ value: 'fizz' }); - wrapper2.find('[data-test="search-clear"]').props().onClick(); + wrapper2.find('[data-test="search-clear"]').first().props().onClick(); expect(defaultProps.onClear).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/components/SearchInput/index.tsx b/superset-frontend/src/components/SearchInput/index.tsx index ba7eb2d95fc3..7ca7026f333c 100644 --- a/superset-frontend/src/components/SearchInput/index.tsx +++ b/superset-frontend/src/components/SearchInput/index.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { styled } from '@superset-ui/core'; +import { styled, useTheme } from '@superset-ui/core'; import React from 'react'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface SearchInputProps { onSubmit: () => void; @@ -53,13 +53,13 @@ const commonStyles = ` display: block; cursor: pointer; `; -const SearchIcon = styled(Icon)` +const SearchIcon = styled(Icons.Search)` ${commonStyles}; top: 4px; left: 2px; `; -const ClearIcon = styled(Icon)` +const ClearIcon = styled(Icons.CancelX)` ${commonStyles}; right: 0px; top: 4px; @@ -73,12 +73,13 @@ export default function SearchInput({ name, value, }: SearchInputProps) { + const theme = useTheme(); return ( <SearchInputWrapper> <SearchIcon + iconColor={theme.colors.grayscale.base} data-test="search-submit" role="button" - name="search" onClick={() => onSubmit()} /> <StyledInput @@ -98,7 +99,7 @@ export default function SearchInput({ <ClearIcon data-test="search-clear" role="button" - name="cancel-x" + iconColor={theme.colors.grayscale.base} onClick={() => onClear()} /> )} diff --git a/superset-frontend/src/components/Select/DeprecatedSelect.stories.tsx b/superset-frontend/src/components/Select/DeprecatedSelect.stories.tsx new file mode 100644 index 000000000000..191ee81e00e0 --- /dev/null +++ b/superset-frontend/src/components/Select/DeprecatedSelect.stories.tsx @@ -0,0 +1,143 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { useArgs } from '@storybook/client-api'; +import { OptionTypeBase } from 'react-select'; +import Select from '.'; + +const OPTIONS = [ + { label: 'Blue', value: 'blue' }, + { label: 'Red', value: 'red' }, + { label: 'Orange', value: 'orange' }, +]; + +export default { + title: 'DeprecatedSelect', + argTypes: { + options: { + type: 'select', + options: OPTIONS, + }, + multi: { + type: 'boolean', + }, + value: { + type: 'string', + }, + clearable: { + type: 'boolean', + }, + placeholder: { + type: 'string', + }, + }, +}; + +export const SelectGallery = ({ value }: { value: OptionTypeBase }) => ( + <> + <h4>With default value</h4> + <Select + value={OPTIONS[0]} + ignoreAccents={false} + name="select-datasource" + onChange={() => {}} + options={OPTIONS} + placeholder="choose one" + width={600} + /> + <hr /> + <h4>With no value</h4> + <Select + ignoreAccents={false} + name="select-datasource" + onChange={() => {}} + options={OPTIONS} + placeholder="choose one" + width={600} + value={value} + /> + <hr /> + <h4>Multi select</h4> + <Select + ignoreAccents={false} + name="select-datasource" + onChange={() => {}} + options={OPTIONS} + placeholder="choose one or more values" + width={600} + value={[OPTIONS[0]]} + multi + /> + </> +); + +SelectGallery.args = { + value: '', + options: OPTIONS, +}; + +SelectGallery.story = { + parameters: { + knobs: { + disabled: true, + }, + }, +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const InteractiveSelect = (args: any) => { + const [{ value, multi, clearable, placeholder }, updateArgs] = useArgs(); + const onSelect = (selection: {}) => { + const { value }: { value?: any } = selection || {}; + if (multi) { + updateArgs({ value: selection }); + return; + } + updateArgs({ value }); + }; + + return ( + <Select + clearable={clearable} + onChange={onSelect} + name="interactive-select" + options={OPTIONS} + placeholder={placeholder} + with={600} + value={value} + multi={multi} + /> + ); +}; + +InteractiveSelect.args = { + value: '', + multi: false, + options: OPTIONS, + clearable: false, + placeholder: "I'm interactive", +}; + +InteractiveSelect.story = { + parameters: { + knobs: { + disabled: true, + }, + }, +}; diff --git a/superset-frontend/src/components/Select/DeprecatedSelect.tsx b/superset-frontend/src/components/Select/DeprecatedSelect.tsx new file mode 100644 index 000000000000..20afd0ee2d7c --- /dev/null +++ b/superset-frontend/src/components/Select/DeprecatedSelect.tsx @@ -0,0 +1,322 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { SyntheticEvent, MutableRefObject, ComponentType } from 'react'; +import { merge } from 'lodash'; +import BasicSelect, { + OptionTypeBase, + MultiValueProps, + FormatOptionLabelMeta, + ValueType, + SelectComponentsConfig, + components as defaultComponents, + createFilter, +} from 'react-select'; +import Async from 'react-select/async'; +import Creatable from 'react-select/creatable'; +import AsyncCreatable from 'react-select/async-creatable'; +import { withAsyncPaginate } from 'react-select-async-paginate'; + +import { SelectComponents } from 'react-select/src/components'; +import { + SortableContainer, + SortableElement, + SortableContainerProps, +} from 'react-sortable-hoc'; +import arrayMove from 'array-move'; +import { Props as SelectProps } from 'react-select/src/Select'; +import { useTheme } from '@superset-ui/core'; +import { + WindowedSelectComponentType, + WindowedSelectProps, + WindowedSelect, + WindowedAsyncSelect, + WindowedCreatableSelect, + WindowedAsyncCreatableSelect, +} from './WindowedSelect'; +import { + DEFAULT_CLASS_NAME, + DEFAULT_CLASS_NAME_PREFIX, + DEFAULT_STYLES, + DEFAULT_COMPONENTS, + VALUE_LABELED_STYLES, + PartialThemeConfig, + PartialStylesConfig, + SelectComponentsType, + InputProps, + defaultTheme, +} from './styles'; +import { findValue } from './utils'; + +type AnyReactSelect<OptionType extends OptionTypeBase> = + | BasicSelect<OptionType> + | Async<OptionType> + | Creatable<OptionType> + | AsyncCreatable<OptionType>; + +export type SupersetStyledSelectProps< + OptionType extends OptionTypeBase, + T extends WindowedSelectProps<OptionType> = WindowedSelectProps<OptionType> +> = T & { + // additional props for easier usage or backward compatibility + labelKey?: string; + valueKey?: string; + assistiveText?: string; + multi?: boolean; + clearable?: boolean; + sortable?: boolean; + ignoreAccents?: boolean; + creatable?: boolean; + selectRef?: + | React.RefCallback<AnyReactSelect<OptionType>> + | MutableRefObject<AnyReactSelect<OptionType>>; + getInputValue?: (selectBalue: ValueType<OptionType>) => string | undefined; + optionRenderer?: (option: OptionType) => React.ReactNode; + valueRenderer?: (option: OptionType) => React.ReactNode; + valueRenderedAsLabel?: boolean; + // callback for paste event + onPaste?: (e: SyntheticEvent) => void; + forceOverflow?: boolean; + // for simplier theme overrides + themeConfig?: PartialThemeConfig; + stylesConfig?: PartialStylesConfig; +}; + +function styled< + OptionType extends OptionTypeBase, + SelectComponentType extends + | WindowedSelectComponentType<OptionType> + | ComponentType< + SelectProps<OptionType> + > = WindowedSelectComponentType<OptionType> +>(SelectComponent: SelectComponentType) { + type SelectProps = SupersetStyledSelectProps<OptionType>; + type Components = SelectComponents<OptionType>; + + const SortableSelectComponent = SortableContainer(SelectComponent, { + withRef: true, + }); + + // default components for the given OptionType + const supersetDefaultComponents: SelectComponentsConfig<OptionType> = DEFAULT_COMPONENTS; + + const getSortableMultiValue = (MultiValue: Components['MultiValue']) => + SortableElement((props: MultiValueProps<OptionType>) => { + const onMouseDown = (e: SyntheticEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + const innerProps = { onMouseDown }; + return <MultiValue {...props} innerProps={innerProps} />; + }); + + /** + * Superset styled `Select` component. Apply Superset themed stylesheets and + * consolidate props API for backward compatibility with react-select v1. + */ + function StyledSelect(selectProps: SelectProps) { + let stateManager: AnyReactSelect<OptionType>; // reference to react-select StateManager + const { + // additional props for Superset Select + selectRef, + labelKey = 'label', + valueKey = 'value', + themeConfig, + stylesConfig = {}, + optionRenderer, + valueRenderer, + // whether value is rendered as `option-label` in input, + // useful for AdhocMetric and AdhocFilter + valueRenderedAsLabel: valueRenderedAsLabel_, + onPaste, + multi = false, // same as `isMulti`, used for backward compatibility + clearable, // same as `isClearable` + sortable = true, // whether to enable drag & drop sorting + forceOverflow, // whether the dropdown should be forcefully overflowing + + // react-select props + className = DEFAULT_CLASS_NAME, + classNamePrefix = DEFAULT_CLASS_NAME_PREFIX, + options, + value: value_, + components: components_, + isMulti: isMulti_, + isClearable: isClearable_, + minMenuHeight = 100, // apply different defaults + maxMenuHeight = 220, + filterOption, + ignoreAccents = false, // default is `true`, but it is slow + + getOptionValue = option => + typeof option === 'string' ? option : option[valueKey], + + getOptionLabel = option => + typeof option === 'string' + ? option + : option[labelKey] || option[valueKey], + + formatOptionLabel = ( + option: OptionType, + { context }: FormatOptionLabelMeta<OptionType>, + ) => { + if (context === 'value') { + return valueRenderer ? valueRenderer(option) : getOptionLabel(option); + } + return optionRenderer ? optionRenderer(option) : getOptionLabel(option); + }, + + ...restProps + } = selectProps; + + // `value` may be rendered values (strings), we want option objects + const value: OptionType[] = findValue(value_, options || [], valueKey); + + // Add backward compability to v1 API + const isMulti = isMulti_ === undefined ? multi : isMulti_; + const isClearable = isClearable_ === undefined ? clearable : isClearable_; + + // Sort is only applied when there are multiple selected values + const shouldAllowSort = + isMulti && sortable && Array.isArray(value) && value.length > 1; + + const MaybeSortableSelect = shouldAllowSort + ? SortableSelectComponent + : SelectComponent; + const components = { ...supersetDefaultComponents, ...components_ }; + + // Make multi-select sortable as per https://react-select.netlify.app/advanced + if (shouldAllowSort) { + components.MultiValue = getSortableMultiValue( + components.MultiValue || defaultComponents.MultiValue, + ); + + const sortableContainerProps: Partial<SortableContainerProps> = { + getHelperDimensions: ({ node }) => node.getBoundingClientRect(), + axis: 'xy', + onSortEnd: ({ oldIndex, newIndex }) => { + const newValue = arrayMove(value, oldIndex, newIndex); + if (restProps.onChange) { + restProps.onChange(newValue, { action: 'set-value' }); + } + }, + distance: 4, + }; + Object.assign(restProps, sortableContainerProps); + } + + // When values are rendered as labels, adjust valueContainer padding + const valueRenderedAsLabel = + valueRenderedAsLabel_ === undefined ? isMulti : valueRenderedAsLabel_; + if (valueRenderedAsLabel && !stylesConfig.valueContainer) { + Object.assign(stylesConfig, VALUE_LABELED_STYLES); + } + + // Handle onPaste event + if (onPaste) { + const Input = + (components.Input as SelectComponentsType['Input']) || + (defaultComponents.Input as SelectComponentsType['Input']); + components.Input = (props: InputProps) => ( + <Input {...props} onPaste={onPaste} /> + ); + } + // for CreaTable + if (SelectComponent === WindowedCreatableSelect) { + restProps.getNewOptionData = (inputValue: string, label: string) => ({ + label: label || inputValue, + [valueKey]: inputValue, + isNew: true, + }); + } + + // handle forcing dropdown overflow + // use only when setting overflow:visible isn't possible on the container element + if (forceOverflow) { + Object.assign(restProps, { + closeMenuOnScroll: (e: Event) => { + const target = e.target as HTMLElement; + return target && !target.classList?.contains('Select__menu-list'); + }, + menuPosition: 'fixed', + }); + } + + // Make sure always return StateManager for the refs. + // To get the real `Select` component, keep tap into `obj.select`: + // - for normal <Select /> component: StateManager -> Select, + // - for <Creatable />: StateManager -> Creatable -> Select + const setRef = (instance: any) => { + stateManager = + shouldAllowSort && instance && 'refs' in instance + ? instance.refs.wrappedInstance // obtain StateManger from SortableContainer + : instance; + if (typeof selectRef === 'function') { + selectRef(stateManager); + } else if (selectRef && 'current' in selectRef) { + selectRef.current = stateManager; + } + }; + + const theme = useTheme(); + + return ( + <MaybeSortableSelect + ref={setRef} + className={className} + classNamePrefix={classNamePrefix} + isMulti={isMulti} + isClearable={isClearable} + options={options} + value={value} + minMenuHeight={minMenuHeight} + maxMenuHeight={maxMenuHeight} + filterOption={ + // filterOption may be NULL + filterOption !== undefined + ? filterOption + : createFilter({ ignoreAccents }) + } + styles={{ ...DEFAULT_STYLES, ...stylesConfig } as SelectProps['styles']} + // merge default theme from `react-select`, default theme for Superset, + // and the theme from props. + theme={reactSelectTheme => + merge(reactSelectTheme, defaultTheme(theme), themeConfig) + } + formatOptionLabel={formatOptionLabel} + getOptionLabel={getOptionLabel} + getOptionValue={getOptionValue} + components={components} + {...restProps} + /> + ); + } + + // React.memo makes sure the component does no rerender given the same props + return React.memo(StyledSelect); +} + +export const Select = styled(WindowedSelect); +export const AsyncSelect = styled(WindowedAsyncSelect); +export const CreatableSelect = styled(WindowedCreatableSelect); +export const AsyncCreatableSelect = styled(WindowedAsyncCreatableSelect); +export const PaginatedSelect = withAsyncPaginate( + styled<OptionTypeBase, ComponentType<SelectProps<OptionTypeBase>>>( + BasicSelect, + ), +); +export default Select; diff --git a/superset-frontend/src/components/Select/NativeSelect.tsx b/superset-frontend/src/components/Select/NativeSelect.tsx index 604496701eb3..2f690b1ba6d8 100644 --- a/superset-frontend/src/components/Select/NativeSelect.tsx +++ b/superset-frontend/src/components/Select/NativeSelect.tsx @@ -20,6 +20,11 @@ import React from 'react'; import { styled } from '@superset-ui/core'; import Select, { SelectProps } from 'antd/lib/select'; +export { + OptionType as NativeSelectOptionType, + SelectProps as NativeSelectProps, +} from 'antd/lib/select'; + const StyledNativeSelect = styled((props: SelectProps<any>) => ( <Select getPopupContainer={(trigger: any) => trigger.parentNode} {...props} /> ))` diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx index 125f66778649..e6dd867d972c 100644 --- a/superset-frontend/src/components/Select/Select.stories.tsx +++ b/superset-frontend/src/components/Select/Select.stories.tsx @@ -16,128 +16,444 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { useArgs } from '@storybook/client-api'; -import { OptionTypeBase } from 'react-select'; -import Select from '.'; - -const OPTIONS = [ - { label: 'Blue', value: 'blue' }, - { label: 'Red', value: 'red' }, - { label: 'Orange', value: 'orange' }, -]; +import React, { ReactNode, useState, useCallback } from 'react'; +import ControlHeader from 'src/explore/components/ControlHeader'; +import Select, { SelectProps, OptionsTypePage } from './Select'; export default { title: 'Select', - argTypes: { - options: { - type: 'select', - options: OPTIONS, + component: Select, +}; + +const DEFAULT_WIDTH = 200; + +const options = [ + { + label: 'Such an incredibly awesome long long label', + value: 'Such an incredibly awesome long long label', + }, + { + label: 'Another incredibly awesome long long label', + value: 'Another incredibly awesome long long label', + }, + { label: 'Just a label', value: 'Just a label' }, + { label: 'A', value: 'A' }, + { label: 'B', value: 'B' }, + { label: 'C', value: 'C' }, + { label: 'D', value: 'D' }, + { label: 'E', value: 'E' }, + { label: 'F', value: 'F' }, + { label: 'G', value: 'G' }, + { label: 'H', value: 'H' }, + { label: 'I', value: 'I' }, +]; + +const selectPositions = [ + { + id: 'topLeft', + style: { top: '0', left: '0' }, + }, + { + id: 'topRight', + style: { top: '0', right: '0' }, + }, + { + id: 'bottomLeft', + style: { bottom: '0', left: '0' }, + }, + { + id: 'bottomRight', + style: { bottom: '0', right: '0' }, + }, +]; + +const ARG_TYPES = { + options: { + defaultValue: options, + table: { + disable: true, + }, + }, + ariaLabel: { + table: { + disable: true, + }, + }, + name: { + table: { + disable: true, }, - multi: { - type: 'boolean', + }, + notFoundContent: { + table: { + disable: true, + }, + }, + mode: { + defaultValue: 'single', + control: { + type: 'inline-radio', + options: ['single', 'multiple'], }, - value: { - type: 'string', + }, +}; + +const mountHeader = (type: String) => { + let header; + if (type === 'text') { + header = 'Text header'; + } else if (type === 'control') { + header = ( + <ControlHeader + label="Control header" + warning="Example of warning messsage" + /> + ); + } + return header; +}; + +export const InteractiveSelect = (args: SelectProps & { header: string }) => ( + <div + style={{ + width: DEFAULT_WIDTH, + }} + > + <Select {...args} header={mountHeader(args.header)} /> + </div> +); + +InteractiveSelect.args = { + autoFocus: false, + allowNewOptions: false, + allowClear: false, + showSearch: false, + disabled: false, + invertSelection: false, + placeholder: 'Select ...', +}; + +InteractiveSelect.argTypes = { + ...ARG_TYPES, + header: { + defaultValue: 'none', + control: { type: 'inline-radio', options: ['none', 'text', 'control'] }, + }, + pageSize: { + table: { + disable: true, }, - clearable: { - type: 'boolean', + }, + fetchOnlyOnSearch: { + table: { + disable: true, }, - placeholder: { - type: 'string', + }, +}; + +InteractiveSelect.story = { + parameters: { + knobs: { + disable: true, }, }, }; -export const SelectGallery = ({ value }: { value: OptionTypeBase }) => ( +export const AtEveryCorner = () => ( <> - <h4>With default value</h4> - <Select - value={OPTIONS[0]} - ignoreAccents={false} - name="select-datasource" - onChange={() => {}} - options={OPTIONS} - placeholder="choose one" - width={600} - /> - <hr /> - <h4>With no value</h4> - <Select - ignoreAccents={false} - name="select-datasource" - onChange={() => {}} - options={OPTIONS} - placeholder="choose one" - width={600} - value={value} - /> - <hr /> - <h4>Multi select</h4> - <Select - ignoreAccents={false} - name="select-datasource" - onChange={() => {}} - options={OPTIONS} - placeholder="choose one or more values" - width={600} - value={[OPTIONS[0]]} - multi - /> + {selectPositions.map(position => ( + <div + key={position.id} + style={{ + ...position.style, + margin: 30, + width: DEFAULT_WIDTH, + position: 'absolute', + }} + > + <Select ariaLabel={`gallery-${position.id}`} options={options} /> + </div> + ))} + <p style={{ position: 'absolute', top: '40%', left: '33%', width: 500 }}> + The objective of this panel is to show how the Select behaves when in + touch with the viewport extremities. In particular, how the drop-down is + displayed and if the tooltips of truncated items are correctly positioned. + </p> </> ); -SelectGallery.args = { - value: '', - options: OPTIONS, +AtEveryCorner.story = { + parameters: { + actions: { + disable: true, + }, + controls: { + disable: true, + }, + knobs: { + disable: true, + }, + }, }; -SelectGallery.story = { +export const PageScroll = () => ( + <div style={{ height: 2000, overflowY: 'auto' }}> + <div + style={{ + width: DEFAULT_WIDTH, + position: 'absolute', + top: 30, + right: 30, + }} + > + <Select ariaLabel="page-scroll-select-1" options={options} /> + </div> + <div + style={{ + width: DEFAULT_WIDTH, + position: 'absolute', + bottom: 30, + right: 30, + }} + > + <Select ariaLabel="page-scroll-select-2" options={options} /> + </div> + <p + style={{ + position: 'absolute', + top: '40%', + left: 30, + width: 500, + }} + > + The objective of this panel is to show how the Select behaves when there's + a scroll on the page. In particular, how the drop-down is displayed. + </p> + </div> +); + +PageScroll.story = { parameters: { + actions: { + disable: true, + }, + controls: { + disable: true, + }, knobs: { - disabled: true, + disable: true, }, }, }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const InteractiveSelect = (args: any) => { - const [{ value, multi, clearable, placeholder }, updateArgs] = useArgs(); - const onSelect = (selection: {}) => { - const { value }: { value?: any } = selection || {}; - if (multi) { - updateArgs({ value: selection }); - return; +const USERS = [ + 'John', + 'Liam', + 'Olivia', + 'Emma', + 'Noah', + 'Ava', + 'Oliver', + 'Elijah', + 'Charlotte', + 'Diego', + 'Evan', + 'Michael', + 'Giovanni', + 'Luca', + 'Paolo', + 'Francesca', + 'Chiara', + 'Sara', + 'Valentina', + 'Jessica', + 'Angelica', + 'Mario', + 'Marco', + 'Andrea', + 'Luigi', + 'Quarto', + 'Quinto', + 'Sesto', + 'Franco', + 'Sandro', + 'Alehandro', + 'Johnny', + 'Nikole', + 'Igor', + 'Sipatha', + 'Thami', + 'Munei', + 'Guilherme', + 'Umair', + 'Ashfaq', + 'Amna', + 'Irfan', + 'George', + 'Naseer', + 'Mohammad', + 'Rick', + 'Saliya', + 'Claire', + 'Benedetta', + 'Ilenia', +]; + +export const AsyncSelect = ({ + withError, + withInitialValue, + responseTime, + ...rest +}: SelectProps & { + withError: boolean; + withInitialValue: boolean; + responseTime: number; +}) => { + const [requests, setRequests] = useState<ReactNode[]>([]); + + const getResults = (username?: string) => { + let results: { label: string; value: string }[] = []; + + if (!username) { + results = USERS.map(u => ({ + label: u, + value: u, + })); + } else { + const foundUsers = USERS.filter(u => u.toLowerCase().includes(username)); + if (foundUsers) { + results = foundUsers.map(u => ({ label: u, value: u })); + } else { + results = []; + } } - updateArgs({ value }); + return results; + }; + + const setRequestLog = (results: number, total: number, username?: string) => { + const request = ( + <> + Emulating network request with search <b>{username || 'empty'}</b> ...{' '} + <b> + {results}/{total} + </b>{' '} + results + </> + ); + + setRequests(requests => [request, ...requests]); }; + const fetchUserListPage = useCallback( + ( + search: string, + page: number, + pageSize: number, + ): Promise<OptionsTypePage> => { + const username = search.trim().toLowerCase(); + return new Promise(resolve => { + let results = getResults(username); + const totalCount = results.length; + const start = page * pageSize; + const deleteCount = + start + pageSize < totalCount ? pageSize : totalCount - start; + results = results.splice(start, deleteCount); + setRequestLog(start + results.length, totalCount, username); + setTimeout(() => { + resolve({ data: results, totalCount }); + }, responseTime * 1000); + }); + }, + [responseTime], + ); + + const fetchUserListError = async (): Promise<OptionsTypePage> => + new Promise((_, reject) => { + reject(new Error('Error while fetching the names from the server')); + }); + return ( - <Select - clearable={clearable} - onChange={onSelect} - name="interactive-select" - options={OPTIONS} - placeholder={placeholder} - with={600} - value={value} - multi={multi} - /> + <> + <div + style={{ + width: DEFAULT_WIDTH, + }} + > + <Select + {...rest} + options={withError ? fetchUserListError : fetchUserListPage} + value={ + withInitialValue + ? { label: 'Valentina', value: 'Valentina' } + : undefined + } + /> + </div> + <div + style={{ + position: 'absolute', + top: 32, + left: DEFAULT_WIDTH + 100, + height: 400, + width: 600, + overflowY: 'auto', + border: '1px solid #d9d9d9', + padding: 20, + }} + > + {requests.map((request, index) => ( + <p key={`request-${index}`}>{request}</p> + ))} + </div> + </> ); }; -InteractiveSelect.args = { - value: '', - multi: false, - options: OPTIONS, - clearable: false, - placeholder: "I'm interactive", +AsyncSelect.args = { + allowNewOptions: false, + fetchOnlyOnSearch: false, + pageSize: 10, + withError: false, + withInitialValue: false, }; -InteractiveSelect.story = { +AsyncSelect.argTypes = { + ...ARG_TYPES, + header: { + table: { + disable: true, + }, + }, + invertSelection: { + table: { + disable: true, + }, + }, + pageSize: { + defaultValue: 10, + control: { + type: 'range', + min: 10, + max: 50, + step: 10, + }, + }, + responseTime: { + defaultValue: 0.5, + name: 'responseTime (seconds)', + control: { + type: 'range', + min: 0.5, + max: 5, + step: 0.5, + }, + }, +}; + +AsyncSelect.story = { parameters: { knobs: { - disabled: true, + disable: true, }, }, }; diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx new file mode 100644 index 000000000000..dc39899af792 --- /dev/null +++ b/superset-frontend/src/components/Select/Select.test.tsx @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import { Select } from 'src/components'; + +test('renders with default props', () => { + const ariaLabel = 'test'; + render( + <Select + ariaLabel={ariaLabel} + options={[ + { label: 'A', value: 0 }, + { label: 'B', value: 1 }, + ]} + />, + ); + + expect(screen.getByRole('combobox', { name: ariaLabel })).toBeInTheDocument(); +}); + +/* +Tests for the sync version of the select: +- Opens the select without any data +- Makes a selection in single mode +- Makes multiple selections in multiple mode +- Changes the selected item in single mode +- Deselects an item in multiple mode +- Adds a header to the select +- Adds a new option if none is available and allowNewValue is true +- Does not add a new option if the option already exists +- Does not add a new option if allowNewValue is false +- Inverts the selection +- Sets a initial value in single mode +- Sets a initial value in multiple mode +- Searches for an item +- Displays the selected items first +- Searches for label or value +- Clear all the values + +Tests for the async version of the select: +- Opens the select without any data +- Makes a selection in single mode +- Makes multiple selections in multiple mode +- Changes the selected item in single mode +- Deselects an item in multiple mode +- Adds a new option if none is available and allowNewValue is true +- Does not add a new option if the option already exists +- Does not add a new option if allowNewValue is false +- Sets a initial value in single mode +- Sets a initial value in multiple mode +- Searches for an item already loaded +- Searches for an item in a page not loaded +- Displays the loading indicator +- Fetches more data when scrolling and more data is available +- Doesn't fetch more data when no more data is available +- Requests the correct page and page size +- Fetches only after a search input is entered if fetchOnlyOnSearch is true +- Does not fetch data when rendering +- Fetches data when opening the select +- Displays an error message when an exception is thrown while fetching +- Does not fire a new request for the same search input +- Displays the selected items first +- Sets the page to zero when a new search is made +- Clear all the values +*/ diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx index 20afd0ee2d7c..655a6569afac 100644 --- a/superset-frontend/src/components/Select/Select.tsx +++ b/superset-frontend/src/components/Select/Select.tsx @@ -16,307 +16,517 @@ * specific language governing permissions and limitations * under the License. */ -import React, { SyntheticEvent, MutableRefObject, ComponentType } from 'react'; -import { merge } from 'lodash'; -import BasicSelect, { - OptionTypeBase, - MultiValueProps, - FormatOptionLabelMeta, - ValueType, - SelectComponentsConfig, - components as defaultComponents, - createFilter, -} from 'react-select'; -import Async from 'react-select/async'; -import Creatable from 'react-select/creatable'; -import AsyncCreatable from 'react-select/async-creatable'; -import { withAsyncPaginate } from 'react-select-async-paginate'; - -import { SelectComponents } from 'react-select/src/components'; -import { - SortableContainer, - SortableElement, - SortableContainerProps, -} from 'react-sortable-hoc'; -import arrayMove from 'array-move'; -import { Props as SelectProps } from 'react-select/src/Select'; -import { useTheme } from '@superset-ui/core'; -import { - WindowedSelectComponentType, - WindowedSelectProps, - WindowedSelect, - WindowedAsyncSelect, - WindowedCreatableSelect, - WindowedAsyncCreatableSelect, -} from './WindowedSelect'; -import { - DEFAULT_CLASS_NAME, - DEFAULT_CLASS_NAME_PREFIX, - DEFAULT_STYLES, - DEFAULT_COMPONENTS, - VALUE_LABELED_STYLES, - PartialThemeConfig, - PartialStylesConfig, - SelectComponentsType, - InputProps, - defaultTheme, -} from './styles'; -import { findValue } from './utils'; - -type AnyReactSelect<OptionType extends OptionTypeBase> = - | BasicSelect<OptionType> - | Async<OptionType> - | Creatable<OptionType> - | AsyncCreatable<OptionType>; - -export type SupersetStyledSelectProps< - OptionType extends OptionTypeBase, - T extends WindowedSelectProps<OptionType> = WindowedSelectProps<OptionType> -> = T & { - // additional props for easier usage or backward compatibility - labelKey?: string; - valueKey?: string; - assistiveText?: string; - multi?: boolean; - clearable?: boolean; - sortable?: boolean; - ignoreAccents?: boolean; - creatable?: boolean; - selectRef?: - | React.RefCallback<AnyReactSelect<OptionType>> - | MutableRefObject<AnyReactSelect<OptionType>>; - getInputValue?: (selectBalue: ValueType<OptionType>) => string | undefined; - optionRenderer?: (option: OptionType) => React.ReactNode; - valueRenderer?: (option: OptionType) => React.ReactNode; - valueRenderedAsLabel?: boolean; - // callback for paste event - onPaste?: (e: SyntheticEvent) => void; - forceOverflow?: boolean; - // for simplier theme overrides - themeConfig?: PartialThemeConfig; - stylesConfig?: PartialStylesConfig; +import React, { + ReactElement, + ReactNode, + RefObject, + UIEvent, + useEffect, + useMemo, + useState, + useRef, + useCallback, +} from 'react'; +import { styled, t } from '@superset-ui/core'; +import AntdSelect, { + SelectProps as AntdSelectProps, + SelectValue as AntdSelectValue, + LabeledValue as AntdLabeledValue, +} from 'antd/lib/select'; +import { DownOutlined, SearchOutlined } from '@ant-design/icons'; +import debounce from 'lodash/debounce'; +import { isEqual } from 'lodash'; +import { Spin } from 'antd'; +import Icons from 'src/components/Icons'; +import { getClientErrorObject } from 'src/utils/getClientErrorObject'; +import { hasOption } from './utils'; + +type AntdSelectAllProps = AntdSelectProps<AntdSelectValue>; + +type PickedSelectProps = Pick< + AntdSelectAllProps, + | 'allowClear' + | 'autoFocus' + | 'value' + | 'disabled' + | 'filterOption' + | 'notFoundContent' + | 'onChange' + | 'placeholder' + | 'showSearch' + | 'value' +>; + +export type OptionsType = Exclude<AntdSelectAllProps['options'], undefined>; + +export type OptionsTypePage = { + data: OptionsType; + totalCount: number; }; -function styled< - OptionType extends OptionTypeBase, - SelectComponentType extends - | WindowedSelectComponentType<OptionType> - | ComponentType< - SelectProps<OptionType> - > = WindowedSelectComponentType<OptionType> ->(SelectComponent: SelectComponentType) { - type SelectProps = SupersetStyledSelectProps<OptionType>; - type Components = SelectComponents<OptionType>; - - const SortableSelectComponent = SortableContainer(SelectComponent, { - withRef: true, - }); - - // default components for the given OptionType - const supersetDefaultComponents: SelectComponentsConfig<OptionType> = DEFAULT_COMPONENTS; - - const getSortableMultiValue = (MultiValue: Components['MultiValue']) => - SortableElement((props: MultiValueProps<OptionType>) => { - const onMouseDown = (e: SyntheticEvent) => { - e.preventDefault(); - e.stopPropagation(); - }; - const innerProps = { onMouseDown }; - return <MultiValue {...props} innerProps={innerProps} />; - }); +export type OptionsPagePromise = ( + search: string, + page: number, + pageSize: number, +) => Promise<OptionsTypePage>; + +export interface SelectProps extends PickedSelectProps { + allowNewOptions?: boolean; + ariaLabel: string; + header?: ReactNode; + mode?: 'single' | 'multiple'; + name?: string; // discourage usage + options: OptionsType | OptionsPagePromise; + pageSize?: number; + invertSelection?: boolean; + fetchOnlyOnSearch?: boolean; +} + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const StyledSelect = styled(AntdSelect)` + ${({ theme }) => ` + width: 100%; + + && .ant-select-selector { + border-radius: ${theme.gridUnit}px; + } + + // Open the dropdown when clicking on the suffix + // This is fixed in version 4.16 + .ant-select-arrow .anticon:not(.ant-select-suffix) { + pointer-events: none; + } + `} +`; + +const StyledStopOutlined = styled(Icons.StopOutlined)` + vertical-align: 0; +`; + +const StyledCheckOutlined = styled(Icons.CheckOutlined)` + vertical-align: 0; +`; + +const StyledError = styled.div` + ${({ theme }) => ` + display: flex; + justify-content: center; + align-items: flex-start; + width: 100%; + padding: ${theme.gridUnit * 2}px; + color: ${theme.colors.error.base}; + + & svg { + margin-right: ${theme.gridUnit * 2}px; + } + `} +`; - /** - * Superset styled `Select` component. Apply Superset themed stylesheets and - * consolidate props API for backward compatibility with react-select v1. - */ - function StyledSelect(selectProps: SelectProps) { - let stateManager: AnyReactSelect<OptionType>; // reference to react-select StateManager - const { - // additional props for Superset Select - selectRef, - labelKey = 'label', - valueKey = 'value', - themeConfig, - stylesConfig = {}, - optionRenderer, - valueRenderer, - // whether value is rendered as `option-label` in input, - // useful for AdhocMetric and AdhocFilter - valueRenderedAsLabel: valueRenderedAsLabel_, - onPaste, - multi = false, // same as `isMulti`, used for backward compatibility - clearable, // same as `isClearable` - sortable = true, // whether to enable drag & drop sorting - forceOverflow, // whether the dropdown should be forcefully overflowing - - // react-select props - className = DEFAULT_CLASS_NAME, - classNamePrefix = DEFAULT_CLASS_NAME_PREFIX, - options, - value: value_, - components: components_, - isMulti: isMulti_, - isClearable: isClearable_, - minMenuHeight = 100, // apply different defaults - maxMenuHeight = 220, - filterOption, - ignoreAccents = false, // default is `true`, but it is slow - - getOptionValue = option => - typeof option === 'string' ? option : option[valueKey], - - getOptionLabel = option => - typeof option === 'string' - ? option - : option[labelKey] || option[valueKey], - - formatOptionLabel = ( - option: OptionType, - { context }: FormatOptionLabelMeta<OptionType>, - ) => { - if (context === 'value') { - return valueRenderer ? valueRenderer(option) : getOptionLabel(option); +const StyledErrorMessage = styled.div` + overflow: hidden; + text-overflow: ellipsis; +`; + +const StyledSpin = styled(Spin)` + margin-top: ${({ theme }) => -theme.gridUnit}px; +`; + +const MAX_TAG_COUNT = 4; +const TOKEN_SEPARATORS = [',', '\n', '\t', ';']; +const DEBOUNCE_TIMEOUT = 500; +const DEFAULT_PAGE_SIZE = 100; +const EMPTY_OPTIONS: OptionsType = []; + +const Error = ({ error }: { error: string }) => ( + <StyledError> + <Icons.ErrorSolid /> <StyledErrorMessage>{error}</StyledErrorMessage> + </StyledError> +); + +const Select = ({ + allowNewOptions = false, + ariaLabel, + fetchOnlyOnSearch, + filterOption = true, + header = null, + invertSelection = false, + mode = 'single', + name, + options, + pageSize = DEFAULT_PAGE_SIZE, + placeholder = t('Select ...'), + showSearch, + value, + ...props +}: SelectProps) => { + const isAsync = typeof options === 'function'; + const isSingleMode = mode === 'single'; + const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch; + const initialOptions = + options && Array.isArray(options) ? options : EMPTY_OPTIONS; + const [selectOptions, setSelectOptions] = useState<OptionsType>( + initialOptions, + ); + const [selectValue, setSelectValue] = useState(value); + const [searchedValue, setSearchedValue] = useState(''); + const [isLoading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); + const [page, setPage] = useState(0); + const [totalCount, setTotalCount] = useState(0); + const [loadingEnabled, setLoadingEnabled] = useState(false); + const fetchedQueries = useRef(new Map<string, number>()); + const mappedMode = isSingleMode + ? undefined + : allowNewOptions + ? 'tags' + : 'multiple'; + + useEffect(() => { + fetchedQueries.current.clear(); + setSelectOptions( + options && Array.isArray(options) ? options : EMPTY_OPTIONS, + ); + }, [options]); + + useEffect(() => { + if (isAsync && value) { + const array: AntdLabeledValue[] = Array.isArray(value) + ? (value as AntdLabeledValue[]) + : [value as AntdLabeledValue]; + const options: AntdLabeledValue[] = []; + array.forEach(element => { + const found = selectOptions.find( + option => option.value === element.value, + ); + if (!found) { + options.push(element); } - return optionRenderer ? optionRenderer(option) : getOptionLabel(option); - }, + }); + if (options.length > 0) { + setSelectOptions([...selectOptions, ...options]); + } + } + }, [isAsync, selectOptions, value]); - ...restProps - } = selectProps; + useEffect(() => { + setSelectValue(value); + }, [value]); - // `value` may be rendered values (strings), we want option objects - const value: OptionType[] = findValue(value_, options || [], valueKey); + const handleTopOptions = useCallback( + (selectedValue: AntdSelectValue | undefined) => { + // bringing selected options to the top of the list + if (selectedValue !== undefined && selectedValue !== null) { + const topOptions: OptionsType = []; + const otherOptions: OptionsType = []; - // Add backward compability to v1 API - const isMulti = isMulti_ === undefined ? multi : isMulti_; - const isClearable = isClearable_ === undefined ? clearable : isClearable_; + selectOptions.forEach(opt => { + let found = false; + if (Array.isArray(selectedValue)) { + if (isAsync) { + found = + (selectedValue as AntdLabeledValue[]).find( + element => element.value === opt.value, + ) !== undefined; + } else { + found = selectedValue.includes(opt.value); + } + } else { + found = isAsync + ? (selectedValue as AntdLabeledValue).value === opt.value + : selectedValue === opt.value; + } - // Sort is only applied when there are multiple selected values - const shouldAllowSort = - isMulti && sortable && Array.isArray(value) && value.length > 1; + if (found) { + topOptions.push(opt); + } else { + otherOptions.push(opt); + } + }); - const MaybeSortableSelect = shouldAllowSort - ? SortableSelectComponent - : SelectComponent; - const components = { ...supersetDefaultComponents, ...components_ }; + // fallback for custom options in tags mode as they + // do not appear in the selectOptions state + if (!isSingleMode && Array.isArray(selectedValue)) { + selectedValue.forEach((val: string | number | AntdLabeledValue) => { + if ( + !topOptions.find( + tOpt => + tOpt.value === + (isAsync ? (val as AntdLabeledValue)?.value : val), + ) + ) { + if (isAsync) { + const labelValue = val as AntdLabeledValue; + topOptions.push({ + label: labelValue.label, + value: labelValue.value, + }); + } else { + const value = val as string | number; + topOptions.push({ label: String(value), value }); + } + } + }); + } - // Make multi-select sortable as per https://react-select.netlify.app/advanced - if (shouldAllowSort) { - components.MultiValue = getSortableMultiValue( - components.MultiValue || defaultComponents.MultiValue, - ); + const sortedOptions = [...topOptions, ...otherOptions]; + if (!isEqual(sortedOptions, selectOptions)) { + setSelectOptions(sortedOptions); + } + } + }, + [isAsync, isSingleMode, selectOptions], + ); + + const handleOnSelect = ( + selectedValue: string | number | AntdLabeledValue, + ) => { + if (isSingleMode) { + setSelectValue(selectedValue); + } else { + const currentSelected = selectValue + ? Array.isArray(selectValue) + ? selectValue + : [selectValue] + : []; + if ( + typeof selectedValue === 'number' || + typeof selectedValue === 'string' + ) { + setSelectValue([ + ...(currentSelected as (string | number)[]), + selectedValue as string | number, + ]); + } else { + setSelectValue([ + ...(currentSelected as AntdLabeledValue[]), + selectedValue as AntdLabeledValue, + ]); + } + } + setSearchedValue(''); + }; + + const handleOnDeselect = (value: string | number | AntdLabeledValue) => { + if (Array.isArray(selectValue)) { + if (typeof value === 'number' || typeof value === 'string') { + const array = selectValue as (string | number)[]; + setSelectValue(array.filter(element => element !== value)); + } else { + const array = selectValue as AntdLabeledValue[]; + setSelectValue(array.filter(element => element.value !== value.value)); + } + } + setSearchedValue(''); + }; + + const onError = (response: Response) => + getClientErrorObject(response).then(e => { + const { error } = e; + setError(error); + }); + + const handleData = (data: OptionsType) => { + if (data && Array.isArray(data) && data.length) { + // merges with existing and creates unique options + setSelectOptions(prevOptions => [ + ...prevOptions, + ...data.filter( + newOpt => + !prevOptions.find(prevOpt => prevOpt.value === newOpt.value), + ), + ]); + } + }; - const sortableContainerProps: Partial<SortableContainerProps> = { - getHelperDimensions: ({ node }) => node.getBoundingClientRect(), - axis: 'xy', - onSortEnd: ({ oldIndex, newIndex }) => { - const newValue = arrayMove(value, oldIndex, newIndex); - if (restProps.onChange) { - restProps.onChange(newValue, { action: 'set-value' }); + const handlePaginatedFetch = useMemo( + () => (value: string, page: number, pageSize: number) => { + const key = `${value};${page};${pageSize}`; + const cachedCount = fetchedQueries.current.get(key); + if (cachedCount) { + setTotalCount(cachedCount); + return; + } + setLoading(true); + const fetchOptions = options as OptionsPagePromise; + fetchOptions(value, page, pageSize) + .then(({ data, totalCount }: OptionsTypePage) => { + handleData(data); + fetchedQueries.current.set(key, totalCount); + setTotalCount(totalCount); + }) + .catch(onError) + .finally(() => setLoading(false)); + }, + [options], + ); + + const handleOnSearch = useMemo( + () => + debounce((search: string) => { + const searchValue = search.trim(); + // enables option creation + if (allowNewOptions && isSingleMode) { + const firstOption = + selectOptions.length > 0 && selectOptions[0].value; + // replaces the last search value entered with the new one + // only when the value wasn't part of the original options + if ( + searchValue && + firstOption === searchedValue && + !initialOptions.find(o => o.value === searchedValue) + ) { + selectOptions.shift(); + setSelectOptions(selectOptions); + } + if (searchValue && !hasOption(searchValue, selectOptions)) { + const newOption = { + label: searchValue, + value: searchValue, + }; + // adds a custom option + const newOptions = [...selectOptions, newOption]; + setSelectOptions(newOptions); + setSelectValue(searchValue); } - }, - distance: 4, - }; - Object.assign(restProps, sortableContainerProps); + } + setSearchedValue(searchValue); + }, DEBOUNCE_TIMEOUT), + [ + allowNewOptions, + initialOptions, + isSingleMode, + searchedValue, + selectOptions, + ], + ); + + const handlePagination = (e: UIEvent<HTMLElement>) => { + const vScroll = e.currentTarget; + const thresholdReached = + vScroll.scrollTop > (vScroll.scrollHeight - vScroll.offsetHeight) * 0.7; + const hasMoreData = page * pageSize + pageSize < totalCount; + + if (!isLoading && isAsync && hasMoreData && thresholdReached) { + const newPage = page + 1; + handlePaginatedFetch(searchedValue, newPage, pageSize); + setPage(newPage); } + }; - // When values are rendered as labels, adjust valueContainer padding - const valueRenderedAsLabel = - valueRenderedAsLabel_ === undefined ? isMulti : valueRenderedAsLabel_; - if (valueRenderedAsLabel && !stylesConfig.valueContainer) { - Object.assign(stylesConfig, VALUE_LABELED_STYLES); + const handleFilterOption = (search: string, option: AntdLabeledValue) => { + if (typeof filterOption === 'function') { + return filterOption(search, option); } - // Handle onPaste event - if (onPaste) { - const Input = - (components.Input as SelectComponentsType['Input']) || - (defaultComponents.Input as SelectComponentsType['Input']); - components.Input = (props: InputProps) => ( - <Input {...props} onPaste={onPaste} /> + if (filterOption) { + const searchValue = search.trim().toLowerCase(); + const { value, label } = option; + const valueText = String(value); + const labelText = String(label); + return ( + valueText.toLowerCase().includes(searchValue) || + labelText.toLowerCase().includes(searchValue) ); } - // for CreaTable - if (SelectComponent === WindowedCreatableSelect) { - restProps.getNewOptionData = (inputValue: string, label: string) => ({ - label: label || inputValue, - [valueKey]: inputValue, - isNew: true, - }); + + return false; + }; + + const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => { + setIsDropdownVisible(isDropdownVisible); + + if (isAsync && !loadingEnabled) { + setLoadingEnabled(true); } - // handle forcing dropdown overflow - // use only when setting overflow:visible isn't possible on the container element - if (forceOverflow) { - Object.assign(restProps, { - closeMenuOnScroll: (e: Event) => { - const target = e.target as HTMLElement; - return target && !target.classList?.contains('Select__menu-list'); - }, - menuPosition: 'fixed', - }); + // multiple or tags mode keep the dropdown visible while selecting options + // this waits for the dropdown to be closed before sorting the top options + if (!isSingleMode && !isDropdownVisible) { + handleTopOptions(selectValue); } + }; - // Make sure always return StateManager for the refs. - // To get the real `Select` component, keep tap into `obj.select`: - // - for normal <Select /> component: StateManager -> Select, - // - for <Creatable />: StateManager -> Creatable -> Select - const setRef = (instance: any) => { - stateManager = - shouldAllowSort && instance && 'refs' in instance - ? instance.refs.wrappedInstance // obtain StateManger from SortableContainer - : instance; - if (typeof selectRef === 'function') { - selectRef(stateManager); - } else if (selectRef && 'current' in selectRef) { - selectRef.current = stateManager; - } - }; - - const theme = useTheme(); - - return ( - <MaybeSortableSelect - ref={setRef} - className={className} - classNamePrefix={classNamePrefix} - isMulti={isMulti} - isClearable={isClearable} - options={options} - value={value} - minMenuHeight={minMenuHeight} - maxMenuHeight={maxMenuHeight} - filterOption={ - // filterOption may be NULL - filterOption !== undefined - ? filterOption - : createFilter({ ignoreAccents }) - } - styles={{ ...DEFAULT_STYLES, ...stylesConfig } as SelectProps['styles']} - // merge default theme from `react-select`, default theme for Superset, - // and the theme from props. - theme={reactSelectTheme => - merge(reactSelectTheme, defaultTheme(theme), themeConfig) + useEffect(() => { + const allowFetch = !fetchOnlyOnSearch || searchedValue; + if (isAsync && loadingEnabled && allowFetch) { + const page = 0; + handlePaginatedFetch(searchedValue, page, pageSize); + setPage(page); + } + }, [ + isAsync, + searchedValue, + pageSize, + handlePaginatedFetch, + loadingEnabled, + fetchOnlyOnSearch, + ]); + + useEffect(() => { + if (isSingleMode) { + handleTopOptions(selectValue); + } + }, [handleTopOptions, isSingleMode, selectValue]); + + const dropdownRender = ( + originNode: ReactElement & { ref?: RefObject<HTMLElement> }, + ) => { + if (!isDropdownVisible) { + originNode.ref?.current?.scrollTo({ top: 0 }); + } + return error ? <Error error={error} /> : originNode; + }; + + const SuffixIcon = () => { + if (isLoading) { + return <StyledSpin size="small" />; + } + if (shouldShowSearch && isDropdownVisible) { + return <SearchOutlined />; + } + return <DownOutlined />; + }; + + return ( + <StyledContainer> + {header} + <StyledSelect + aria-label={ariaLabel || name} + dropdownRender={dropdownRender} + filterOption={handleFilterOption} + getPopupContainer={triggerNode => triggerNode.parentNode} + labelInValue={isAsync} + maxTagCount={MAX_TAG_COUNT} + mode={mappedMode} + onDeselect={handleOnDeselect} + onDropdownVisibleChange={handleOnDropdownVisibleChange} + onPopupScroll={isAsync ? handlePagination : undefined} + onSearch={shouldShowSearch ? handleOnSearch : undefined} + onSelect={handleOnSelect} + onClear={() => setSelectValue(undefined)} + options={selectOptions} + placeholder={placeholder} + showSearch={shouldShowSearch} + showArrow + tokenSeparators={TOKEN_SEPARATORS} + value={selectValue} + suffixIcon={<SuffixIcon />} + menuItemSelectedIcon={ + invertSelection ? ( + <StyledStopOutlined iconSize="m" /> + ) : ( + <StyledCheckOutlined iconSize="m" /> + ) } - formatOptionLabel={formatOptionLabel} - getOptionLabel={getOptionLabel} - getOptionValue={getOptionValue} - components={components} - {...restProps} + {...props} /> - ); - } - - // React.memo makes sure the component does no rerender given the same props - return React.memo(StyledSelect); -} + </StyledContainer> + ); +}; -export const Select = styled(WindowedSelect); -export const AsyncSelect = styled(WindowedAsyncSelect); -export const CreatableSelect = styled(WindowedCreatableSelect); -export const AsyncCreatableSelect = styled(WindowedAsyncCreatableSelect); -export const PaginatedSelect = withAsyncPaginate( - styled<OptionTypeBase, ComponentType<SelectProps<OptionTypeBase>>>( - BasicSelect, - ), -); export default Select; diff --git a/superset-frontend/src/components/Select/index.ts b/superset-frontend/src/components/Select/index.ts index 009e8edf220b..e8f30eb7e4cb 100644 --- a/superset-frontend/src/components/Select/index.ts +++ b/superset-frontend/src/components/Select/index.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -export * from './Select'; +export * from './DeprecatedSelect'; export * from './styles'; -export { default } from './Select'; +export { default } from './DeprecatedSelect'; export { default as OnPasteSelect } from './OnPasteSelect'; export { NativeSelect, NativeGraySelect } from './NativeSelect'; diff --git a/superset-frontend/src/components/Select/styles.tsx b/superset-frontend/src/components/Select/styles.tsx index b1603e6473e1..015c9bbc471f 100644 --- a/superset-frontend/src/components/Select/styles.tsx +++ b/superset-frontend/src/components/Select/styles.tsx @@ -30,7 +30,7 @@ import { Props as SelectProps } from 'react-select/src/Select'; import { colors as reactSelectColors } from 'react-select/src/theme'; import { DeepNonNullable } from 'react-select/src/components'; import { OptionType } from 'antd/lib/select'; -import { SupersetStyledSelectProps } from './Select'; +import { SupersetStyledSelectProps } from './DeprecatedSelect'; export const DEFAULT_CLASS_NAME = 'Select'; export const DEFAULT_CLASS_NAME_PREFIX = 'Select'; diff --git a/superset-frontend/src/components/Select/utils.ts b/superset-frontend/src/components/Select/utils.ts index 497791b62d75..54a7caf9b18d 100644 --- a/superset-frontend/src/components/Select/utils.ts +++ b/superset-frontend/src/components/Select/utils.ts @@ -23,6 +23,8 @@ import { GroupedOptionsType, } from 'react-select'; +import { OptionsType as AntdOptionsType } from './Select'; + /** * Find Option value that matches a possibly string value. * @@ -57,3 +59,16 @@ export function findValue<OptionType extends OptionTypeBase>( // empty: https://github.com/JedWatson/react-select/blob/32ad5c040bdd96cd1ca71010c2558842d684629c/packages/react-select/src/utils.js#L64 return (Array.isArray(value) ? value : [value]).map(find); } + +export function hasOption(search: string, options: AntdOptionsType) { + const searchOption = search.trim().toLowerCase(); + return options.find(opt => { + const { label, value } = opt; + const labelText = String(label); + const valueText = String(value); + return ( + valueText.toLowerCase().includes(searchOption) || + labelText.toLowerCase().includes(searchOption) + ); + }); +} diff --git a/superset-frontend/src/components/SupersetResourceSelect/SupersetResourceSelect.test.tsx b/superset-frontend/src/components/SupersetResourceSelect/SupersetResourceSelect.test.tsx deleted file mode 100644 index ad5d688030ab..000000000000 --- a/superset-frontend/src/components/SupersetResourceSelect/SupersetResourceSelect.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; -import userEvent from '@testing-library/user-event'; -import fetchMock from 'fetch-mock'; -import SupersetResourceSelect from '.'; - -const mockedProps = { - resource: 'dataset', - searchColumn: 'table_name', - onError: () => {}, -}; - -fetchMock.get('glob:*/api/v1/dataset/?q=*', {}); - -test('should render', () => { - const { container } = render(<SupersetResourceSelect {...mockedProps} />); - expect(container).toBeInTheDocument(); -}); - -test('should render the Select... placeholder', () => { - render(<SupersetResourceSelect {...mockedProps} />); - expect(screen.getByText('Select...')).toBeInTheDocument(); -}); - -test('should render the Loading... message', () => { - render(<SupersetResourceSelect {...mockedProps} />); - const select = screen.getByText('Select...'); - userEvent.click(select); - expect(screen.getByText('Loading...')).toBeInTheDocument(); -}); - -test('should render the No options message', async () => { - render(<SupersetResourceSelect {...mockedProps} />); - const select = screen.getByText('Select...'); - userEvent.click(select); - expect(await screen.findByText('No options')).toBeInTheDocument(); -}); - -test('should render the typed text', async () => { - render(<SupersetResourceSelect {...mockedProps} />); - const select = screen.getByText('Select...'); - userEvent.click(select); - userEvent.type(select, 'typed text'); - expect(await screen.findByText('typed text')).toBeInTheDocument(); -}); diff --git a/superset-frontend/src/components/SupersetResourceSelect/index.tsx b/superset-frontend/src/components/SupersetResourceSelect/index.tsx deleted file mode 100644 index d11b365059e9..000000000000 --- a/superset-frontend/src/components/SupersetResourceSelect/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React, { useEffect } from 'react'; -import rison from 'rison'; -import { SupersetClient } from '@superset-ui/core'; -import { AsyncSelect } from 'src/components/Select'; -import { - ClientErrorObject, - getClientErrorObject, -} from 'src/utils/getClientErrorObject'; -import { cacheWrapper } from 'src/utils/cacheWrapper'; - -export type Value<V> = { value: V; label: string }; - -export interface SupersetResourceSelectProps<T = unknown, V = string> { - value?: Value<V> | null; - initialId?: number | string; - onChange?: (value?: Value<V>) => void; - isMulti?: boolean; - searchColumn?: string; - resource?: string; // e.g. "dataset", "dashboard/related/owners" - transformItem?: (item: T) => Value<V>; - onError: (error: ClientErrorObject) => void; - defaultOptions?: { value: number; label: string }[] | boolean; -} - -/** - * This is a special-purpose select component for when you're selecting - * items from one of the standard Superset resource APIs. - * Such as selecting a datasource, a chart, or users. - * - * If you're selecting a "related" resource (such as dashboard/related/owners), - * leave the searchColumn prop unset. - * The api doesn't do columns on related resources for some reason. - * - * If you're doing anything more complex than selecting a standard resource, - * we'll all be better off if you use AsyncSelect directly instead. - */ - -const localCache = new Map<string, any>(); - -export const cachedSupersetGet = cacheWrapper( - SupersetClient.get, - localCache, - ({ endpoint }) => endpoint || '', -); - -export default function SupersetResourceSelect<T, V>({ - value, - initialId, - onChange, - isMulti, - resource, - searchColumn, - transformItem, - onError, - defaultOptions = true, -}: SupersetResourceSelectProps<T, V>) { - useEffect(() => { - if (initialId == null) return; - cachedSupersetGet({ - endpoint: `/api/v1/${resource}/${initialId}`, - }) - .then(response => { - const { result } = response.json; - const value = transformItem ? transformItem(result) : result; - if (onChange) onChange(value); - }) - .catch(response => { - if (response?.status === 404 && onChange) onChange(undefined); - }); - }, [resource, initialId]); // eslint-disable-line react-hooks/exhaustive-deps - - function loadOptions(input: string) { - const query = searchColumn - ? rison.encode({ - filters: [{ col: searchColumn, opr: 'ct', value: input }], - }) - : rison.encode({ filter: value }); - return cachedSupersetGet({ - endpoint: `/api/v1/${resource}/?q=${query}`, - }).then( - response => - response.json.result - .map(transformItem) - .sort((a: Value<V>, b: Value<V>) => a.label.localeCompare(b.label)), - async badResponse => { - onError(await getClientErrorObject(badResponse)); - return []; - }, - ); - } - - return ( - <AsyncSelect - value={value} - onChange={onChange} - isMulti={isMulti} - loadOptions={loadOptions} - defaultOptions={defaultOptions} // true - load options on render - cacheOptions - filterOption={null} // options are filtered at the api - /> - ); -} diff --git a/superset-frontend/src/components/TableSelector/TableSelector.test.jsx b/superset-frontend/src/components/TableSelector/TableSelector.test.jsx deleted file mode 100644 index e1fa66b70709..000000000000 --- a/superset-frontend/src/components/TableSelector/TableSelector.test.jsx +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import configureStore from 'redux-mock-store'; -import { mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import sinon from 'sinon'; -import fetchMock from 'fetch-mock'; -import thunk from 'redux-thunk'; -import { supersetTheme, ThemeProvider } from '@superset-ui/core'; - -import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; - -import DatabaseSelector from 'src/components/DatabaseSelector'; -import TableSelector from 'src/components/TableSelector'; -import { initialState, tables } from 'spec/javascripts/sqllab/fixtures'; - -const mockStore = configureStore([thunk]); -const store = mockStore(initialState); - -const FETCH_SCHEMAS_ENDPOINT = 'glob:*/api/v1/database/*/schemas/*'; -const GET_TABLE_ENDPOINT = 'glob:*/superset/tables/1/*/*'; -const GET_TABLE_NAMES_ENDPOINT = 'glob:*/superset/tables/1/main/*'; - -const mockedProps = { - clearable: false, - database: { id: 1, database_name: 'main' }, - dbId: 1, - formMode: false, - getDbList: sinon.stub(), - handleError: sinon.stub(), - horizontal: false, - onChange: sinon.stub(), - onDbChange: sinon.stub(), - onSchemaChange: sinon.stub(), - onTableChange: sinon.stub(), - sqlLabMode: true, - tableName: '', - tableNameSticky: true, -}; - -const schemaOptions = { - result: ['main', 'erf', 'superset'], -}; -const selectedSchema = { label: 'main', title: 'main', value: 'main' }; -const selectedTable = { - extra: null, - label: 'birth_names', - schema: 'main', - title: 'birth_names', - type: undefined, - value: 'birth_names', -}; - -async function mountAndWait(props = mockedProps) { - const mounted = mount(<TableSelector {...props} />, { - context: { store }, - wrappingComponent: ThemeProvider, - wrappingComponentProps: { theme: supersetTheme }, - }); - await waitForComponentToPaint(mounted); - - return mounted; -} - -describe('TableSelector', () => { - let wrapper; - - beforeEach(async () => { - fetchMock.reset(); - wrapper = await mountAndWait(); - }); - - it('renders', () => { - expect(wrapper.find(TableSelector)).toExist(); - expect(wrapper.find(DatabaseSelector)).toExist(); - }); - - describe('change database', () => { - afterEach(fetchMock.resetHistory); - afterAll(fetchMock.reset); - - it('should fetch schemas', async () => { - fetchMock.get(FETCH_SCHEMAS_ENDPOINT, { overwriteRoutes: true }); - act(() => { - wrapper.find('[data-test="select-database"]').first().props().onChange({ - id: 1, - database_name: 'main', - }); - }); - await waitForComponentToPaint(wrapper); - expect(fetchMock.calls(FETCH_SCHEMAS_ENDPOINT)).toHaveLength(1); - }); - - it('should fetch schema options', async () => { - fetchMock.get(FETCH_SCHEMAS_ENDPOINT, schemaOptions, { - overwriteRoutes: true, - }); - act(() => { - wrapper.find('[data-test="select-database"]').first().props().onChange({ - id: 1, - database_name: 'main', - }); - }); - await waitForComponentToPaint(wrapper); - wrapper.update(); - expect(fetchMock.calls(FETCH_SCHEMAS_ENDPOINT)).toHaveLength(1); - - expect( - wrapper.find('[name="select-schema"]').first().props().options, - ).toEqual([ - { value: 'main', label: 'main', title: 'main' }, - { value: 'erf', label: 'erf', title: 'erf' }, - { value: 'superset', label: 'superset', title: 'superset' }, - ]); - }); - - it('should clear table options', async () => { - act(() => { - wrapper.find('[data-test="select-database"]').first().props().onChange({ - id: 1, - database_name: 'main', - }); - }); - await waitForComponentToPaint(wrapper); - const props = wrapper.find('[name="async-select-table"]').first().props(); - expect(props.isDisabled).toBe(true); - expect(props.value).toEqual(undefined); - }); - }); - - describe('change schema', () => { - beforeEach(async () => { - fetchMock.get(FETCH_SCHEMAS_ENDPOINT, schemaOptions, { - overwriteRoutes: true, - }); - }); - - afterEach(fetchMock.resetHistory); - afterAll(fetchMock.reset); - - it('should fetch table', async () => { - fetchMock.get(GET_TABLE_NAMES_ENDPOINT, { overwriteRoutes: true }); - act(() => { - wrapper.find('[data-test="select-database"]').first().props().onChange({ - id: 1, - database_name: 'main', - }); - }); - await waitForComponentToPaint(wrapper); - act(() => { - wrapper - .find('[name="select-schema"]') - .first() - .props() - .onChange(selectedSchema); - }); - await waitForComponentToPaint(wrapper); - expect(fetchMock.calls(GET_TABLE_NAMES_ENDPOINT)).toHaveLength(1); - }); - - it('should fetch table options', async () => { - fetchMock.get(GET_TABLE_NAMES_ENDPOINT, tables, { - overwriteRoutes: true, - }); - act(() => { - wrapper.find('[data-test="select-database"]').first().props().onChange({ - id: 1, - database_name: 'main', - }); - }); - await waitForComponentToPaint(wrapper); - act(() => { - wrapper - .find('[name="select-schema"]') - .first() - .props() - .onChange(selectedSchema); - }); - await waitForComponentToPaint(wrapper); - expect( - wrapper.find('[name="select-schema"]').first().props().value[0], - ).toEqual(selectedSchema); - expect(fetchMock.calls(GET_TABLE_NAMES_ENDPOINT)).toHaveLength(1); - const { options } = wrapper.find('[name="select-table"]').first().props(); - expect({ options }).toEqual(tables); - }); - }); - - describe('change table', () => { - beforeEach(async () => { - fetchMock.get(GET_TABLE_NAMES_ENDPOINT, tables, { - overwriteRoutes: true, - }); - }); - - it('should change table value', async () => { - act(() => { - wrapper - .find('[name="select-schema"]') - .first() - .props() - .onChange(selectedSchema); - }); - await waitForComponentToPaint(wrapper); - act(() => { - wrapper - .find('[name="select-table"]') - .first() - .props() - .onChange(selectedTable); - }); - await waitForComponentToPaint(wrapper); - expect( - wrapper.find('[name="select-table"]').first().props().value, - ).toEqual('birth_names'); - }); - - it('should call onTableChange with schema from table object', async () => { - act(() => { - wrapper - .find('[name="select-schema"]') - .first() - .props() - .onChange(selectedSchema); - }); - await waitForComponentToPaint(wrapper); - act(() => { - wrapper - .find('[name="select-table"]') - .first() - .props() - .onChange(selectedTable); - }); - await waitForComponentToPaint(wrapper); - expect(mockedProps.onTableChange.getCall(0).args[0]).toBe('birth_names'); - expect(mockedProps.onTableChange.getCall(0).args[1]).toBe('main'); - }); - }); - - describe('getTableNamesBySubStr', () => { - afterEach(fetchMock.resetHistory); - afterAll(fetchMock.reset); - - it('should handle empty', async () => { - act(() => { - wrapper - .find('[name="async-select-table"]') - .first() - .props() - .loadOptions(); - }); - await waitForComponentToPaint(wrapper); - const props = wrapper.find('[name="async-select-table"]').first().props(); - expect(props.isDisabled).toBe(true); - expect(props.value).toEqual(''); - }); - - it('should handle table name', async () => { - wrapper.setProps({ schema: 'main' }); - fetchMock.get(GET_TABLE_ENDPOINT, tables, { - overwriteRoutes: true, - }); - act(() => { - wrapper - .find('[name="async-select-table"]') - .first() - .props() - .loadOptions(); - }); - await waitForComponentToPaint(wrapper); - expect(fetchMock.calls(GET_TABLE_ENDPOINT)).toHaveLength(1); - }); - }); -}); diff --git a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx new file mode 100644 index 000000000000..3b8b61715399 --- /dev/null +++ b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx @@ -0,0 +1,91 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import { SupersetClient } from '@superset-ui/core'; +import userEvent from '@testing-library/user-event'; +import TableSelector from '.'; + +const SupersetClientGet = jest.spyOn(SupersetClient, 'get'); + +const createProps = () => ({ + dbId: 1, + schema: 'test_schema', + handleError: jest.fn(), +}); + +beforeAll(() => { + SupersetClientGet.mockImplementation( + async () => + ({ + json: { + options: [ + { label: 'table_a', value: 'table_a' }, + { label: 'table_b', value: 'table_b' }, + ], + }, + } as any), + ); +}); + +test('renders with default props', async () => { + const props = createProps(); + render(<TableSelector {...props} />); + const databaseSelect = screen.getByRole('combobox', { + name: 'Select a database', + }); + const schemaSelect = screen.getByRole('combobox', { + name: 'Select a database', + }); + const tableSelect = screen.getByRole('combobox', { + name: 'Select a table', + }); + await waitFor(() => { + expect(databaseSelect).toBeInTheDocument(); + expect(schemaSelect).toBeInTheDocument(); + expect(tableSelect).toBeInTheDocument(); + }); +}); + +test('renders table options', async () => { + const props = createProps(); + render(<TableSelector {...props} />); + const tableSelect = screen.getByRole('combobox', { + name: 'Select a table', + }); + userEvent.click(tableSelect); + expect( + await screen.findByRole('option', { name: 'table_a' }), + ).toBeInTheDocument(); + expect( + await screen.findByRole('option', { name: 'table_b' }), + ).toBeInTheDocument(); +}); + +test('renders disabled without schema', async () => { + const props = createProps(); + render(<TableSelector {...props} schema={undefined} />); + const tableSelect = screen.getByRole('combobox', { + name: 'Select a table', + }); + await waitFor(() => { + expect(tableSelect).toBeDisabled(); + }); +}); diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx index 56d3b2157294..5f68a94dfe56 100644 --- a/superset-frontend/src/components/TableSelector/index.tsx +++ b/superset-frontend/src/components/TableSelector/index.tsx @@ -18,53 +18,49 @@ */ import React, { FunctionComponent, - useEffect, useState, ReactNode, + useMemo, + useEffect, } from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; -import { AsyncSelect, CreatableSelect, Select } from 'src/components/Select'; - +import { Select } from 'src/components'; import { FormLabel } from 'src/components/Form'; - +import Icons from 'src/components/Icons'; import DatabaseSelector from 'src/components/DatabaseSelector'; import RefreshLabel from 'src/components/RefreshLabel'; import CertifiedIcon from 'src/components/CertifiedIcon'; import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip'; -const FieldTitle = styled.p` - color: ${({ theme }) => theme.colors.secondary.light2}; - font-size: ${({ theme }) => theme.typography.sizes.s}px; - margin: 20px 0 10px 0; - text-transform: uppercase; -`; - const TableSelectorWrapper = styled.div` - .fa-refresh { - padding-left: 9px; - } + ${({ theme }) => ` + .refresh { + display: flex; + align-items: center; + width: 30px; + margin-left: ${theme.gridUnit}px; + margin-top: ${theme.gridUnit * 5}px; + } - .refresh-col { - display: flex; - align-items: center; - width: 30px; - margin-left: ${({ theme }) => theme.gridUnit}px; - } + .section { + display: flex; + flex-direction: row; + align-items: center; + } - .section { - padding-bottom: 5px; - display: flex; - flex-direction: row; - } + .divider { + border-bottom: 1px solid ${theme.colors.secondary.light5}; + margin: 15px 0; + } - .select { - flex-grow: 1; - } + .table-length { + color: ${theme.colors.grayscale.light1}; + } - .divider { - border-bottom: 1px solid ${({ theme }) => theme.colors.secondary.light5}; - margin: 15px 0; - } + .select { + flex: 1; + } + `} `; const TableLabel = styled.span` @@ -72,8 +68,8 @@ const TableLabel = styled.span` display: flex; white-space: nowrap; - > svg, - > small { + svg, + small { margin-right: ${({ theme }) => theme.gridUnit}px; } `; @@ -86,7 +82,7 @@ interface TableSelectorProps { getDbList?: (arg0: any) => {}; handleError: (msg: string) => void; isDatabaseSelectEnabled?: boolean; - onChange?: ({ + onUpdate?: ({ dbId, schema, }: { @@ -94,7 +90,15 @@ interface TableSelectorProps { schema?: string; tableName?: string; }) => void; - onDbChange?: (db: any) => void; + onDbChange?: ( + db: + | { + id: number; + database_name: string; + backend: string; + } + | undefined, + ) => void; onSchemaChange?: (arg0?: any) => {}; onSchemasLoad?: () => void; onTableChange?: (tableName: string, schema: string) => void; @@ -106,6 +110,52 @@ interface TableSelectorProps { tableNameSticky?: boolean; } +interface Table { + label: string; + value: string; + type: string; + extra?: { + certification?: { + certified_by: string; + details: string; + }; + warning_markdown?: string; + }; +} + +interface TableOption { + label: JSX.Element; + text: string; + value: string; +} + +const TableOption = ({ table }: { table: Table }) => { + const { label, type, extra } = table; + return ( + <TableLabel title={label}> + {type === 'view' ? ( + <Icons.Eye iconSize="m" /> + ) : ( + <Icons.Table iconSize="m" /> + )} + {extra?.certification && ( + <CertifiedIcon + certifiedBy={extra.certification.certified_by} + details={extra.certification.details} + size="l" + /> + )} + {extra?.warning_markdown && ( + <WarningIconWithTooltip + warningMarkdown={extra.warning_markdown} + size="l" + /> + )} + {label} + </TableLabel> + ); +}; + const TableSelector: FunctionComponent<TableSelectorProps> = ({ database, dbId, @@ -113,7 +163,7 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({ getDbList, handleError, isDatabaseSelectEnabled = true, - onChange, + onUpdate, onDbChange, onSchemaChange, onSchemasLoad, @@ -125,179 +175,187 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({ tableName, tableNameSticky = true, }) => { + const [currentDbId, setCurrentDbId] = useState<number | undefined>(dbId); const [currentSchema, setCurrentSchema] = useState<string | undefined>( schema, ); - const [currentTableName, setCurrentTableName] = useState<string | undefined>( - tableName, - ); - const [tableLoading, setTableLoading] = useState(false); - const [tableOptions, setTableOptions] = useState([]); - - function fetchTables( - databaseId?: number, - schema?: string, - forceRefresh = false, - substr = 'undefined', - ) { - const dbSchema = schema || currentSchema; - const actualDbId = databaseId || dbId; - if (actualDbId && dbSchema) { - const encodedSchema = encodeURIComponent(dbSchema); - const encodedSubstr = encodeURIComponent(substr); - setTableLoading(true); - setTableOptions([]); + const [currentTable, setCurrentTable] = useState<TableOption | undefined>(); + const [refresh, setRefresh] = useState(0); + const [previousRefresh, setPreviousRefresh] = useState(0); + + const loadTable = useMemo( + () => async (dbId: number, schema: string, tableName: string) => { const endpoint = encodeURI( - `/superset/tables/${actualDbId}/${encodedSchema}/${encodedSubstr}/${!!forceRefresh}/`, + `/superset/tables/${dbId}/${schema}/${encodeURIComponent( + tableName, + )}/false/true`, ); - return SupersetClient.get({ endpoint }) - .then(({ json }) => { - const options = json.options.map((o: any) => ({ - value: o.value, - schema: o.schema, - label: o.label, - title: o.title, - type: o.type, - extra: o?.extra, - })); - setTableLoading(false); - setTableOptions(options); + + if (previousRefresh !== refresh) { + setPreviousRefresh(refresh); + } + + return SupersetClient.get({ endpoint }).then(({ json }) => { + const options = json.options as Table[]; + if (options && options.length > 0) { + return options[0]; + } + return null; + }); + }, + [], // eslint-disable-line react-hooks/exhaustive-deps + ); + + const loadTables = useMemo( + () => async (search: string) => { + const dbSchema = schema || currentSchema; + if (currentDbId && dbSchema) { + const encodedSchema = encodeURIComponent(dbSchema); + const encodedSubstr = encodeURIComponent(search || 'undefined'); + const forceRefresh = refresh !== previousRefresh; + const endpoint = encodeURI( + `/superset/tables/${currentDbId}/${encodedSchema}/${encodedSubstr}/${forceRefresh}/`, + ); + + if (previousRefresh !== refresh) { + setPreviousRefresh(refresh); + } + + return SupersetClient.get({ endpoint }).then(({ json }) => { + const options = json.options + .map((table: Table) => ({ + value: table.value, + label: <TableOption table={table} />, + text: table.label, + })) + .sort((a: { text: string }, b: { text: string }) => + a.text.localeCompare(b.text), + ); + if (onTablesLoad) { onTablesLoad(json.options); } - }) - .catch(() => { - setTableLoading(false); - setTableOptions([]); - handleError(t('Error while fetching table list')); + + return { + data: options, + totalCount: options.length, + }; }); - } - setTableLoading(false); - setTableOptions([]); - return Promise.resolve(); - } + } + return { data: [], totalCount: 0 }; + }, + // We are using the refresh state to re-trigger the query + // previousRefresh should be out of dependencies array + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentDbId, currentSchema, onTablesLoad, schema, refresh], + ); useEffect(() => { - if (dbId && schema) { - fetchTables(); + async function fetchTable() { + if (schema && tableName) { + const table = await loadTable(dbId, schema, tableName); + if (table) { + setCurrentTable({ + label: <TableOption table={table} />, + text: table.label, + value: table.value, + }); + } + } } - }, [dbId, schema]); + fetchTable(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps function onSelectionChange({ dbId, schema, - tableName, + table, }: { dbId: number; schema?: string; - tableName?: string; + table?: TableOption; }) { - setCurrentTableName(tableName); + setCurrentTable(table); + setCurrentDbId(dbId); setCurrentSchema(schema); - if (onChange) { - onChange({ dbId, schema, tableName }); - } - } - - function getTableNamesBySubStr(substr = 'undefined') { - if (!dbId || !substr) { - const options: any[] = []; - return Promise.resolve({ options }); + if (onUpdate) { + onUpdate({ dbId, schema, tableName: table?.value }); } - const encodedSchema = encodeURIComponent(schema || ''); - const encodedSubstr = encodeURIComponent(substr); - return SupersetClient.get({ - endpoint: encodeURI( - `/superset/tables/${dbId}/${encodedSchema}/${encodedSubstr}`, - ), - }).then(({ json }) => { - const options = json.options.map((o: any) => ({ - value: o.value, - schema: o.schema, - label: o.label, - title: o.title, - type: o.type, - })); - return { options }; - }); } - function changeTable(tableOpt: any) { - if (!tableOpt) { - setCurrentTableName(''); + function changeTable(table: TableOption) { + if (!table) { + setCurrentTable(undefined); return; } - const schemaName = tableOpt.schema; - const tableOptTableName = tableOpt.value; - if (tableNameSticky) { + const tableOptTableName = table.value; + if (currentDbId && tableNameSticky) { onSelectionChange({ - dbId, - schema: schemaName, - tableName: tableOptTableName, + dbId: currentDbId, + schema: currentSchema, + table, }); } - if (onTableChange) { - onTableChange(tableOptTableName, schemaName); + if (onTableChange && currentSchema) { + onTableChange(tableOptTableName, currentSchema); } } - function changeSchema(schemaOpt: any, force = false) { - const value = schemaOpt ? schemaOpt.value : null; + function onRefresh() { if (onSchemaChange) { - onSchemaChange(value); + onSchemaChange(currentSchema); } - onSelectionChange({ - dbId, - schema: value, - tableName: undefined, - }); - fetchTables(dbId, currentSchema, force); - } - - function renderTableOption(option: any) { - return ( - <TableLabel title={option.label}> - <small className="text-muted"> - <i className={`fa fa-${option.type === 'view' ? 'eye' : 'table'}`} /> - </small> - {option.extra?.certification && ( - <CertifiedIcon - certifiedBy={option.extra.certification.certified_by} - details={option.extra.certification.details} - size={20} - /> - )} - {option.extra?.warning_markdown && ( - <WarningIconWithTooltip - warningMarkdown={option.extra.warning_markdown} - size={20} - /> - )} - {option.label} - </TableLabel> - ); + if (currentDbId && currentSchema) { + onSelectionChange({ + dbId: currentDbId, + schema: currentSchema, + table: currentTable, + }); + } + setRefresh(refresh + 1); } function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) { return ( <div className="section"> <span className="select">{select}</span> - <span className="refresh-col">{refreshBtn}</span> + <span className="refresh">{refreshBtn}</span> </div> ); } + const internalDbChange = ( + db: + | { + id: number; + database_name: string; + backend: string; + } + | undefined, + ) => { + setCurrentDbId(db?.id); + if (onDbChange) { + onDbChange(db); + } + }; + + const internalSchemaChange = (schema?: string) => { + setCurrentSchema(schema); + if (onSchemaChange) { + onSchemaChange(schema); + } + }; + function renderDatabaseSelector() { return ( <DatabaseSelector - dbId={dbId} + db={database} formMode={formMode} getDbList={getDbList} - getTableList={fetchTables} handleError={handleError} - onChange={onSelectionChange} - onDbChange={readOnly ? undefined : onDbChange} - onSchemaChange={readOnly ? undefined : onSchemaChange} + onUpdate={onSelectionChange} + onDbChange={readOnly ? undefined : internalDbChange} + onSchemaChange={readOnly ? undefined : internalSchemaChange} onSchemasLoad={onSchemasLoad} schema={currentSchema} sqlLabMode={sqlLabMode} @@ -307,97 +365,54 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({ ); } + const handleFilterOption = useMemo( + () => (search: string, option: TableOption) => { + const searchValue = search.trim().toLowerCase(); + const { text } = option; + return text.toLowerCase().includes(searchValue); + }, + [], + ); + function renderTableSelect() { - const options = tableOptions; - let select = null; - if (currentSchema && !formMode) { - // dataset editor - select = ( - <Select - name="select-table" - isLoading={tableLoading} - ignoreAccents={false} - placeholder={t('Select table or type table name')} - autosize={false} - onChange={changeTable} - options={options} - // @ts-ignore - value={currentTableName} - optionRenderer={renderTableOption} - valueRenderer={renderTableOption} - isDisabled={readOnly} - /> - ); - } else if (formMode) { - select = ( - <CreatableSelect - name="select-table" - isLoading={tableLoading} - ignoreAccents={false} - placeholder={t('Select table or type table name')} - autosize={false} - onChange={changeTable} - options={options} - // @ts-ignore - value={currentTableName} - optionRenderer={renderTableOption} - /> - ); - } else { - // sql lab - let tableSelectPlaceholder; - let tableSelectDisabled = false; - if (database && database.allow_multi_schema_metadata_fetch) { - tableSelectPlaceholder = t('Type to search ...'); - } else { - tableSelectPlaceholder = t('Select table '); - tableSelectDisabled = true; - } - select = ( - <AsyncSelect - name="async-select-table" - placeholder={tableSelectPlaceholder} - isDisabled={tableSelectDisabled} - autosize={false} - onChange={changeTable} - // @ts-ignore - value={currentTableName} - loadOptions={getTableNamesBySubStr} - optionRenderer={renderTableOption} - /> - ); - } + const disabled = + (currentSchema && !formMode && readOnly) || + (!currentSchema && !database?.allow_multi_schema_metadata_fetch); + + const header = sqlLabMode ? ( + <FormLabel>{t('See table schema')}</FormLabel> + ) : ( + <FormLabel>{t('Table')}</FormLabel> + ); + + const select = ( + <Select + ariaLabel={t('Select a table')} + disabled={disabled} + filterOption={handleFilterOption} + header={header} + name="select-table" + onChange={changeTable} + options={loadTables} + placeholder={t('Select a table')} + value={currentTable} + /> + ); + const refresh = !formMode && !readOnly && ( <RefreshLabel - onClick={() => changeSchema({ value: schema }, true)} + onClick={onRefresh} tooltipContent={t('Force refresh table list')} /> ); - return renderSelectRow(select, refresh); - } - function renderSeeTableLabel() { - return ( - <div className="section"> - <FormLabel> - {t('See table schema')}{' '} - {schema && ( - <small> - {tableOptions.length} in - <i>{schema}</i> - </small> - )} - </FormLabel> - </div> - ); + return renderSelectRow(select, refresh); } return ( <TableSelectorWrapper> {renderDatabaseSelector()} - {!formMode && <div className="divider" />} - {sqlLabMode && renderSeeTableLabel()} - {formMode && <FieldTitle>{t('Table')}</FieldTitle>} + {sqlLabMode && !formMode && <div className="divider" />} {renderTableSelect()} </TableSelectorWrapper> ); diff --git a/superset-frontend/src/components/TableView/TableView.tsx b/superset-frontend/src/components/TableView/TableView.tsx index bc3517f5bd90..ff3fee1cbacf 100644 --- a/superset-frontend/src/components/TableView/TableView.tsx +++ b/superset-frontend/src/components/TableView/TableView.tsx @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useEffect } from 'react'; +import isEqual from 'lodash/isEqual'; import { styled, t } from '@superset-ui/core'; import { useFilters, usePagination, useSortBy, useTable } from 'react-table'; import { Empty } from 'src/common/components'; import { TableCollection, Pagination } from 'src/components/dataViewCommon'; -import { SortColumns } from './types'; +import { SortByType, ServerPagination } from './types'; const DEFAULT_PAGE_SIZE = 10; @@ -34,8 +35,11 @@ export interface TableViewProps { columns: any[]; data: any[]; pageSize?: number; + totalCount?: number; + serverPagination?: boolean; + onServerPagination?: (args: ServerPagination) => void; initialPageIndex?: number; - initialSortBy?: SortColumns; + initialSortBy?: SortByType; loading?: boolean; withPagination?: boolean; emptyWrapperType?: EmptyWrapperType; @@ -44,6 +48,7 @@ export interface TableViewProps { isPaginationSticky?: boolean; showRowCount?: boolean; scrollTable?: boolean; + small?: boolean; } const EmptyWrapper = styled.div` @@ -53,34 +58,54 @@ const EmptyWrapper = styled.div` const TableViewStyles = styled.div<{ isPaginationSticky?: boolean; scrollTable?: boolean; + small?: boolean; }>` ${({ scrollTable, theme }) => scrollTable && ` - height: 300px; + flex: 1 1 auto; margin-bottom: ${theme.gridUnit * 4}px; overflow: auto; `} - .table-cell.table-cell { - vertical-align: top; + .table-row { + ${({ theme, small }) => !small && `height: ${theme.gridUnit * 11 - 1}px;`} + + .table-cell { + ${({ theme, small }) => + small && + ` + padding-top: ${theme.gridUnit + 1}px; + padding-bottom: ${theme.gridUnit + 1}px; + line-height: 1.45; + `} + } } - .pagination-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: ${({ theme }) => theme.colors.grayscale.light5}; + th[role='columnheader'] { + z-index: 1; + border-bottom: ${({ theme }) => + `${theme.gridUnit - 2}px solid ${theme.colors.grayscale.light2}`}; + ${({ small }) => small && `padding-bottom: 0;`} + } +`; - ${({ theme, isPaginationSticky }) => - isPaginationSticky && - ` +const PaginationStyles = styled.div<{ + isPaginationSticky?: boolean; +}>` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.colors.grayscale.light5}; + + ${({ isPaginationSticky }) => + isPaginationSticky && + ` position: sticky; bottom: 0; left: 0; `}; - } .row-count-container { margin-top: ${({ theme }) => theme.gridUnit * 2}px; @@ -92,6 +117,7 @@ const TableView = ({ columns, data, pageSize: initialPageSize, + totalCount = data.length, initialPageIndex, initialSortBy = [], loading = false, @@ -99,6 +125,8 @@ const TableView = ({ emptyWrapperType = EmptyWrapperType.Default, noDataText, showRowCount = true, + serverPagination = false, + onServerPagination = () => {}, ...props }: TableViewProps) => { const initialState = { @@ -116,18 +144,38 @@ const TableView = ({ prepareRow, pageCount, gotoPage, - state: { pageIndex, pageSize }, + state: { pageIndex, pageSize, sortBy }, } = useTable( { columns, data, initialState, + manualPagination: serverPagination, + manualSortBy: serverPagination, + pageCount: Math.ceil(totalCount / initialState.pageSize), }, useFilters, useSortBy, usePagination, ); + useEffect(() => { + if (serverPagination && pageIndex !== initialState.pageIndex) { + onServerPagination({ + pageIndex, + }); + } + }, [pageIndex]); + + useEffect(() => { + if (serverPagination && !isEqual(sortBy, initialState.sortBy)) { + onServerPagination({ + pageIndex: 0, + sortBy, + }); + } + }, [sortBy]); + const content = withPagination ? page : rows; let EmptyWrapperComponent; @@ -143,32 +191,38 @@ const TableView = ({ } const isEmpty = !loading && content.length === 0; + const hasPagination = pageCount > 1 && withPagination; return ( - <TableViewStyles {...props}> - <TableCollection - getTableProps={getTableProps} - getTableBodyProps={getTableBodyProps} - prepareRow={prepareRow} - headerGroups={headerGroups} - rows={content} - columns={columns} - loading={loading} - /> - {isEmpty && ( - <EmptyWrapperComponent> - {noDataText ? ( - <Empty - image={Empty.PRESENTED_IMAGE_SIMPLE} - description={noDataText} - /> - ) : ( - <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> - )} - </EmptyWrapperComponent> - )} - {pageCount > 1 && withPagination && ( - <div className="pagination-container"> + <> + <TableViewStyles {...props}> + <TableCollection + getTableProps={getTableProps} + getTableBodyProps={getTableBodyProps} + prepareRow={prepareRow} + headerGroups={headerGroups} + rows={content} + columns={columns} + loading={loading} + /> + {isEmpty && ( + <EmptyWrapperComponent> + {noDataText ? ( + <Empty + image={Empty.PRESENTED_IMAGE_SIMPLE} + description={noDataText} + /> + ) : ( + <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> + )} + </EmptyWrapperComponent> + )} + </TableViewStyles> + {hasPagination && ( + <PaginationStyles + className="pagination-container" + isPaginationSticky={props.isPaginationSticky} + > <Pagination totalPages={pageCount || 0} currentPage={pageCount ? pageIndex + 1 : 0} @@ -182,13 +236,13 @@ const TableView = ({ '%s-%s of %s', pageSize * pageIndex + (page.length && 1), pageSize * pageIndex + page.length, - data.length, + totalCount, )} </div> )} - </div> + </PaginationStyles> )} - </TableViewStyles> + </> ); }; diff --git a/superset-frontend/src/components/TableView/types.ts b/superset-frontend/src/components/TableView/types.ts index 974cb2496233..dceb27573b7e 100644 --- a/superset-frontend/src/components/TableView/types.ts +++ b/superset-frontend/src/components/TableView/types.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -export interface SortColumn { - id: string; - desc?: boolean; -} +import { SortingRule } from 'react-table'; + +export type SortByType = SortingRule<string>[]; -export type SortColumns = SortColumn[]; +export interface ServerPagination { + pageIndex: number; + sortBy?: SortByType; +} diff --git a/superset-frontend/src/components/Tabs/Tabs.tsx b/superset-frontend/src/components/Tabs/Tabs.tsx index 704d84b5220f..b55d91707da3 100644 --- a/superset-frontend/src/components/Tabs/Tabs.tsx +++ b/superset-frontend/src/components/Tabs/Tabs.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { css, styled } from '@superset-ui/core'; import AntDTabs, { TabsProps as AntDTabsProps } from 'antd/lib/tabs'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; export interface TabsProps extends AntDTabsProps { fullWidth?: boolean; @@ -119,6 +119,9 @@ const StyledEditableTabs = styled(StyledTabs)` `} `; +const StyledCancelXIcon = styled(Icons.CancelX)` + color: ${({ theme }) => theme.colors.grayscale.base}; +`; export const EditableTabs = Object.assign(StyledEditableTabs, { TabPane: StyledTabPane, }); @@ -130,9 +133,7 @@ EditableTabs.defaultProps = { }; EditableTabs.TabPane.defaultProps = { - closeIcon: ( - <Icon role="button" tabIndex={0} cursor="pointer" name="cancel-x" /> - ), + closeIcon: <StyledCancelXIcon role="button" tabIndex={0} />, }; export const StyledLineEditableTabs = styled(EditableTabs)` diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.stories.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.stories.tsx new file mode 100644 index 000000000000..cf9d1d6e730e --- /dev/null +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.stories.tsx @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { useArgs } from '@storybook/client-api'; +import TimezoneSelector, { TimezoneProps } from './index'; + +export default { + title: 'TimezoneSelector', + component: TimezoneSelector, +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const InteractiveTimezoneSelector = (args: TimezoneProps) => { + const [{ timezone }, updateArgs] = useArgs(); + const onTimezoneChange = (value: string) => { + updateArgs({ timezone: value }); + }; + return ( + <TimezoneSelector timezone={timezone} onTimezoneChange={onTimezoneChange} /> + ); +}; + +InteractiveTimezoneSelector.args = { + timezone: 'America/Los_Angeles', +}; diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx new file mode 100644 index 000000000000..f0b12d477753 --- /dev/null +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import moment from 'moment-timezone'; +import { render } from 'spec/helpers/testing-library'; +import TimezoneSelector from './index'; + +describe('TimezoneSelector', () => { + let timezone: string; + const onTimezoneChange = jest.fn(zone => { + timezone = zone; + }); + it('renders a TimezoneSelector with a default if undefined', () => { + jest.spyOn(moment.tz, 'guess').mockReturnValue('America/New_York'); + render( + <TimezoneSelector + onTimezoneChange={onTimezoneChange} + timezone={timezone} + />, + ); + expect(onTimezoneChange).toHaveBeenCalledWith('America/Nassau'); + }); + it('renders a TimezoneSelector with the closest value if passed in', async () => { + render( + <TimezoneSelector + onTimezoneChange={onTimezoneChange} + timezone="America/Los_Angeles" + />, + ); + expect(onTimezoneChange).toHaveBeenLastCalledWith('America/Vancouver'); + }); +}); diff --git a/superset-frontend/src/components/TimezoneSelector/index.tsx b/superset-frontend/src/components/TimezoneSelector/index.tsx new file mode 100644 index 000000000000..73c6f1fc8696 --- /dev/null +++ b/superset-frontend/src/components/TimezoneSelector/index.tsx @@ -0,0 +1,132 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef } from 'react'; +import moment from 'moment-timezone'; + +import { NativeGraySelect as Select } from 'src/components/Select'; + +const DEFAULT_TIMEZONE = 'GMT Standard Time'; +const MIN_SELECT_WIDTH = '400px'; + +const offsetsToName = { + '-300-240': ['Eastern Standard Time', 'Eastern Daylight Time'], + '-360-300': ['Central Standard Time', 'Central Daylight Time'], + '-420-360': ['Mountain Standard Time', 'Mountain Daylight Time'], + '-420-420': [ + 'Mountain Standard Time - Phoenix', + 'Mountain Standard Time - Phoenix', + ], + '-480-420': ['Pacific Standard Time', 'Pacific Daylight Time'], + '-540-480': ['Alaska Standard Time', 'Alaska Daylight Time'], + '-600-600': ['Hawaii Standard Time', 'Hawaii Daylight Time'], + '60120': ['Central European Time', 'Central European Daylight Time'], + '00': [DEFAULT_TIMEZONE, DEFAULT_TIMEZONE], + '060': ['GMT Standard Time - London', 'British Summer Time'], +}; + +const currentDate = moment(); +const JANUARY = moment([2021, 1]); +const JULY = moment([2021, 7]); + +const getOffsetKey = (name: string) => + JANUARY.tz(name).utcOffset().toString() + + JULY.tz(name).utcOffset().toString(); + +const getTimezoneName = (name: string) => { + const offsets = getOffsetKey(name); + return ( + (currentDate.tz(name).isDST() + ? offsetsToName[offsets]?.[1] + : offsetsToName[offsets]?.[0]) || name + ); +}; + +export interface TimezoneProps { + onTimezoneChange: (value: string) => void; + timezone?: string | null; +} + +const ALL_ZONES = moment.tz + .countries() + .map(country => moment.tz.zonesForCountry(country, true)) + .flat(); + +const TIMEZONES: moment.MomentZoneOffset[] = []; +ALL_ZONES.forEach(zone => { + if ( + !TIMEZONES.find( + option => getOffsetKey(option.name) === getOffsetKey(zone.name), + ) + ) { + TIMEZONES.push(zone); // dedupe zones by offsets + } +}); + +const TIMEZONE_OPTIONS = TIMEZONES.sort( + // sort by offset + (a, b) => + moment.tz(currentDate, a.name).utcOffset() - + moment.tz(currentDate, b.name).utcOffset(), +).map(zone => ({ + label: `GMT ${moment + .tz(currentDate, zone.name) + .format('Z')} (${getTimezoneName(zone.name)})`, + value: zone.name, + offsets: getOffsetKey(zone.name), +})); + +const timezoneOptions = TIMEZONE_OPTIONS.map(option => ( + <Select.Option key={option.value} value={option.value}> + {option.label} + </Select.Option> +)); + +const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => { + const prevTimezone = useRef(timezone); + const matchTimezoneToOptions = (timezone: string) => + TIMEZONE_OPTIONS.find(option => option.offsets === getOffsetKey(timezone)) + ?.value || DEFAULT_TIMEZONE; + + const updateTimezone = (tz: string) => { + // update the ref to track changes + prevTimezone.current = tz; + // the parent component contains the state for the value + onTimezoneChange(tz); + }; + + useEffect(() => { + const updatedTz = matchTimezoneToOptions(timezone || moment.tz.guess()); + if (prevTimezone.current !== updatedTz) { + updateTimezone(updatedTz); + } + }, [timezone]); + + return ( + <Select + css={{ minWidth: MIN_SELECT_WIDTH }} // smallest size for current values + onChange={onTimezoneChange} + value={timezone || DEFAULT_TIMEZONE} + > + {timezoneOptions} + </Select> + ); +}; + +export default TimezoneSelector; diff --git a/superset-frontend/src/components/Tooltip/index.tsx b/superset-frontend/src/components/Tooltip/index.tsx index 5ec24bb3a60f..926750268280 100644 --- a/superset-frontend/src/components/Tooltip/index.tsx +++ b/superset-frontend/src/components/Tooltip/index.tsx @@ -17,17 +17,32 @@ * under the License. */ import React from 'react'; -import { useTheme } from '@superset-ui/core'; +import { useTheme, css } from '@superset-ui/core'; import { Tooltip as AntdTooltip } from 'antd'; import { TooltipProps } from 'antd/lib/tooltip'; +import { Global } from '@emotion/react'; export const Tooltip = (props: TooltipProps) => { const theme = useTheme(); return ( - <AntdTooltip - overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6' }} - color={`${theme.colors.grayscale.dark2}e6`} - {...props} - /> + <> + {/* Safari hack to hide browser default tooltips */} + <Global + styles={css` + .ant-tooltip-open { + display: inline-block; + &::after { + content: ''; + display: block; + } + } + `} + /> + <AntdTooltip + overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6' }} + color={`${theme.colors.grayscale.dark2}e6`} + {...props} + /> + </> ); }; diff --git a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx index 640397e5c65c..f732554e15aa 100644 --- a/superset-frontend/src/components/WarningIconWithTooltip/index.tsx +++ b/superset-frontend/src/components/WarningIconWithTooltip/index.tsx @@ -18,16 +18,17 @@ */ import React from 'react'; import { useTheme, SafeMarkdown } from '@superset-ui/core'; -import Icons from 'src/components/Icons'; +import Icons, { IconType } from 'src/components/Icons'; import { Tooltip } from 'src/components/Tooltip'; export interface WarningIconWithTooltipProps { warningMarkdown: string; - size?: number; + size?: IconType['iconSize']; } function WarningIconWithTooltip({ warningMarkdown, + size, }: WarningIconWithTooltipProps) { const theme = useTheme(); return ( @@ -35,7 +36,11 @@ function WarningIconWithTooltip({ id="warning-tooltip" title={<SafeMarkdown source={warningMarkdown} />} > - <Icons.AlertSolid iconColor={theme.colors.alert.base} /> + <Icons.AlertSolid + iconColor={theme.colors.alert.base} + iconSize={size} + css={{ marginRight: theme.gridUnit * 2 }} + /> </Tooltip> ); } diff --git a/superset-frontend/src/components/dataViewCommon/TableCollection.tsx b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx index e6f46609d99d..b8f644fe656f 100644 --- a/superset-frontend/src/components/dataViewCommon/TableCollection.tsx +++ b/superset-frontend/src/components/dataViewCommon/TableCollection.tsx @@ -20,7 +20,7 @@ import React from 'react'; import cx from 'classnames'; import { TableInstance } from 'react-table'; import { styled } from '@superset-ui/core'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; interface TableCollectionProps { getTableProps: (userProps?: any) => any; @@ -187,7 +187,7 @@ export const Table = styled.table` text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - max-width: 300px; + max-width: 320px; line-height: 1; vertical-align: middle; &:first-of-type { @@ -228,11 +228,11 @@ export default React.memo( {headerGroups.map(headerGroup => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map(column => { - let sortIcon = <Icon name="sort" />; + let sortIcon = <Icons.Sort />; if (column.isSorted && column.isSortedDesc) { - sortIcon = <Icon name="sort-desc" />; + sortIcon = <Icons.SortDesc />; } else if (column.isSorted && !column.isSortedDesc) { - sortIcon = <Icon name="sort-asc" />; + sortIcon = <Icons.SortAsc />; } return column.hidden ? null : ( <th diff --git a/superset-frontend/src/components/index.ts b/superset-frontend/src/components/index.ts new file mode 100644 index 000000000000..596fbb435b0d --- /dev/null +++ b/superset-frontend/src/components/index.ts @@ -0,0 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { default as Select } from './Select/Select'; diff --git a/superset-frontend/src/constants.ts b/superset-frontend/src/constants.ts index bfa7033697cb..a525106189ec 100644 --- a/superset-frontend/src/constants.ts +++ b/superset-frontend/src/constants.ts @@ -31,6 +31,14 @@ export const URL_PARAMS = { name: 'preselect_filters', type: 'object', }, + nativeFilters: { + name: 'native_filters', + type: 'rison', + }, + filterSet: { + name: 'filter_set', + type: 'string', + }, showFilters: { name: 'show_filters', type: 'boolean', diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js index d0b39e2ff14d..36868b85d56d 100644 --- a/superset-frontend/src/dashboard/actions/dashboardState.js +++ b/superset-frontend/src/dashboard/actions/dashboardState.js @@ -275,6 +275,32 @@ export function fetchCharts( }; } +const refreshCharts = (chartList, force, interval, dashboardId, dispatch) => + new Promise(resolve => { + dispatch(fetchCharts(chartList, force, interval, dashboardId)); + resolve(); + }); + +export const ON_REFRESH_SUCCESS = 'ON_REFRESH_SUCCESS'; +export function onRefreshSuccess() { + return { type: ON_REFRESH_SUCCESS }; +} + +export const ON_REFRESH = 'ON_REFRESH'; +export function onRefresh( + chartList = [], + force = false, + interval = 0, + dashboardId, +) { + return dispatch => { + dispatch({ type: ON_REFRESH }); + refreshCharts(chartList, force, interval, dashboardId, dispatch).then(() => + dispatch({ type: ON_REFRESH_SUCCESS }), + ); + }; +} + export const SHOW_BUILDER_PANE = 'SHOW_BUILDER_PANE'; export function showBuilderPane() { return { type: SHOW_BUILDER_PANE }; @@ -344,9 +370,9 @@ export function setDirectPathToChild(path) { return { type: SET_DIRECT_PATH, path }; } -export const SET_LAST_FOCUSED_TAB = 'SET_LAST_FOCUSED_TAB'; -export function setLastFocusedTab(tabId) { - return { type: SET_LAST_FOCUSED_TAB, tabId }; +export const SET_ACTIVE_TABS = 'SET_ACTIVE_TABS'; +export function setActiveTabs(tabIds) { + return { type: SET_ACTIVE_TABS, tabIds }; } export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD'; @@ -359,6 +385,11 @@ export function unsetFocusedFilterField(chartId, column) { return { type: UNSET_FOCUSED_FILTER_FIELD, chartId, column }; } +export const SET_FULL_SIZE_CHART_ID = 'SET_FULL_SIZE_CHART_ID'; +export function setFullSizeChartId(chartId) { + return { type: SET_FULL_SIZE_CHART_ID, chartId }; +} + // Undo history --------------------------------------------------------------- export const SET_MAX_UNDO_HISTORY_EXCEEDED = 'SET_MAX_UNDO_HISTORY_EXCEEDED'; export function setMaxUndoHistoryExceeded(maxUndoHistoryExceeded = true) { diff --git a/superset-frontend/src/dashboard/actions/datasources.js b/superset-frontend/src/dashboard/actions/datasources.ts similarity index 53% rename from superset-frontend/src/dashboard/actions/datasources.js rename to superset-frontend/src/dashboard/actions/datasources.ts index 4277edc661fc..42004272ccb0 100644 --- a/superset-frontend/src/dashboard/actions/datasources.js +++ b/superset-frontend/src/dashboard/actions/datasources.ts @@ -16,26 +16,44 @@ * specific language governing permissions and limitations * under the License. */ +import { Dispatch } from 'redux'; import { SupersetClient } from '@superset-ui/core'; -import { getClientErrorObject } from '../../utils/getClientErrorObject'; +import { Datasource, RootState } from 'src/dashboard/types'; -export const SET_DATASOURCE = 'SET_DATASOURCE'; -export function setDatasource(datasource, key) { - return { type: SET_DATASOURCE, datasource, key }; +// update datasources index for Dashboard +export enum DatasourcesAction { + SET_DATASOURCES = 'SET_DATASOURCES', + SET_DATASOURCE = 'SET_DATASOURCE', } -export const FETCH_DATASOURCE_STARTED = 'FETCH_DATASOURCE_STARTED'; -export function fetchDatasourceStarted(key) { - return { type: FETCH_DATASOURCE_STARTED, key }; +export type DatasourcesActionPayload = + | { + type: DatasourcesAction.SET_DATASOURCES; + datasources: Datasource[] | null; + } + | { + type: DatasourcesAction.SET_DATASOURCE; + key: Datasource['uid']; + datasource: Datasource; + }; + +export function setDatasources(datasources: Datasource[] | null) { + return { + type: DatasourcesAction.SET_DATASOURCES, + datasources, + }; } -export const FETCH_DATASOURCE_FAILED = 'FETCH_DATASOURCE_FAILED'; -export function fetchDatasourceFailed(error, key) { - return { type: FETCH_DATASOURCE_FAILED, error, key }; +export function setDatasource(datasource: Datasource, key: string) { + return { + type: DatasourcesAction.SET_DATASOURCE, + key, + datasource, + }; } -export function fetchDatasourceMetadata(key) { - return (dispatch, getState) => { +export function fetchDatasourceMetadata(key: string) { + return (dispatch: Dispatch, getState: () => RootState) => { const { datasources } = getState(); const datasource = datasources[key]; @@ -45,12 +63,6 @@ export function fetchDatasourceMetadata(key) { return SupersetClient.get({ endpoint: `/superset/fetch_datasource_metadata?datasourceKey=${key}`, - }) - .then(({ json }) => dispatch(setDatasource(json, key))) - .catch(response => - getClientErrorObject(response).then(({ error }) => - dispatch(fetchDatasourceFailed(error, key)), - ), - ); + }).then(({ json }) => dispatch(setDatasource(json as Datasource, key))); }; } diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 62751033ad67..9c524e2d05be 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -17,7 +17,7 @@ * under the License. */ /* eslint-disable camelcase */ -import { isString, keyBy } from 'lodash'; +import { isString } from 'lodash'; import { Behavior, CategoricalColorNamespace, @@ -60,7 +60,7 @@ import extractUrlParams from '../util/extractUrlParams'; export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD'; -export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( +export const hydrateDashboard = (dashboardData, chartData) => ( dispatch, getState, ) => { @@ -288,7 +288,8 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( } metadata.show_native_filters = - dashboardData?.metadata?.show_native_filters ?? true; + dashboardData?.metadata?.show_native_filters ?? + isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS); if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) { // If user just added cross filter to dashboard it's not saving it scope on server, @@ -328,14 +329,13 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( return dispatch({ type: HYDRATE_DASHBOARD, data: { - datasources: keyBy(datasourcesData, 'uid'), sliceEntities: { ...initSliceEntities, slices, isLoading: false }, charts: chartQueries, // read-only data dashboardInfo: { ...dashboardData, metadata, - userId: String(user.userId), // legacy, please use state.user instead + userId: user.userId ? String(user.userId) : null, // legacy, please use state.user instead dash_edit_perm: canEdit, dash_save_perm: findPermission('can_save_dash', 'Superset', roles), dash_share_perm: findPermission( @@ -360,6 +360,7 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( dashboardFilters, nativeFilters, dashboardState: { + preselectNativeFilters: getUrlParam(URL_PARAMS.nativeFilters), sliceIds: Array.from(sliceIds), directPathToChild, directPathLastUpdated: Date.now(), @@ -377,7 +378,8 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( hasUnsavedChanges: false, maxUndoHistoryExceeded: false, lastModifiedTime: dashboardData.changed_on, - lastFocusedTabId: null, + isRefreshing: false, + activeTabs: [], }, dashboardLayout, }, diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts index e01a5a67244e..150b8857ae6e 100644 --- a/superset-frontend/src/dashboard/actions/nativeFilters.ts +++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts @@ -49,6 +49,11 @@ export interface SetFilterConfigFail { type: typeof SET_FILTER_CONFIG_FAIL; filterConfig: FilterConfiguration; } +export const SET_IN_SCOPE_STATUS_OF_FILTERS = 'SET_IN_SCOPE_STATUS_OF_FILTERS'; +export interface SetInScopeStatusOfFilters { + type: typeof SET_IN_SCOPE_STATUS_OF_FILTERS; + filterConfig: FilterConfiguration; +} export const SET_FILTER_SETS_CONFIG_BEGIN = 'SET_FILTER_SETS_CONFIG_BEGIN'; export interface SetFilterSetsConfigBegin { type: typeof SET_FILTER_SETS_CONFIG_BEGIN; @@ -124,6 +129,25 @@ export const setFilterConfiguration = ( } }; +export const setInScopeStatusOfFilters = ( + filterScopes: { + filterId: string; + chartsInScope: number[]; + tabsInScope: string[]; + }[], +) => async (dispatch: Dispatch, getState: () => any) => { + const filters = getState().nativeFilters?.filters; + const filtersWithScopes = filterScopes.map(scope => ({ + ...filters[scope.filterId], + chartsInScope: scope.chartsInScope, + tabsInScope: scope.tabsInScope, + })); + dispatch({ + type: SET_IN_SCOPE_STATUS_OF_FILTERS, + filterConfig: filtersWithScopes, + }); +}; + type BootstrapData = { nativeFilters: { filters: Filters; @@ -227,6 +251,7 @@ export type AnyFilterAction = | SetFilterSetsConfigBegin | SetFilterSetsConfigComplete | SetFilterSetsConfigFail + | SetInScopeStatusOfFilters | SaveFilterSets | SetBootstrapData | SetFocusedNativeFilter diff --git a/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/CrossFilterScopingForm.test.tsx b/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/CrossFilterScopingForm.test.tsx index 581ac8e31e3c..39cb6f92e102 100644 --- a/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/CrossFilterScopingForm.test.tsx +++ b/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/CrossFilterScopingForm.test.tsx @@ -43,9 +43,9 @@ test('Should send correct props', () => { expect(FilterScope).toHaveBeenCalledWith( expect.objectContaining({ chartId: 123, - scope: 'Scope', - formScope: 'scope', - formScoping: 'scoping', + filterScope: 'Scope', + formFilterScope: 'scope', + formScopingType: 'scoping', }), {}, ); diff --git a/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/index.tsx b/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/index.tsx index c184f9e0d3e9..d1364f372988 100644 --- a/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/index.tsx +++ b/superset-frontend/src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingForm/index.tsx @@ -45,11 +45,11 @@ const CrossFilterScopingForm: FC<CrossFilterScopingFormProps> = ({ ...values, }); }} - scope={scope} + filterScope={scope} chartId={chartId} - formScope={formScope} + formFilterScope={formScope} forceUpdate={forceUpdate} - formScoping={formScoping} + formScopingType={formScoping} /> ); }; diff --git a/superset-frontend/src/dashboard/components/CssEditor/CssEditor.test.tsx b/superset-frontend/src/dashboard/components/CssEditor/CssEditor.test.tsx index 86730fedebed..e96291c9b64c 100644 --- a/superset-frontend/src/dashboard/components/CssEditor/CssEditor.test.tsx +++ b/superset-frontend/src/dashboard/components/CssEditor/CssEditor.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor'; import { AceEditorProps } from 'react-ace'; import userEvent from '@testing-library/user-event'; @@ -32,6 +32,12 @@ jest.mock('src/components/AsyncAceEditor', () => ({ ), })); +const templates = [ + { label: 'Template A', css: 'background-color: red;' }, + { label: 'Template B', css: 'background-color: blue;' }, + { label: 'Template C', css: 'background-color: yellow;' }, +]; + AceCssEditor.preload = () => new Promise(() => {}); test('renders with default props', () => { @@ -46,14 +52,15 @@ test('renders with initial CSS', () => { expect(screen.getByText(initialCss)).toBeInTheDocument(); }); -test('renders with templates', () => { - const templates = ['Template A', 'Template B', 'Template C']; +test('renders with templates', async () => { render(<CssEditor triggerNode={<>Click</>} templates={templates} />); userEvent.click(screen.getByRole('button', { name: 'Click' })); - userEvent.click(screen.getByText('Load a CSS template')); - templates.forEach(template => - expect(screen.getByText(template)).toBeInTheDocument(), - ); + userEvent.hover(screen.getByText('Load a CSS template')); + await waitFor(() => { + templates.forEach(template => + expect(screen.getByText(template.label)).toBeInTheDocument(), + ); + }); }); test('triggers onChange when using the editor', () => { @@ -73,9 +80,8 @@ test('triggers onChange when using the editor', () => { expect(onChange).toHaveBeenLastCalledWith(initialCss.concat(additionalCss)); }); -test('triggers onChange when selecting a template', () => { +test('triggers onChange when selecting a template', async () => { const onChange = jest.fn(); - const templates = ['Template A', 'Template B', 'Template C']; render( <CssEditor triggerNode={<>Click</>} @@ -86,6 +92,6 @@ test('triggers onChange when selecting a template', () => { userEvent.click(screen.getByRole('button', { name: 'Click' })); userEvent.click(screen.getByText('Load a CSS template')); expect(onChange).not.toHaveBeenCalled(); - userEvent.click(screen.getByText('Template A')); + userEvent.click(await screen.findByText('Template A')); expect(onChange).toHaveBeenCalledTimes(1); }); diff --git a/superset-frontend/src/dashboard/components/CssEditor/index.jsx b/superset-frontend/src/dashboard/components/CssEditor/index.jsx index da0b4320447f..995dc081fe2b 100644 --- a/superset-frontend/src/dashboard/components/CssEditor/index.jsx +++ b/superset-frontend/src/dashboard/components/CssEditor/index.jsx @@ -18,11 +18,30 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import Select from 'src/components/Select'; -import { t } from '@superset-ui/core'; +import { Menu, Dropdown } from 'src/common/components'; +import Button from 'src/components/Button'; +import { t, styled } from '@superset-ui/core'; import ModalTrigger from 'src/components/ModalTrigger'; import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor'; +const StyledWrapper = styled.div` + ${({ theme }) => ` + .css-editor-header { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: ${theme.gridUnit * 2}px; + + h5 { + margin-top: ${theme.gridUnit}px; + } + } + .css-editor { + border: 1px solid ${theme.colors.grayscale.light1}; + } + `} +`; + const propTypes = { initialCss: PropTypes.string, triggerNode: PropTypes.node.isRequired, @@ -55,21 +74,24 @@ class CssEditor extends React.PureComponent { }); } - changeCssTemplate(opt) { - this.changeCss(opt.css); + changeCssTemplate({ key }) { + this.changeCss(key); } renderTemplateSelector() { if (this.props.templates) { + const menu = ( + <Menu onClick={this.changeCssTemplate}> + {this.props.templates.map(template => ( + <Menu.Item key={template.css}>{template.label}</Menu.Item> + ))} + </Menu> + ); + return ( - <div style={{ zIndex: 10 }}> - <h5>{t('Load a template')}</h5> - <Select - options={this.props.templates} - placeholder={t('Load a CSS template')} - onChange={this.changeCssTemplate} - /> - </div> + <Dropdown overlay={menu} placement="bottomRight"> + <Button>{t('Load a CSS template')}</Button> + </Dropdown> ); } return null; @@ -81,24 +103,23 @@ class CssEditor extends React.PureComponent { triggerNode={this.props.triggerNode} modalTitle={t('CSS')} modalBody={ - <div> - {this.renderTemplateSelector()} - <div style={{ zIndex: 1 }}> + <StyledWrapper> + <div className="css-editor-header"> <h5>{t('Live CSS editor')}</h5> - <div style={{ border: 'solid 1px grey' }}> - <AceCssEditor - minLines={12} - maxLines={30} - onChange={this.changeCss} - height="200px" - width="100%" - editorProps={{ $blockScrolling: true }} - enableLiveAutocompletion - value={this.state.css || ''} - /> - </div> + {this.renderTemplateSelector()} </div> - </div> + <AceCssEditor + className="css-editor" + minLines={12} + maxLines={30} + onChange={this.changeCss} + height="200px" + width="100%" + editorProps={{ $blockScrolling: true }} + enableLiveAutocompletion + value={this.state.css || ''} + /> + </StyledWrapper> } /> ); diff --git a/superset-frontend/src/dashboard/components/Dashboard.jsx b/superset-frontend/src/dashboard/components/Dashboard.jsx index 4a5c8831dce0..21bd809e1397 100644 --- a/superset-frontend/src/dashboard/components/Dashboard.jsx +++ b/superset-frontend/src/dashboard/components/Dashboard.jsx @@ -132,6 +132,11 @@ class Dashboard extends React.PureComponent { const currentChartIds = getChartIdsFromLayout(this.props.layout); const nextChartIds = getChartIdsFromLayout(nextProps.layout); + if (this.props.dashboardInfo.id !== nextProps.dashboardInfo.id) { + // single-page-app navigation check + return; + } + if (currentChartIds.length < nextChartIds.length) { const newChartIds = nextChartIds.filter( key => currentChartIds.indexOf(key) === -1, diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index 9873dfbd4ff9..cc897b8df574 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -19,15 +19,14 @@ /* eslint-env browser */ import cx from 'classnames'; import React, { FC } from 'react'; -import { Sticky, StickyContainer } from 'react-sticky'; -import { JsonObject, styled } from '@superset-ui/core'; +import { JsonObject, styled, css } from '@superset-ui/core'; import ErrorBoundary from 'src/components/ErrorBoundary'; import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane'; import DashboardHeader from 'src/dashboard/containers/DashboardHeader'; +import Icons from 'src/components/Icons'; import IconButton from 'src/dashboard/components/IconButton'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; -import ToastPresenter from 'src/messageToasts/containers/ToastPresenter'; import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu'; import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex'; import { URL_PARAMS } from 'src/constants'; @@ -35,6 +34,8 @@ import { useDispatch, useSelector } from 'react-redux'; import { getUrlParam } from 'src/utils/urlUtils'; import { DashboardLayout, RootState } from 'src/dashboard/types'; import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState'; +import { useElementOnScreen } from 'src/common/hooks/useElementOnScreen'; +import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { deleteTopLevelTabs, handleComponentDrop, @@ -47,22 +48,69 @@ import { } from 'src/dashboard/util/constants'; import FilterBar from 'src/dashboard/components/nativeFilters/FilterBar'; import Loading from 'src/components/Loading'; -import { StickyVerticalBar } from '../StickyVerticalBar'; +import { Global } from '@emotion/react'; import { shouldFocusTabs, getRootLevelTabsComponent } from './utils'; import DashboardContainer from './DashboardContainer'; import { useNativeFilters } from './state'; -const TABS_HEIGHT = 47; -const HEADER_HEIGHT = 67; +const MAIN_HEADER_HEIGHT = 53; +const TABS_HEIGHT = 50; +const HEADER_HEIGHT = 72; +const CLOSED_FILTER_BAR_WIDTH = 32; +const OPEN_FILTER_BAR_WIDTH = 260; +const FILTER_BAR_HEADER_HEIGHT = 80; +const FILTER_BAR_TABS_HEIGHT = 46; type DashboardBuilderProps = {}; -const StyledDashboardContent = styled.div<{ dashboardFiltersOpen: boolean }>` +const StyledDiv = styled.div` + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr; + flex: 1; +`; + +// @z-index-above-dashboard-charts + 1 = 11 +const FiltersPanel = styled.div` + grid-column: 1; + grid-row: 1 / span 2; + z-index: 11; +`; + +const StickyPanel = styled.div<{ width: number }>` + position: sticky; + top: -1px; + width: ${({ width }) => width}px; + flex: 0 0 ${({ width }) => width}px; +`; + +// @z-index-above-dashboard-popovers (99) + 1 = 100 +const StyledHeader = styled.div` + grid-column: 2; + grid-row: 1; + position: sticky; + top: 0px; + z-index: 100; +`; + +const StyledContent = styled.div<{ + fullSizeChartId: number | null; +}>` + grid-column: 2; + grid-row: 2; + // @z-index-above-dashboard-header (100) + 1 = 101 + ${({ fullSizeChartId }) => fullSizeChartId && `z-index: 101;`} +`; + +const StyledDashboardContent = styled.div<{ + dashboardFiltersOpen: boolean; + editMode: boolean; +}>` display: flex; flex-direction: row; flex-wrap: nowrap; height: auto; - flex-grow: 1; + flex: 1; .grid-container .dashboard-component-tabs { box-shadow: none; @@ -71,17 +119,18 @@ const StyledDashboardContent = styled.div<{ dashboardFiltersOpen: boolean }>` .grid-container { /* without this, the grid will not get smaller upon toggling the builder panel on */ - min-width: 0; - width: 100%; - flex-grow: 1; + width: 0px; + flex: 1; position: relative; - margin: ${({ theme }) => theme.gridUnit * 6}px - ${({ theme }) => theme.gridUnit * 8}px - ${({ theme }) => theme.gridUnit * 6}px - ${({ theme, dashboardFiltersOpen }) => { - if (dashboardFiltersOpen) return theme.gridUnit * 8; + margin-top: ${({ theme }) => theme.gridUnit * 6}px; + margin-right: ${({ theme }) => theme.gridUnit * 8}px; + margin-bottom: ${({ theme }) => theme.gridUnit * 6}px; + margin-left: ${({ theme, dashboardFiltersOpen, editMode }) => { + if (!dashboardFiltersOpen && !editMode) { return 0; - }}px; + } + return theme.gridUnit * 8; + }}px; } .dashboard-component-chart-holder { @@ -104,6 +153,9 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => { const directPathToChild = useSelector<RootState, string[]>( state => state.dashboardState.directPathToChild, ); + const fullSizeChartId = useSelector<RootState, number | null>( + state => state.dashboardState.fullSizeChartId, + ); const handleChangeTab = ({ pathToTabIndex, @@ -129,10 +181,10 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => { rootChildId !== DASHBOARD_GRID_ID ? dashboardLayout[rootChildId] : undefined; - + const StandaloneMode = getUrlParam(URL_PARAMS.standalone); + const isReport = StandaloneMode === DashboardStandaloneMode.REPORT; const hideDashboardHeader = - getUrlParam(URL_PARAMS.standalone) === - DashboardStandaloneMode.HIDE_NAV_AND_TITLE; + StandaloneMode === DashboardStandaloneMode.HIDE_NAV_AND_TITLE || isReport; const barTopOffset = (hideDashboardHeader ? 0 : HEADER_HEIGHT) + @@ -145,89 +197,121 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => { nativeFiltersEnabled, } = useNativeFilters(); + const filterBarWidth = dashboardFiltersOpen + ? OPEN_FILTER_BAR_WIDTH + : CLOSED_FILTER_BAR_WIDTH; + + const [containerRef, isSticky] = useElementOnScreen<HTMLDivElement>({ + threshold: [1], + }); + + const filterSetEnabled = isFeatureEnabled( + FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET, + ); + + const offset = + FILTER_BAR_HEADER_HEIGHT + + (isSticky || StandaloneMode ? 0 : MAIN_HEADER_HEIGHT) + + (filterSetEnabled ? FILTER_BAR_TABS_HEIGHT : 0); + + const filterBarHeight = `calc(100vh - ${offset}px)`; + const filterBarOffset = dashboardFiltersOpen ? 0 : barTopOffset + 20; + return ( - <StickyContainer - className={cx('dashboard', editMode && 'dashboard--editing')} - > - <Sticky> - {({ style }) => ( - // @ts-ignore - <DragDroppable - component={dashboardRoot} - parentComponent={null} - depth={DASHBOARD_ROOT_DEPTH} - index={0} - orientation="column" - onDrop={dropResult => dispatch(handleComponentDrop(dropResult))} - editMode={editMode} - // you cannot drop on/displace tabs if they already exist - disableDragdrop={!!topLevelTabs} - style={{ - zIndex: 100, - ...style, - }} - > - {({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => ( - <div> - {!hideDashboardHeader && <DashboardHeader />} - {dropIndicatorProps && <div {...dropIndicatorProps} />} - {topLevelTabs && ( - <WithPopoverMenu - shouldFocus={shouldFocusTabs} - menuItems={[ - <IconButton - className="fa fa-level-down" - label="Collapse tab content" - onClick={handleDeleteTopLevelTabs} - />, - ]} - editMode={editMode} - > - {/* - // @ts-ignore */} - <DashboardComponent - id={topLevelTabs?.id} - parentId={DASHBOARD_ROOT_ID} - depth={DASHBOARD_ROOT_DEPTH + 1} - index={0} - renderTabContent={false} - renderHoverMenu={false} - onChangeTab={handleChangeTab} - /> - </WithPopoverMenu> - )} - </div> - )} - </DragDroppable> - )} - </Sticky> - <StyledDashboardContent - className="dashboard-content" - dashboardFiltersOpen={dashboardFiltersOpen} - > - {nativeFiltersEnabled && !editMode && ( - <StickyVerticalBar - filtersOpen={dashboardFiltersOpen} - topOffset={barTopOffset} - > + <StyledDiv> + {nativeFiltersEnabled && !editMode && ( + <FiltersPanel> + <StickyPanel ref={containerRef} width={filterBarWidth}> <ErrorBoundary> <FilterBar filtersOpen={dashboardFiltersOpen} toggleFiltersBar={toggleDashboardFiltersOpen} directPathToChild={directPathToChild} + width={filterBarWidth} + height={filterBarHeight} + offset={filterBarOffset} /> </ErrorBoundary> - </StickyVerticalBar> - )} - {showDashboard ? ( - <DashboardContainer topLevelTabs={topLevelTabs} /> - ) : ( - <Loading /> - )} - {editMode && <BuilderComponentPane topOffset={barTopOffset} />} - </StyledDashboardContent> - <ToastPresenter /> - </StickyContainer> + </StickyPanel> + </FiltersPanel> + )} + <StyledHeader> + {/* @ts-ignore */} + <DragDroppable + data-test="top-level-tabs" + component={dashboardRoot} + parentComponent={null} + depth={DASHBOARD_ROOT_DEPTH} + index={0} + orientation="column" + onDrop={dropResult => dispatch(handleComponentDrop(dropResult))} + editMode={editMode} + // you cannot drop on/displace tabs if they already exist + disableDragDrop={!!topLevelTabs} + style={{ + marginLeft: dashboardFiltersOpen || editMode ? 0 : -32, + }} + > + {({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => ( + <div> + {!hideDashboardHeader && <DashboardHeader />} + {dropIndicatorProps && <div {...dropIndicatorProps} />} + {!isReport && topLevelTabs && ( + <WithPopoverMenu + shouldFocus={shouldFocusTabs} + menuItems={[ + <IconButton + icon={<Icons.FallOutlined iconSize="xl" />} + label="Collapse tab content" + onClick={handleDeleteTopLevelTabs} + />, + ]} + editMode={editMode} + > + {/* + // @ts-ignore */} + <DashboardComponent + id={topLevelTabs?.id} + parentId={DASHBOARD_ROOT_ID} + depth={DASHBOARD_ROOT_DEPTH + 1} + index={0} + renderTabContent={false} + renderHoverMenu={false} + onChangeTab={handleChangeTab} + /> + </WithPopoverMenu> + )} + </div> + )} + </DragDroppable> + </StyledHeader> + <StyledContent fullSizeChartId={fullSizeChartId}> + <Global + styles={css` + // @z-index-above-dashboard-header (100) + 1 = 101 + ${fullSizeChartId && + `div > .filterStatusPopover.ant-popover{z-index: 101}`} + `} + /> + <div + data-test="dashboard-content" + className={cx('dashboard', editMode && 'dashboard--editing')} + > + <StyledDashboardContent + className="dashboard-content" + dashboardFiltersOpen={dashboardFiltersOpen} + editMode={editMode} + > + {showDashboard ? ( + <DashboardContainer topLevelTabs={topLevelTabs} /> + ) : ( + <Loading /> + )} + {editMode && <BuilderComponentPane topOffset={barTopOffset} />} + </StyledDashboardContent> + </div> + </StyledContent> + </StyledDiv> ); }; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx index 00c5d73ae5b7..933843337a85 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx @@ -18,10 +18,11 @@ */ // ParentSize uses resize observer so the dashboard will update size // when its container size changes, due to e.g., builder side panel opening -import { ParentSize } from '@vx/responsive'; -import Tabs from 'src/components/Tabs'; import React, { FC, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; +import { ParentSize } from '@vx/responsive'; +import Tabs from 'src/components/Tabs'; import DashboardGrid from 'src/dashboard/containers/DashboardGrid'; import getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath'; import { DashboardLayout, LayoutItem, RootState } from 'src/dashboard/types'; @@ -29,11 +30,12 @@ import { DASHBOARD_GRID_ID, DASHBOARD_ROOT_DEPTH, } from 'src/dashboard/util/constants'; -import { getRootLevelTabIndex } from './utils'; +import { getRootLevelTabIndex, getRootLevelTabsComponent } from './utils'; import { Filters } from '../../reducers/types'; import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; +import findTabIndexByComponentId from '../../util/findTabIndexByComponentId'; import { findTabsWithChartsInScope } from '../nativeFilters/utils'; -import { setFilterConfiguration } from '../../actions/nativeFilters'; +import { setInScopeStatusOfFilters } from '../../actions/nativeFilters'; type DashboardContainerProps = { topLevelTabs?: LayoutItem; @@ -43,9 +45,9 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => { const dashboardLayout = useSelector<RootState, DashboardLayout>( state => state.dashboardLayout.present, ); - const nativeFilters = useSelector<RootState, Filters>( - state => state.nativeFilters.filters, - ); + const nativeFilters = + useSelector<RootState, Filters>(state => state.nativeFilters?.filters) ?? + {}; const directPathToChild = useSelector<RootState, string[]>( state => state.dashboardState.directPathToChild, ); @@ -56,33 +58,48 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => { const dispatch = useDispatch(); useEffect(() => { - setTabIndex(getRootLevelTabIndex(dashboardLayout, directPathToChild)); + const nextTabIndex = findTabIndexByComponentId({ + currentComponent: getRootLevelTabsComponent(dashboardLayout), + directPathToChild, + }); + if (nextTabIndex > -1) { + setTabIndex(nextTabIndex); + } }, [getLeafComponentIdFromPath(directPathToChild)]); // recalculate charts and tabs in scopes of native filters only when a scope or dashboard layout changes - const nativeFiltersValues = Object.values(nativeFilters); - const scopes = nativeFiltersValues.map(filter => filter.scope); + const filterScopes = Object.values(nativeFilters).map(filter => ({ + id: filter.id, + scope: filter.scope, + })); useEffect(() => { - nativeFiltersValues.forEach(filter => { - const filterScope = filter.scope; - const chartsInScope = getChartIdsInFilterScope({ + if ( + !isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) || + filterScopes.length === 0 + ) { + return; + } + const scopes = filterScopes.map(filterScope => { + const { scope } = filterScope; + const chartsInScope: number[] = getChartIdsInFilterScope({ filterScope: { - scope: filterScope.rootPath, + scope: scope.rootPath, // @ts-ignore - immune: filterScope.excluded, + immune: scope.excluded, }, }); const tabsInScope = findTabsWithChartsInScope( dashboardLayout, chartsInScope, ); - Object.assign(filter, { - chartsInScope, + return { + filterId: filterScope.id, tabsInScope: Array.from(tabsInScope), - }); + chartsInScope, + }; }); - dispatch(setFilterConfiguration(nativeFiltersValues)); - }, [JSON.stringify(scopes), JSON.stringify(dashboardLayout)]); + dispatch(setInScopeStatusOfFilters(scopes)); + }, [JSON.stringify(filterScopes), dashboardLayout, dispatch]); const childIds: string[] = topLevelTabs ? topLevelTabs.children diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts index 874525d93d48..71b4cca38d28 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts @@ -50,7 +50,7 @@ export const useNativeFilters = () => { (canEdit || (!canEdit && filterValues.length !== 0)); const requiredFirstFilter = filterValues.filter( - ({ requiredFirst }) => requiredFirst, + filter => filter.requiredFirst, ); const dataMask = useNativeFiltersDataMask(); const showDashboard = diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/utils.ts b/superset-frontend/src/dashboard/components/DashboardBuilder/utils.ts index 999adf62a7c8..f2aa381136b4 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/utils.ts +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/utils.ts @@ -37,8 +37,7 @@ export const shouldFocusTabs = ( ) => // don't focus the tabs when we click on a tab event.target.className === 'ant-tabs-nav-wrap' || - (/icon-button/.test(event.target.className) && - container.contains(event.target)); + container.contains(event.target); export const getRootLevelTabIndex = ( dashboardLayout: DashboardLayout, diff --git a/superset-frontend/src/dashboard/components/DeleteComponentButton.jsx b/superset-frontend/src/dashboard/components/DeleteComponentButton.jsx index 4e3e10d3b0b1..d3936f3ed647 100644 --- a/superset-frontend/src/dashboard/components/DeleteComponentButton.jsx +++ b/superset-frontend/src/dashboard/components/DeleteComponentButton.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; - +import Icons from 'src/components/Icons'; import IconButton from './IconButton'; const propTypes = { @@ -30,7 +30,9 @@ const defaultProps = {}; export default class DeleteComponentButton extends React.PureComponent { render() { const { onDelete } = this.props; - return <IconButton onClick={onDelete} className="fa fa-trash" />; + return ( + <IconButton onClick={onDelete} icon={<Icons.Trash iconSize="xl" />} /> + ); } } diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx index 239df0f54d70..12efe2c3e5d6 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx @@ -96,6 +96,7 @@ test('Should render "appliedCrossFilterIndicators"', () => { <DetailsPanel {...props}> <div data-test="details-panel-content">Content</div> </DetailsPanel>, + { useRedux: true }, ); userEvent.click(screen.getByTestId('details-panel-content')); @@ -129,6 +130,7 @@ test('Should render "appliedIndicators"', () => { <DetailsPanel {...props}> <div data-test="details-panel-content">Content</div> </DetailsPanel>, + { useRedux: true }, ); userEvent.click(screen.getByTestId('details-panel-content')); @@ -160,6 +162,7 @@ test('Should render "incompatibleIndicators"', () => { <DetailsPanel {...props}> <div data-test="details-panel-content">Content</div> </DetailsPanel>, + { useRedux: true }, ); userEvent.click(screen.getByTestId('details-panel-content')); @@ -193,6 +196,7 @@ test('Should render "unsetIndicators"', () => { <DetailsPanel {...props}> <div data-test="details-panel-content">Content</div> </DetailsPanel>, + { useRedux: true }, ); userEvent.click(screen.getByTestId('details-panel-content')); @@ -227,6 +231,7 @@ test('Should render empty', () => { <DetailsPanel {...props}> <div data-test="details-panel-content">Content</div> </DetailsPanel>, + { useRedux: true }, ); expect(screen.getByTestId('details-panel-content')).toBeInTheDocument(); diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx index 5ad9b7838891..54058236c1d2 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx @@ -16,7 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import { Global, css } from '@emotion/react'; import { t, useTheme } from '@superset-ui/core'; import { @@ -26,7 +27,7 @@ import { } from '@ant-design/icons'; import Popover from 'src/components/Popover'; import Collapse from 'src/components/Collapse'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { Indent, Panel, @@ -35,6 +36,7 @@ import { } from 'src/dashboard/components/FiltersBadge/Styles'; import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors'; import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator'; +import { RootState } from 'src/dashboard/types'; export interface DetailsPanelProps { appliedCrossFilterIndicators: Indicator[]; @@ -53,7 +55,25 @@ const DetailsPanelPopover = ({ onHighlightFilterSource, children, }: DetailsPanelProps) => { + const [visible, setVisible] = useState(false); const theme = useTheme(); + const activeTabs = useSelector<RootState>( + state => state.dashboardState?.activeTabs, + ); + + // we don't need to clean up useEffect, setting { once: true } removes the event listener after handle function is called + useEffect(() => { + if (visible) { + window.addEventListener('resize', () => setVisible(false), { + once: true, + }); + } + }, [visible]); + + // if tabs change, popover doesn't close automatically + useEffect(() => { + setVisible(false); + }, [activeTabs]); const getDefaultActivePanel = () => { const result = []; @@ -77,6 +97,7 @@ const DetailsPanelPopover = ({ ]); function handlePopoverStatus(isOpen: boolean) { + setVisible(isOpen); // every time the popover opens, make sure the most relevant panel is active if (isOpen) { setActivePanels(getDefaultActivePanel()); @@ -141,6 +162,7 @@ const DetailsPanelPopover = ({ } &.ant-popover { color: ${theme.colors.grayscale.light4}; + z-index: 99; } } `} @@ -157,9 +179,9 @@ const DetailsPanelPopover = ({ key="appliedCrossFilters" header={ <Title bold color={theme.colors.primary.light1}> - <Icon - name="cross-filter-badge" + <Icons.CursorTarget css={{ fill: theme.colors.primary.light1 }} + iconSize="xl" /> {t( 'Applied Cross Filters (%d)', @@ -255,6 +277,7 @@ const DetailsPanelPopover = ({ <Popover overlayClassName="filterStatusPopover" content={content} + visible={visible} onVisibleChange={handlePopoverStatus} placement="bottom" trigger="click" diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx index f962a72e478e..e94bca37751d 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx @@ -21,6 +21,7 @@ import { SearchOutlined } from '@ant-design/icons'; import React, { FC } from 'react'; import { getFilterValueForDisplay } from 'src/dashboard/components/nativeFilters/FilterBar/FilterSets/utils'; import { + FilterIndicatorText, FilterValue, Item, ItemIcon, @@ -31,24 +32,29 @@ import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors'; export interface IndicatorProps { indicator: Indicator; onClick?: (path: string[]) => void; + text?: string; } const FilterIndicator: FC<IndicatorProps> = ({ indicator: { column, name, value, path = [] }, onClick = () => {}, + text, }) => { const resultValue = getFilterValueForDisplay(value); return ( - <Item onClick={() => onClick([...path, `LABEL-${column}`])}> - <Title bold> - <ItemIcon> - <SearchOutlined /> - </ItemIcon> - {name} - {resultValue ? ': ' : ''} - - {resultValue} - + <> + onClick([...path, `LABEL-${column}`])}> + + <ItemIcon> + <SearchOutlined /> + </ItemIcon> + {name} + {resultValue ? ': ' : ''} + + {resultValue} + + {text && {text}} + ); }; diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx index 74ffd1967789..2fa1afb944ef 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx @@ -35,7 +35,7 @@ export const Pill = styled.div` svg { position: relative; - top: -1px; + top: -2px; color: ${({ theme }) => theme.colors.grayscale.light5}; width: 1em; height: 1em; @@ -153,3 +153,13 @@ export const FilterValue = styled.div` overflow: auto; color: ${({ theme }) => theme.colors.grayscale.light5}; `; + +export const FilterIndicatorText = styled.div` + ${({ theme }) => ` + padding-top: ${theme.gridUnit * 3}px; + max-width: 100%; + flex-grow: 1; + overflow: auto; + color: ${theme.colors.grayscale.light5}; + `} +`; diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index 506dc5216fe4..b9c2b5f736f2 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -16,29 +16,158 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { uniqWith } from 'lodash'; import cx from 'classnames'; -import Icon from 'src/components/Icon'; import Icons from 'src/components/Icons'; +import { usePrevious } from 'src/common/hooks/usePrevious'; +import { DataMaskStateWithId } from 'src/dataMask/types'; import DetailsPanelPopover from './DetailsPanel'; import { Pill } from './Styles'; -import { Indicator } from './selectors'; +import { + Indicator, + IndicatorStatus, + selectIndicatorsForChart, + selectNativeIndicatorsForChart, +} from './selectors'; +import { setDirectPathToChild } from '../../actions/dashboardState'; +import { + ChartsState, + DashboardInfo, + DashboardLayout, + RootState, +} from '../../types'; +import { Filters } from '../../reducers/types'; export interface FiltersBadgeProps { - appliedCrossFilterIndicators: Indicator[]; - appliedIndicators: Indicator[]; - unsetIndicators: Indicator[]; - incompatibleIndicators: Indicator[]; - onHighlightFilterSource: (path: string[]) => void; + chartId: number; } -const FiltersBadge = ({ - appliedCrossFilterIndicators, - appliedIndicators, - unsetIndicators, - incompatibleIndicators, - onHighlightFilterSource, -}: FiltersBadgeProps) => { +const sortByStatus = (indicators: Indicator[]): Indicator[] => { + const statuses = [ + IndicatorStatus.Applied, + IndicatorStatus.Unset, + IndicatorStatus.Incompatible, + ]; + return indicators.sort( + (a, b) => + statuses.indexOf(a.status as IndicatorStatus) - + statuses.indexOf(b.status as IndicatorStatus), + ); +}; + +export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { + const dispatch = useDispatch(); + const datasources = useSelector(state => state.datasources); + const dashboardFilters = useSelector( + state => state.dashboardFilters, + ); + const nativeFilters = useSelector( + state => state.nativeFilters?.filters, + ); + const dashboardInfo = useSelector( + state => state.dashboardInfo, + ); + const charts = useSelector(state => state.charts); + const present = useSelector( + state => state.dashboardLayout.present, + ); + const dataMask = useSelector( + state => state.dataMask, + ); + + const [nativeIndicators, setNativeIndicators] = useState([]); + const [dashboardIndicators, setDashboardIndicators] = useState( + [], + ); + + const onHighlightFilterSource = useCallback( + (path: string[]) => { + dispatch(setDirectPathToChild(path)); + }, + [dispatch], + ); + + const chart = charts[chartId]; + const prevChartStatus = usePrevious(chart?.chartStatus); + + const showIndicators = useCallback( + () => + chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus), + [chart.chartStatus], + ); + useEffect(() => { + if (!showIndicators) { + setDashboardIndicators([]); + } + if (prevChartStatus !== 'success') { + setDashboardIndicators( + selectIndicatorsForChart(chartId, dashboardFilters, datasources, chart), + ); + } + }, [ + chart, + chartId, + dashboardFilters, + datasources, + prevChartStatus, + showIndicators, + ]); + + useEffect(() => { + if (!showIndicators) { + setNativeIndicators([]); + } + if (prevChartStatus !== 'success') { + setNativeIndicators( + selectNativeIndicatorsForChart( + nativeFilters, + dataMask, + chartId, + chart, + present, + dashboardInfo.metadata?.chart_configuration, + ), + ); + } + }, [ + chart, + chartId, + dashboardInfo.metadata?.chart_configuration, + dataMask, + nativeFilters, + present, + prevChartStatus, + showIndicators, + ]); + + const indicators = useMemo( + () => + uniqWith( + sortByStatus([...dashboardIndicators, ...nativeIndicators]), + (ind1, ind2) => + ind1.column === ind2.column && + ind1.name === ind2.name && + (ind1.status !== IndicatorStatus.Applied || + ind2.status !== IndicatorStatus.Applied), + ), + [dashboardIndicators, nativeIndicators], + ); + + const appliedCrossFilterIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.CrossFilterApplied, + ); + const appliedIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Applied, + ); + const unsetIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Unset, + ); + const incompatibleIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Incompatible, + ); + if ( !appliedCrossFilterIndicators.length && !appliedIndicators.length && @@ -69,7 +198,7 @@ const FiltersBadge = ({ isInactive && 'filters-inactive', )} > - + {!isInactive && ( {appliedIndicators.length + appliedCrossFilterIndicators.length} @@ -89,4 +218,4 @@ const FiltersBadge = ({ ); }; -export default FiltersBadge; +export default React.memo(FiltersBadge); diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts index 9d7b84799ba5..34bdfce6d975 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts @@ -16,14 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { TIME_FILTER_MAP } from 'src/explore/constants'; +import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants'; import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; -import { - ChartConfiguration, - NativeFiltersState, -} from 'src/dashboard/reducers/types'; +import { ChartConfiguration, Filters } from 'src/dashboard/reducers/types'; import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types'; -import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; +import { + ensureIsArray, + FeatureFlag, + FilterState, + isFeatureEnabled, +} from '@superset-ui/core'; import { Layout } from '../../types'; import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils'; @@ -53,6 +55,16 @@ type Filter = { datasourceId: string; }; +const extractLabel = (filter?: FilterState): string | null => { + if (filter?.label) { + return filter.label; + } + if (filter?.value) { + return ensureIsArray(filter?.value).join(', '); + } + return null; +}; + const selectIndicatorValue = ( columnKey: string, filter: Filter, @@ -63,7 +75,7 @@ const selectIndicatorValue = ( if ( values == null || - (filter.isDateFilter && values === 'No filter') || + (filter.isDateFilter && values === NO_TIME_RANGE) || arrValues.length === 0 ) { return []; @@ -147,12 +159,8 @@ export const selectIndicatorsForChart = ( chartId: number, filters: { [key: number]: Filter }, datasources: { [key: string]: Datasource }, - charts: any, + chart: any, ): Indicator[] => { - const chart = charts[chartId]; - // no indicators if chart is loading - if (chart.chartStatus === 'loading') return []; - // for now we only need to know which columns are compatible/incompatible, // so grab the columns from the applied/rejected filters const appliedColumns = getAppliedColumns(chart); @@ -178,29 +186,27 @@ export const selectIndicatorsForChart = ( }; export const selectNativeIndicatorsForChart = ( - nativeFilters: NativeFiltersState, + nativeFilters: Filters, dataMask: DataMaskStateWithId, chartId: number, - charts: any, + chart: any, dashboardLayout: Layout, chartConfiguration: ChartConfiguration = {}, ): Indicator[] => { - const chart = charts[chartId]; - const appliedColumns = getAppliedColumns(chart); const rejectedColumns = getRejectedColumns(chart); const getStatus = ({ - value, + label, column, type = DataMaskType.NativeFilters, }: { - value: any; + label: string | null; column?: string; type?: DataMaskType; }): IndicatorStatus => { // a filter is only considered unset if it's value is null - const hasValue = value !== null; + const hasValue = label !== null; if (type === DataMaskType.CrossFilters && hasValue) { return IndicatorStatus.CrossFilterApplied; } @@ -218,29 +224,27 @@ export const selectNativeIndicatorsForChart = ( let nativeFilterIndicators: any = []; if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) { - nativeFilterIndicators = Object.values(nativeFilters.filters) - .filter(nativeFilter => - getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( - layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId, - ), - ) - .map(nativeFilter => { - const column = nativeFilter.targets[0]?.column?.name; - let value = - dataMask[nativeFilter.id]?.filterState?.label ?? - dataMask[nativeFilter.id]?.filterState?.value ?? - null; - if (!Array.isArray(value) && value !== null) { - value = [value]; - } - return { - column, - name: nativeFilter.name, - path: [nativeFilter.id], - status: getStatus({ value, column }), - value, - }; - }); + nativeFilterIndicators = + nativeFilters && + Object.values(nativeFilters) + .filter(nativeFilter => + getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( + layoutItem => + dashboardLayout[layoutItem]?.meta?.chartId === chartId, + ), + ) + .map(nativeFilter => { + const column = nativeFilter.targets[0]?.column?.name; + const filterState = dataMask[nativeFilter.id]?.filterState; + const label = extractLabel(filterState); + return { + column, + name: nativeFilter.name, + path: [nativeFilter.id], + status: getStatus({ label, column }), + value: label, + }; + }); } let crossFilterIndicators: any = []; @@ -255,23 +259,26 @@ export const selectNativeIndicatorsForChart = ( ), ) .map(chartConfig => { - let value = - dataMask[chartConfig.id]?.filterState?.label ?? - dataMask[chartConfig.id]?.filterState?.value ?? - null; - if (!Array.isArray(value) && value !== null) { - value = [value]; - } + const filterState = dataMask[chartConfig.id]?.filterState; + const label = extractLabel(filterState); + const filtersState = filterState?.filters; + const column = filtersState && Object.keys(filtersState)[0]; + + const dashboardLayoutItem = Object.values(dashboardLayout).find( + layoutItem => layoutItem?.meta?.chartId === chartConfig.id, + ); return { - name: Object.values(dashboardLayout).find( - layoutItem => layoutItem?.meta?.chartId === chartConfig.id, - )?.meta?.sliceName as string, - path: [`${chartConfig.id}`], + column, + name: dashboardLayoutItem?.meta?.sliceName as string, + path: [ + ...(dashboardLayoutItem?.parents ?? []), + dashboardLayoutItem?.id, + ], status: getStatus({ - value, + label, type: DataMaskType.CrossFilters, }), - value, + value: label, }; }) .filter(filter => filter.status === IndicatorStatus.CrossFilterApplied); diff --git a/superset-frontend/src/dashboard/components/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/Header/Header.test.tsx index d5393b227559..8a9ecdb5be46 100644 --- a/superset-frontend/src/dashboard/components/Header/Header.test.tsx +++ b/superset-frontend/src/dashboard/components/Header/Header.test.tsx @@ -19,7 +19,12 @@ import React from 'react'; import { render, screen, fireEvent } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; +import sinon from 'sinon'; import fetchMock from 'fetch-mock'; +import * as actions from 'src/reports/actions/reports'; +import * as featureFlags from 'src/featureFlags'; +import { ReportObject } from 'src/components/ReportModal'; +import mockState from 'spec/fixtures/mockStateWithoutUser'; import { HeaderProps } from './types'; import Header from '.'; @@ -32,12 +37,24 @@ const createProps = () => ({ dash_edit_perm: false, dash_save_perm: false, dash_share_perm: false, - userId: 1, + userId: '1', metadata: {}, common: { conf: {}, }, }, + user: { + createdOn: '2021-04-27T18:12:38.952304', + email: 'admin@test.com', + firstName: 'admin', + isActive: true, + lastName: 'admin', + permissions: {}, + roles: { Admin: [['menu_access', 'Manage']] }, + userId: 1, + username: 'admin', + }, + reports: {}, dashboardTitle: 'Dashboard Title', charts: {}, layout: {}, @@ -53,6 +70,8 @@ const createProps = () => ({ onChange: jest.fn(), fetchFaveStar: jest.fn(), fetchCharts: jest.fn(), + onRefresh: jest.fn(), + fetchUISpecificReport: jest.fn(), saveFaveStar: jest.fn(), savePublished: jest.fn(), isPublished: false, @@ -94,7 +113,10 @@ const redoProps = { redoLength: 1, }; +const REPORT_ENDPOINT = 'glob:*/api/v1/report*'; + fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {}); +fetchMock.get(REPORT_ENDPOINT, {}); function setup(props: HeaderProps) { return ( @@ -248,6 +270,21 @@ test('should render the selected fave icon', () => { ).toBeInTheDocument(); }); +test('should NOT render the fave icon on anonymous user', () => { + const mockedProps = createProps(); + const anonymousUserProps = { + ...mockedProps, + user: undefined, + }; + render(setup(anonymousUserProps)); + expect(() => + screen.getByRole('img', { name: 'favorite-unselected' }), + ).toThrowError('Unable to find'); + expect(() => + screen.getByRole('img', { name: 'favorite-selected' }), + ).toThrowError('Unable to find'); +}); + test('should fave', async () => { const mockedProps = createProps(); render(setup(mockedProps)); @@ -284,5 +321,146 @@ test('should refresh the charts', async () => { render(setup(mockedProps)); await openActionsDropdown(); userEvent.click(screen.getByText('Refresh dashboard')); - expect(mockedProps.fetchCharts).toHaveBeenCalledTimes(1); + expect(mockedProps.onRefresh).toHaveBeenCalledTimes(1); +}); + +describe('Email Report Modal', () => { + let isFeatureEnabledMock: any; + let dispatch: any; + + beforeEach(async () => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation(() => true); + dispatch = sinon.spy(); + }); + + afterAll(() => { + isFeatureEnabledMock.mockRestore(); + }); + + it('creates a new email report', async () => { + // ---------- Render/value setup ---------- + const mockedProps = createProps(); + render(setup(mockedProps), { useRedux: true }); + + const reportValues = { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: mockedProps.dashboardInfo.id, + name: 'Weekly Report', + owners: [mockedProps.user.userId], + recipients: [ + { + recipient_config_json: { + target: mockedProps.user.email, + }, + type: 'Email', + }, + ], + type: 'Report', + }; + // This is needed to structure the reportValues to match the fetchMock return + const stringyReportValues = `{"active":true,"creation_method":"dashboards","crontab":"0 12 * * 1","dashboard":${mockedProps.dashboardInfo.id},"name":"Weekly Report","owners":[${mockedProps.user.userId}],"recipients":[{"recipient_config_json":{"target":"${mockedProps.user.email}"},"type":"Email"}],"type":"Report"}`; + // Watch for report POST + fetchMock.post(REPORT_ENDPOINT, reportValues); + + screen.logTestingPlaygroundURL(); + // ---------- Begin tests ---------- + // Click calendar icon to open email report modal + const emailReportModalButton = screen.getByRole('button', { + name: /schedule email report/i, + }); + userEvent.click(emailReportModalButton); + + // Click "Add" button to create a new email report + const addButton = screen.getByRole('button', { name: /add/i }); + userEvent.click(addButton); + + // Mock addReport from Redux + const makeRequest = () => { + const request = actions.addReport(reportValues as ReportObject); + return request(dispatch); + }; + + return makeRequest().then(() => { + // 🐞 ----- There are 2 POST calls at this point ----- 🐞 + + // addReport's mocked POST return should match the mocked values + expect(fetchMock.lastOptions()?.body).toEqual(stringyReportValues); + // Dispatch should be called once for addReport + expect(dispatch.callCount).toBe(2); + const reportCalls = fetchMock.calls(REPORT_ENDPOINT); + expect(reportCalls).toHaveLength(2); + }); + }); + + it('edits an existing email report', async () => { + // TODO (lyndsiWilliams): This currently does not work, see TODOs below + // The modal does appear with the edit title, but the PUT call is not registering + + // ---------- Render/value setup ---------- + const mockedProps = createProps(); + const editedReportValues = { + active: true, + creation_method: 'dashboards', + crontab: '0 12 * * 1', + dashboard: mockedProps.dashboardInfo.id, + name: 'Weekly Report edit', + owners: [mockedProps.user.userId], + recipients: [ + { + recipient_config_json: { + target: mockedProps.user.email, + }, + type: 'Email', + }, + ], + type: 'Report', + }; + + // getMockStore({ reports: reportValues }); + render(setup(mockedProps), { + useRedux: true, + initialState: mockState, + }); + // TODO (lyndsiWilliams): currently fetchMock detects this PUT + // address as 'glob:*/api/v1/report/undefined', is not detected + // on fetchMock.calls() + fetchMock.put(`glob:*/api/v1/report*`, editedReportValues); + + // Mock fetchUISpecificReport from Redux + // const makeFetchRequest = () => { + // const request = actions.fetchUISpecificReport( + // mockedProps.user.userId, + // 'dashboard_id', + // 'dashboards', + // mockedProps.dashboardInfo.id, + // ); + // return request(dispatch); + // }; + + // makeFetchRequest(); + + dispatch(actions.setReport(editedReportValues)); + + // ---------- Begin tests ---------- + // Click calendar icon to open email report modal + const emailReportModalButton = screen.getByRole('button', { + name: /schedule email report/i, + }); + userEvent.click(emailReportModalButton); + + const nameTextbox = screen.getByTestId('report-name-test'); + userEvent.type(nameTextbox, ' edit'); + + const saveButton = screen.getByRole('button', { name: /save/i }); + userEvent.click(saveButton); + + // TODO (lyndsiWilliams): There should be a report in state at this porint, + // which would render the HeaderReportActionsDropDown under the calendar icon + // BLOCKER: I cannot get report to populate, as its data is handled through redux + expect.anything(); + }); }); diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx index a5f7bab11c83..1b1cb30424e8 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx @@ -32,7 +32,7 @@ const createProps = () => ({ id: 1, dash_edit_perm: true, dash_save_perm: true, - userId: 1, + userId: '1', metadata: {}, common: { conf: {}, @@ -112,7 +112,7 @@ test('should render the menu items', async () => { expect(screen.getByText('Refresh dashboard')).toBeInTheDocument(); expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument(); expect(screen.getByText('Download as image')).toBeInTheDocument(); - expect(screen.getByText('Toggle fullscreen')).toBeInTheDocument(); + expect(screen.getByText('Enter fullscreen')).toBeInTheDocument(); }); test('should render the menu items in edit mode', async () => { diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx index 4ed5fd623d15..0e276291a0d4 100644 --- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import { styled, SupersetClient, t } from '@superset-ui/core'; import { Menu, NoAnimationDropdown } from 'src/common/components'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { URL_PARAMS } from 'src/constants'; import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems'; import CssEditor from 'src/dashboard/components/CssEditor'; @@ -42,6 +42,7 @@ const propTypes = { dashboardInfo: PropTypes.object.isRequired, dashboardId: PropTypes.number.isRequired, dashboardTitle: PropTypes.string.isRequired, + dataMask: PropTypes.object.isRequired, customCss: PropTypes.string.isRequired, colorNamespace: PropTypes.string, colorScheme: PropTypes.string, @@ -87,6 +88,9 @@ const MENU_KEYS = { const DropdownButton = styled.div` margin-left: ${({ theme }) => theme.gridUnit * 2.5}px; + span { + color: ${({ theme }) => theme.colors.grayscale.base}; + } `; const SCREENSHOT_NODE_SELECTOR = '.dashboard'; @@ -158,18 +162,21 @@ class HeaderActionsDropdown extends React.PureComponent { downloadAsImage( SCREENSHOT_NODE_SELECTOR, this.props.dashboardTitle, + {}, + true, )(domEvent).then(() => { menu.style.visibility = 'visible'; }); break; } case MENU_KEYS.TOGGLE_FULLSCREEN: { - const url = getDashboardUrl( - window.location.pathname, - getActiveFilters(), - window.location.hash, - !getUrlParam(URL_PARAMS.standalone), - ); + const url = getDashboardUrl({ + dataMask: this.props.dataMask, + pathname: window.location.pathname, + filters: getActiveFilters(), + hash: window.location.hash, + standalone: !getUrlParam(URL_PARAMS.standalone), + }); window.location.replace(url); break; } @@ -183,6 +190,7 @@ class HeaderActionsDropdown extends React.PureComponent { dashboardTitle, dashboardId, dashboardInfo, + dataMask, refreshFrequency, shouldPersistRefreshFrequency, editMode, @@ -206,11 +214,13 @@ class HeaderActionsDropdown extends React.PureComponent { const emailTitle = t('Superset dashboard'); const emailSubject = `${emailTitle} ${dashboardTitle}`; const emailBody = t('Check out this dashboard: '); - const url = getDashboardUrl( - window.location.pathname, - getActiveFilters(), - window.location.hash, - ); + + const url = getDashboardUrl({ + dataMask, + pathname: window.location.pathname, + filters: getActiveFilters(), + hash: window.location.hash, + }); const menu = ( - {t('Toggle fullscreen')} + {getUrlParam(URL_PARAMS.standalone) + ? t('Exit fullscreen') + : t('Enter fullscreen')} )} @@ -321,7 +333,7 @@ class HeaderActionsDropdown extends React.PureComponent { } > - + ); diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx index 6befcbbc789f..9198601ae685 100644 --- a/superset-frontend/src/dashboard/components/Header/index.jsx +++ b/superset-frontend/src/dashboard/components/Header/index.jsx @@ -28,31 +28,39 @@ import { LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, } from 'src/logger/LogUtils'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import Button from 'src/components/Button'; import EditableTitle from 'src/components/EditableTitle'; import FaveStar from 'src/components/FaveStar'; import { safeStringify } from 'src/utils/safeStringify'; import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown'; +import HeaderReportActionsDropdown from 'src/components/ReportModal/HeaderReportActionsDropdown'; import PublishedStatus from 'src/dashboard/components/PublishedStatus'; import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners'; import PropertiesModal from 'src/dashboard/components/PropertiesModal'; +import ReportModal from 'src/components/ReportModal'; import { chartPropShape } from 'src/dashboard/util/propShapes'; +import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes'; import { UNDO_LIMIT, SAVE_TYPE_OVERWRITE, DASHBOARD_POSITION_DATA_LIMIT, } from 'src/dashboard/util/constants'; -import setPeriodicRunner from 'src/dashboard/util/setPeriodicRunner'; +import setPeriodicRunner, { + stopPeriodicRender, +} from 'src/dashboard/util/setPeriodicRunner'; import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal'; const propTypes = { addSuccessToast: PropTypes.func.isRequired, addDangerToast: PropTypes.func.isRequired, addWarningToast: PropTypes.func.isRequired, + user: UserWithPermissionsAndRoles, dashboardInfo: PropTypes.object.isRequired, dashboardTitle: PropTypes.string.isRequired, + dataMask: PropTypes.object.isRequired, charts: PropTypes.objectOf(chartPropShape).isRequired, layout: PropTypes.object.isRequired, expandedSlices: PropTypes.object.isRequired, @@ -67,6 +75,7 @@ const propTypes = { onChange: PropTypes.func.isRequired, fetchFaveStar: PropTypes.func.isRequired, fetchCharts: PropTypes.func.isRequired, + fetchUISpecificReport: PropTypes.func.isRequired, saveFaveStar: PropTypes.func.isRequired, savePublished: PropTypes.func.isRequired, updateDashboardTitle: PropTypes.func.isRequired, @@ -80,6 +89,7 @@ const propTypes = { lastModifiedTime: PropTypes.number.isRequired, // redux + onRefresh: PropTypes.func.isRequired, onUndo: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired, undoLength: PropTypes.number.isRequired, @@ -108,6 +118,9 @@ const StyledDashboardHeader = styled.div` padding: 0 ${({ theme }) => theme.gridUnit * 6}px; border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + .action-button > span { + color: ${({ theme }) => theme.colors.grayscale.base}; + } button, .fave-unfave-icon { margin-left: ${({ theme }) => theme.gridUnit * 2}px; @@ -118,13 +131,17 @@ const StyledDashboardHeader = styled.div` flex-wrap: nowrap; .action-button { font-size: ${({ theme }) => theme.typography.sizes.xl}px; + margin-left: ${({ theme }) => theme.gridUnit * 2.5}px; } } `; class Header extends React.PureComponent { static discardChanges() { - window.location.reload(); + const url = new URL(window.location.href); + + url.searchParams.delete('edit'); + window.location.assign(url); } constructor(props) { @@ -133,6 +150,7 @@ class Header extends React.PureComponent { didNotifyMaxUndoHistoryToast: false, emphasizeUndo: false, showingPropertiesModal: false, + showingReportModal: false, }; this.handleChangeText = this.handleChangeText.bind(this); @@ -144,14 +162,28 @@ class Header extends React.PureComponent { this.overwriteDashboard = this.overwriteDashboard.bind(this); this.showPropertiesModal = this.showPropertiesModal.bind(this); this.hidePropertiesModal = this.hidePropertiesModal.bind(this); + this.showReportModal = this.showReportModal.bind(this); + this.hideReportModal = this.hideReportModal.bind(this); + this.renderReportModal = this.renderReportModal.bind(this); } componentDidMount() { - const { refreshFrequency } = this.props; + const { refreshFrequency, user, dashboardInfo } = this.props; this.startPeriodicRender(refreshFrequency * 1000); + if (this.canAddReports()) { + // this is in case there is an anonymous user. + this.props.fetchUISpecificReport( + user.userId, + 'dashboard_id', + 'dashboards', + dashboardInfo.id, + user.email, + ); + } } UNSAFE_componentWillReceiveProps(nextProps) { + const { user } = this.props; if ( UNDO_LIMIT - nextProps.undoLength <= 0 && !this.state.didNotifyMaxUndoHistoryToast @@ -165,9 +197,24 @@ class Header extends React.PureComponent { ) { this.props.setMaxUndoHistoryExceeded(); } + if ( + this.canAddReports() && + nextProps.dashboardInfo.id !== this.props.dashboardInfo.id + ) { + // this is in case there is an anonymous user. + this.props.fetchUISpecificReport( + user.userId, + 'dashboard_id', + 'dashboards', + nextProps.dashboardInfo.id, + user.email, + ); + } } componentWillUnmount() { + stopPeriodicRender(this.refreshTimer); + this.props.setRefreshFrequency(0); clearTimeout(this.ctrlYTimeout); clearTimeout(this.ctrlZTimeout); } @@ -208,8 +255,7 @@ class Header extends React.PureComponent { interval: 0, chartCount: chartList.length, }); - - return this.props.fetchCharts( + return this.props.onRefresh( chartList, true, 0, @@ -326,7 +372,7 @@ class Header extends React.PureComponent { if (positionJSONLength >= limit) { this.props.addDangerToast( t( - 'Your dashboard is too large. Please reduce the size before save it.', + 'Your dashboard is too large. Please reduce its size before saving it.', ), ); } else { @@ -346,6 +392,55 @@ class Header extends React.PureComponent { this.setState({ showingPropertiesModal: false }); } + showReportModal() { + this.setState({ showingReportModal: true }); + } + + hideReportModal() { + this.setState({ showingReportModal: false }); + } + + renderReportModal() { + const attachedReportExists = !!Object.keys(this.props.reports).length; + return attachedReportExists ? ( + + ) : ( + <> + + + + + ); + } + + canAddReports() { + if (!isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) { + return false; + } + const { user } = this.props; + if (!user) { + // this is in the case that there is an anonymous user. + return false; + } + const roles = Object.keys(user.roles || []); + const permissions = roles.map(key => + user.roles[key].filter( + perms => perms[0] === 'menu_access' && perms[1] === 'Manage', + ), + ); + return permissions[0].length > 0; + } + render() { const { dashboardTitle, @@ -353,6 +448,7 @@ class Header extends React.PureComponent { expandedSlices, customCss, colorNamespace, + dataMask, setColorSchemeAndUnsavedChanges, colorScheme, onUndo, @@ -364,6 +460,7 @@ class Header extends React.PureComponent { updateCss, editMode, isPublished, + user, dashboardInfo, hasUnsavedChanges, isLoading, @@ -372,10 +469,10 @@ class Header extends React.PureComponent { setRefreshFrequency, lastModifiedTime, } = this.props; - const userCanEdit = dashboardInfo.dash_edit_perm; const userCanShare = dashboardInfo.dash_share_perm; const userCanSaveAs = dashboardInfo.dash_save_perm; + const shouldShowReport = !editMode && this.canAddReports(); const refreshLimit = dashboardInfo.common.conf.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT; const refreshWarning = @@ -402,7 +499,7 @@ class Header extends React.PureComponent { canEdit={userCanEdit} canSave={userCanSaveAs} /> - {dashboardInfo.userId && ( + {user?.userId && ( - + )} + {shouldShowReport && this.renderReportModal()} {this.state.showingPropertiesModal && ( )} + {this.state.showingReportModal && ( + + )} + ; @@ -65,6 +65,7 @@ export interface HeaderProps { charts: ChartState | {}; colorScheme?: string; customCss: string; + user: Object | undefined; dashboardInfo: DashboardInfo; dashboardTitle: string; setColorSchemeAndUnsavedChanges: () => void; @@ -86,6 +87,7 @@ export interface HeaderProps { lastModifiedTime: number; onUndo: () => void; onRedo: () => void; + onRefresh: () => void; undoLength: number; redoLength: number; setMaxUndoHistoryExceeded: () => void; diff --git a/superset-frontend/src/dashboard/components/IconButton.jsx b/superset-frontend/src/dashboard/components/IconButton.jsx deleted file mode 100644 index 1db166dcb922..000000000000 --- a/superset-frontend/src/dashboard/components/IconButton.jsx +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import PropTypes from 'prop-types'; - -const propTypes = { - onClick: PropTypes.func.isRequired, - className: PropTypes.string, - label: PropTypes.string, -}; - -const defaultProps = { - className: null, - label: null, -}; - -export default class IconButton extends React.PureComponent { - constructor(props) { - super(props); - this.handleClick = this.handleClick.bind(this); - } - - handleClick(event) { - event.preventDefault(); - const { onClick } = this.props; - onClick(event); - } - - render() { - const { className, label } = this.props; - return ( -
- - {label && {label}} -
- ); - } -} - -IconButton.propTypes = propTypes; -IconButton.defaultProps = defaultProps; diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragWrapper/index.tsx b/superset-frontend/src/dashboard/components/IconButton.tsx similarity index 53% rename from superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragWrapper/index.tsx rename to superset-frontend/src/dashboard/components/IconButton.tsx index e12c7f9ed616..1a15456a1eca 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelDragWrapper/index.tsx +++ b/superset-frontend/src/dashboard/components/IconButton.tsx @@ -16,40 +16,40 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ReactNode } from 'react'; -import { useDrag } from 'react-dnd'; +import React, { MouseEventHandler } from 'react'; import { styled } from '@superset-ui/core'; -import { DatasourcePanelDndItem } from '../types'; -const DatasourceItemContainer = styled.div` +interface IconButtonProps { + icon: JSX.Element; + label?: string; + onClick: MouseEventHandler; +} + +const StyledDiv = styled.div` display: flex; align-items: center; - width: 100%; - height: ${({ theme }) => theme.gridUnit * 6}px; - cursor: pointer; - - :hover { - background-color: ${({ theme }) => theme.colors.grayscale.light2}; + color: ${({ theme }) => theme.colors.grayscale.base}; + &:hover { + color: ${({ theme }) => theme.colors.primary.base}; } `; -interface DatasourcePanelDragWrapperProps extends DatasourcePanelDndItem { - children?: ReactNode; -} +const StyledSpan = styled.span` + margin-left: ${({ theme }) => theme.gridUnit * 2}px; +`; -export default function DatasourcePanelDragWrapper( - props: DatasourcePanelDragWrapperProps, -) { - const [, drag] = useDrag({ - item: { - value: props.value, - type: props.type, - }, - }); +const IconButton = ({ icon, label, onClick }: IconButtonProps) => ( + { + e.preventDefault(); + onClick(e); + }} + > + {icon} + {label && {label}} + +); - return ( - - {props.children} - - ); -} +export default IconButton; diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx index e377308f8d51..42b718edadd9 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx @@ -113,8 +113,6 @@ fetchMock.get('http://localhost/api/v1/dashboard/26', { published: false, roles: [], slug: null, - table_names: - '[examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines], [examples].[covid_vaccines]', thumbnail_url: '/api/v1/dashboard/26/thumbnail/b24805e98d90116da8c0974d24f5c533/', url: '/superset/dashboard/26/', @@ -165,7 +163,8 @@ test('should render - FeatureFlag disabled', async () => { expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); expect(screen.getAllByRole('button')).toHaveLength(4); - expect(screen.getAllByRole('textbox')).toHaveLength(3); + expect(screen.getAllByRole('textbox')).toHaveLength(2); + expect(screen.getByRole('combobox')).toBeInTheDocument(); expect(spyColorSchemeControlWrapper).toBeCalledTimes(4); expect(spyColorSchemeControlWrapper).toBeCalledWith( @@ -201,7 +200,8 @@ test('should render - FeatureFlag enabled', async () => { expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); expect(screen.getAllByRole('button')).toHaveLength(4); - expect(screen.getAllByRole('textbox')).toHaveLength(4); + expect(screen.getAllByRole('textbox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(2); expect(spyColorSchemeControlWrapper).toBeCalledTimes(4); expect(spyColorSchemeControlWrapper).toBeCalledWith( @@ -220,9 +220,11 @@ test('should open advance', async () => { await screen.findByTestId('dashboard-edit-properties-form'), ).toBeInTheDocument(); - expect(screen.getAllByRole('textbox')).toHaveLength(4); + expect(screen.getAllByRole('textbox')).toHaveLength(2); + expect(screen.getAllByRole('combobox')).toHaveLength(2); userEvent.click(screen.getByRole('button', { name: 'Advanced' })); - expect(screen.getAllByRole('textbox')).toHaveLength(5); + expect(screen.getAllByRole('textbox')).toHaveLength(3); + expect(screen.getAllByRole('combobox')).toHaveLength(2); }); test('should close modal', async () => { diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx index cdc1b956748f..6ca383063431 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.jsx @@ -22,7 +22,7 @@ import { Row, Col, Input } from 'src/common/components'; import { Form, FormItem } from 'src/components/Form'; import jsonStringify from 'json-stringify-pretty-compact'; import Button from 'src/components/Button'; -import { AsyncSelect } from 'src/components/Select'; +import { Select } from 'src/components'; import rison from 'rison'; import { styled, @@ -38,7 +38,6 @@ import { JsonEditor } from 'src/components/AsyncAceEditor'; import ColorSchemeControlWrapper from 'src/dashboard/components/ColorSchemeControlWrapper'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import withToasts from 'src/messageToasts/enhancers/withToasts'; -import 'src/dashboard/stylesheets/buttons.less'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; const StyledJsonEditor = styled(JsonEditor)` @@ -92,11 +91,13 @@ const loadAccessOptions = accessType => (input = '') => { return SupersetClient.get({ endpoint: `/api/v1/dashboard/related/${accessType}?q=${query}`, }).then( - response => - response.json.result.map(item => ({ + response => ({ + data: response.json.result.map(item => ({ value: item.value, label: item.text, })), + totalCount: response.json.count, + }), badResponse => { handleErrorResponse(badResponse); return []; @@ -104,6 +105,9 @@ const loadAccessOptions = accessType => (input = '') => { ); }; +const loadOwners = loadAccessOptions('owners'); +const loadRoles = loadAccessOptions('roles'); + class PropertiesModal extends React.PureComponent { constructor(props) { super(props); @@ -327,16 +331,15 @@ class PropertiesModal extends React.PureComponent {

{t('Access')}

-

{t( @@ -369,16 +372,15 @@ class PropertiesModal extends React.PureComponent { -

{t( @@ -389,16 +391,15 @@ class PropertiesModal extends React.PureComponent { -

{t( diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx index aa97ff872f7c..731572b8d751 100644 --- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx +++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { RefObject } from 'react'; -import Select from 'src/components/Select'; +import { Select } from 'src/components'; import { t, styled } from '@superset-ui/core'; import Alert from 'src/components/Alert'; import Button from 'src/components/Button'; @@ -36,7 +36,7 @@ export const options = [ [21600, t('6 hours')], [43200, t('12 hours')], [86400, t('24 hours')], -].map(o => ({ value: o[0], label: o[1] })); +].map(o => ({ value: o[0] as number, label: o[1] })); const StyledModalTrigger = styled(ModalTrigger)` .ant-modal-body { @@ -95,10 +95,9 @@ class RefreshIntervalModal extends React.PureComponent< this.modalRef.current?.close(); } - handleFrequencyChange(opt: Record) { - const value = opt ? opt.value : options[0].value; + handleFrequencyChange(value: number) { this.setState({ - refreshFrequency: value, + refreshFrequency: value || options[0].value, }); } @@ -117,10 +116,10 @@ class RefreshIntervalModal extends React.PureComponent<

{t('Refresh frequency')} + ); +}; + +const MemoizedSelect = (props: DatasetSelectProps) => + // eslint-disable-next-line react-hooks/exhaustive-deps + useMemo(() => , []); + +export default MemoizedSelect; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx index 524ab83cd79e..e876006ee9cc 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import { Behavior, SetDataMaskHook, SuperChart, AppSection, + t, } from '@superset-ui/core'; import { FormInstance } from 'antd/lib/form'; import Loading from 'src/components/Loading'; @@ -29,6 +30,7 @@ import { NativeFiltersForm } from '../types'; import { getFormData } from '../../utils'; type DefaultValueProps = { + hasDefaultValue: boolean; filterId: string; setDataMask: SetDataMaskHook; hasDataset: boolean; @@ -38,6 +40,7 @@ type DefaultValueProps = { }; const DefaultValue: FC = ({ + hasDefaultValue, filterId, hasDataset, form, @@ -45,24 +48,18 @@ const DefaultValue: FC = ({ formData, enableNoResults, }) => { - const [loading, setLoading] = useState(hasDataset); const formFilter = (form.getFieldValue('filters') || {})[filterId]; const queriesData = formFilter?.defaultValueQueriesData; - - useEffect(() => { - if (!hasDataset || queriesData !== null) { - setLoading(false); - } else { - setLoading(true); - } - }, [hasDataset, queriesData]); - + const loading = hasDataset && queriesData === null; + const value = formFilter.defaultDataMask?.filterState.value; + const isMissingRequiredValue = + hasDefaultValue && (value === null || value === undefined); return loading ? ( ) : ( = ({ chartType={formFilter?.filterType} hooks={{ setDataMask }} enableNoResults={enableNoResults} - filterState={formFilter.defaultDataMask?.filterState} + filterState={{ + ...formFilter.defaultDataMask?.filterState, + validateMessage: isMissingRequiredValue && t('Value is required'), + validateStatus: isMissingRequiredValue && 'error', + }} /> ); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.tsx index e8ffde2df1e0..5bec228941fb 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.tsx @@ -17,23 +17,25 @@ * under the License. */ -import React, { FC } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import { t, styled } from '@superset-ui/core'; import { Radio } from 'src/components/Radio'; import { Form, Typography } from 'src/common/components'; +import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate/useComponentDidUpdate'; import { Scope } from '../../../types'; -import { Scoping } from './types'; +import { ScopingType } from './types'; import ScopingTree from './ScopingTree'; import { getDefaultScopeValue, isScopingAll } from './utils'; type FilterScopeProps = { pathToFormValue?: string[]; updateFormValues: (values: any) => void; - formScope?: Scope; + formFilterScope?: Scope; forceUpdate: Function; - scope?: Scope; - formScoping?: Scoping; + filterScope?: Scope; + formScopingType?: ScopingType; chartId?: number; + initiallyExcludedCharts?: number[]; }; const Wrapper = styled.div` @@ -50,59 +52,98 @@ const CleanFormItem = styled(Form.Item)` const FilterScope: FC = ({ pathToFormValue = [], - formScoping, - formScope, + formScopingType, + formFilterScope, forceUpdate, - scope, + filterScope, updateFormValues, chartId, + initiallyExcludedCharts, }) => { - const initialScope = scope || getDefaultScopeValue(chartId); - const initialScoping = isScopingAll(initialScope, chartId) - ? Scoping.all - : Scoping.specific; + const [initialFilterScope] = useState( + filterScope || getDefaultScopeValue(chartId, initiallyExcludedCharts), + ); + const [initialScopingType] = useState( + isScopingAll(initialFilterScope, chartId) + ? ScopingType.all + : ScopingType.specific, + ); + const [hasScopeBeenModified, setHasScopeBeenModified] = useState( + !!filterScope, + ); + + const onUpdateFormValues = useCallback( + (formValues: any) => { + updateFormValues(formValues); + setHasScopeBeenModified(true); + }, + [updateFormValues], + ); + + const updateScopes = useCallback(() => { + if (filterScope || hasScopeBeenModified) { + return; + } + + const newScope = getDefaultScopeValue(chartId, initiallyExcludedCharts); + updateFormValues({ + scope: newScope, + scoping: isScopingAll(newScope, chartId) + ? ScopingType.all + : ScopingType.specific, + }); + }, [ + chartId, + filterScope, + hasScopeBeenModified, + initiallyExcludedCharts, + updateFormValues, + ]); + useComponentDidUpdate(updateScopes); return ( { - if (value === Scoping.all) { + if (value === ScopingType.all) { const scope = getDefaultScopeValue(chartId); updateFormValues({ scope, }); } + setHasScopeBeenModified(true); forceUpdate(); }} > - {t('Apply to all panels')} - + {t('Apply to all panels')} + {t('Apply to specific panels')} - {(formScoping ?? initialScoping) === Scoping.specific + {(formScopingType ?? initialScopingType) === ScopingType.specific ? t('Only selected panels will be affected by this filter') : t('All panels with this column will be affected by this filter')} - {(formScoping ?? initialScoping) === Scoping.specific && ( + {(formScopingType ?? initialScopingType) === ScopingType.specific && ( )} ); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/ScopingTree.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/ScopingTree.tsx index d9dcf519d552..af140f862c86 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/ScopingTree.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/ScopingTree.tsx @@ -20,6 +20,8 @@ import React, { FC, useMemo, useState } from 'react'; import { Tree } from 'src/common/components'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; +import { Tooltip } from 'src/components/Tooltip'; +import Icons from 'src/components/Icons'; import { useFilterScopeTree } from './state'; import { findFilterScope, getTreeCheckedItems } from './utils'; import { Scope } from '../../../types'; @@ -30,6 +32,26 @@ type ScopingTreeProps = { formScope?: Scope; initialScope: Scope; chartId?: number; + initiallyExcludedCharts?: number[]; +}; + +const buildTreeLeafTitle = ( + label: string, + hasTooltip: boolean, + tooltipTitle?: string, +) => { + let title = {label}; + if (hasTooltip) { + title = ( + <> + {title}  + + + + + ); + } + return title; }; const ScopingTree: FC = ({ @@ -38,12 +60,17 @@ const ScopingTree: FC = ({ forceUpdate, updateFormValues, chartId, + initiallyExcludedCharts = [], }) => { const [expandedKeys, setExpandedKeys] = useState([ DASHBOARD_ROOT_ID, ]); - const { treeData, layout } = useFilterScopeTree(chartId); + const { treeData, layout } = useFilterScopeTree( + chartId, + initiallyExcludedCharts, + buildTreeLeafTitle, + ); const [autoExpandParent, setAutoExpandParent] = useState(true); const handleExpand = (expandedKeys: string[]) => { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/state.ts index 8926c60be02c..f9b94a39856e 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/state.ts @@ -25,12 +25,14 @@ import { CHART_TYPE, DASHBOARD_ROOT_TYPE, } from 'src/dashboard/util/componentTypes'; -import { TreeItem } from './types'; +import { BuildTreeLeafTitle, TreeItem } from './types'; import { buildTree } from './utils'; // eslint-disable-next-line import/prefer-default-export export function useFilterScopeTree( currentChartId?: number, + initiallyExcludedCharts: number[] = [], + buildTreeLeafTitle: BuildTreeLeafTitle = label => label, ): { treeData: [TreeItem]; layout: Layout; @@ -61,8 +63,16 @@ export function useFilterScopeTree( ); useMemo(() => { - buildTree(layout[DASHBOARD_ROOT_ID], tree, layout, charts, validNodes); - }, [charts, layout, tree]); + buildTree( + layout[DASHBOARD_ROOT_ID], + tree, + layout, + charts, + validNodes, + initiallyExcludedCharts, + buildTreeLeafTitle, + ); + }, [layout, tree, charts, initiallyExcludedCharts, buildTreeLeafTitle]); return { treeData: [tree], layout }; } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/types.ts index cb804bf6d60c..8c9d2a5f3cf5 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/types.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/types.ts @@ -17,7 +17,9 @@ * under the License. */ -export enum Scoping { +import { ReactNode } from 'react'; + +export enum ScopingType { all, specific, } @@ -26,5 +28,11 @@ export enum Scoping { export type TreeItem = { children: TreeItem[]; key: string; - title: string; + title: ReactNode; }; + +export type BuildTreeLeafTitle = ( + label: string, + hasTooltip: boolean, + tooltipTitle?: string, +) => ReactNode; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils.ts index 8c74aa7511c1..555b87ff7779 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils.ts @@ -23,7 +23,8 @@ import { TAB_TYPE, } from 'src/dashboard/util/componentTypes'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; -import { TreeItem } from './types'; +import { t } from '@superset-ui/core'; +import { BuildTreeLeafTitle, TreeItem } from './types'; import { Scope } from '../../../types'; export const isShowTypeInTree = ({ type, meta }: LayoutItem, charts?: Charts) => @@ -36,6 +37,8 @@ export const buildTree = ( layout: Layout, charts: Charts, validNodes: string[], + initiallyExcludedCharts: number[], + buildTreeLeafTitle: BuildTreeLeafTitle, ) => { let itemToPass: TreeItem = treeItem; if ( @@ -43,16 +46,36 @@ export const buildTree = ( node.type !== DASHBOARD_ROOT_TYPE && validNodes.includes(node.id) ) { + const title = buildTreeLeafTitle( + node.meta.sliceNameOverride || + node.meta.sliceName || + node.meta.text || + node.meta.defaultText || + node.id.toString(), + initiallyExcludedCharts.includes(node.meta?.chartId), + t( + "This chart might be incompatible with the filter (datasets don't match)", + ), + ); + const currentTreeItem = { key: node.id, - title: node.meta.sliceName || node.meta.text || node.id.toString(), + title, children: [], }; treeItem.children.push(currentTreeItem); itemToPass = currentTreeItem; } node.children.forEach(child => - buildTree(layout[child], itemToPass, layout, charts, validNodes), + buildTree( + layout[child], + itemToPass, + layout, + charts, + validNodes, + initiallyExcludedCharts, + buildTreeLeafTitle, + ), ); }; @@ -144,9 +167,14 @@ export const findFilterScope = ( }; }; -export const getDefaultScopeValue = (chartId?: number): Scope => ({ +export const getDefaultScopeValue = ( + chartId?: number, + initiallyExcludedCharts: number[] = [], +): Scope => ({ rootPath: [DASHBOARD_ROOT_ID], - excluded: chartId ? [chartId] : [], + excluded: chartId + ? [chartId, ...initiallyExcludedCharts] + : initiallyExcludedCharts, }); export const isScopingAll = (scope: Scope, chartId?: number) => diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index 296ee8e6ad25..e46d581a16a6 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -20,51 +20,65 @@ import { AdhocFilter, Behavior, ChartDataResponseResult, + Column, + GenericDataType, getChartMetadataRegistry, JsonResponse, styled, SupersetApiError, t, + SupersetClient, } from '@superset-ui/core'; import { ColumnMeta, - DatasourceMeta, + InfoTooltipWithTrigger, Metric, } from '@superset-ui/chart-controls'; import { FormInstance } from 'antd/lib/form'; import React, { + forwardRef, useCallback, useEffect, - useState, - useMemo, - forwardRef, useImperativeHandle, + useMemo, + useState, } from 'react'; import { useSelector } from 'react-redux'; +import { isEqual } from 'lodash'; import { FormItem } from 'src/components/Form'; -import { Checkbox, Input } from 'src/common/components'; -import { Select } from 'src/components/Select'; -import SupersetResourceSelect, { - cachedSupersetGet, -} from 'src/components/SupersetResourceSelect'; +import { Input } from 'src/common/components'; +import { Select } from 'src/components'; +import { cacheWrapper } from 'src/utils/cacheWrapper'; import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl'; import { addDangerToast } from 'src/messageToasts/actions'; import { ClientErrorObject } from 'src/utils/getClientErrorObject'; -import SelectControl from 'src/explore/components/controls/SelectControl'; import Collapse from 'src/components/Collapse'; import { getChartDataRequest } from 'src/chart/chartAction'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { waitForAsyncData } from 'src/middleware/asyncEvent'; import Tabs from 'src/components/Tabs'; +import Icons from 'src/components/Icons'; +import { Tooltip } from 'src/components/Tooltip'; +import { Radio } from 'src/components/Radio'; +import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert'; +import { + Chart, + ChartsState, + DatasourcesState, + RootState, +} from 'src/dashboard/types'; +import Loading from 'src/components/Loading'; import { ColumnSelect } from './ColumnSelect'; import { NativeFiltersForm } from '../types'; import { - datasetToSelectOption, + FILTER_SUPPORTED_TYPES, + hasTemporalColumns, setNativeFilterFieldValues, useForceUpdate, + mostUsedDataset, } from './utils'; -import { useBackendFormUpdate } from './state'; +import { useBackendFormUpdate, useDefaultValue } from './state'; import { getFormData } from '../../utils'; import { Filter } from '../../types'; import getControlItemsMap from './getControlItemsMap'; @@ -76,6 +90,7 @@ import { CASCADING_FILTERS, getFiltersConfigModalTestId, } from '../FiltersConfigModal'; +import DatasetSelect from './DatasetSelect'; const { TabPane } = Tabs; @@ -97,7 +112,7 @@ export const StyledFormItem = styled(FormItem)` margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; & .ant-form-item-label { - padding-bottom: 0px; + padding-bottom: 0; } & .ant-form-item-control-input { @@ -106,12 +121,12 @@ export const StyledFormItem = styled(FormItem)` `; export const StyledRowFormItem = styled(FormItem)` - margin-bottom: 0px; - padding-bottom: 0px; + margin-bottom: 0; + padding-bottom: 0; min-width: 50%; & .ant-form-item-label { - padding-bottom: 0px; + padding-bottom: 0; } .ant-form-item-control-input-content > div > div { @@ -123,6 +138,30 @@ export const StyledRowFormItem = styled(FormItem)` } `; +export const StyledRowSubFormItem = styled(FormItem)` + min-width: 50%; + + & .ant-form-item-label { + padding-bottom: 0; + } + + .ant-form-item { + margin-bottom: 0; + } + + .ant-form-item-control-input-content > div > div { + height: auto; + } + + .ant-form-item-extra { + display: none; + } + + & .ant-form-item-control-input { + height: auto; + } +`; + export const StyledLabel = styled.span` color: ${({ theme }) => theme.colors.grayscale.base}; font-size: ${({ theme }) => theme.typography.sizes.s}px; @@ -133,22 +172,33 @@ const CleanFormItem = styled(FormItem)` margin-bottom: 0; `; +const DefaultValueContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; + +const RefreshIcon = styled(Icons.Refresh)` + margin-left: ${({ theme }) => theme.gridUnit * 2}px; + color: ${({ theme }) => theme.colors.primary.base}; +`; + const StyledCollapse = styled(Collapse)` margin-left: ${({ theme }) => theme.gridUnit * -4 - 1}px; margin-right: ${({ theme }) => theme.gridUnit * -4}px; border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - border-radius: 0px; + border-radius: 0; .ant-collapse-header { border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; margin-top: -1px; - border-radius: 0px; + border-radius: 0; } .ant-collapse-content { - border: 0px; + border: 0; } .ant-collapse-content-box { @@ -156,32 +206,34 @@ const StyledCollapse = styled(Collapse)` } &.ant-collapse > .ant-collapse-item { - border: 0px; - border-radius: 0px; + border: 0; + border-radius: 0; } `; const StyledTabs = styled(Tabs)` .ant-tabs-nav { position: sticky; - top: 0px; + margin-left: ${({ theme }) => theme.gridUnit * -4}px; + margin-right: ${({ theme }) => theme.gridUnit * -4}px; + top: 0; background: white; z-index: 1; } .ant-tabs-nav-list { - padding: 0px; + padding: 0; } .ant-form-item-label { - padding-bottom: 0px; + padding-bottom: 0; } `; const StyledAsterisk = styled.span` color: ${({ theme }) => theme.colors.error.base}; - font-family: SimSun, sans-serif; - margin-right: ${({ theme }) => theme.gridUnit - 1}px; + font-size: ${({ theme }) => theme.typography.sizes.s}px; + margin-left: ${({ theme }) => theme.gridUnit - 1}px; &:before { content: '*'; } @@ -218,13 +270,6 @@ export interface FiltersConfigFormProps { parentFilters: { id: string; title: string }[]; } -// TODO: Need to do with it something -const FILTERS_WITHOUT_COLUMN = [ - 'filter_timegrain', - 'filter_timecolumn', - 'filter_groupby', -]; - const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range']; const BASIC_CONTROL_ITEMS = ['enableEmptyFilter', 'multiSelect']; @@ -239,6 +284,14 @@ const FILTER_TYPE_NAME_MAPPING = { [t('Group By')]: t('Group by'), }; +const localCache = new Map(); + +const cachedSupersetGet = cacheWrapper( + SupersetClient.get, + localCache, + ({ endpoint }) => endpoint || '', +); + /** * The configuration form for a specific filter. * Assigns field values to `filters[filterId]` in the form. @@ -254,6 +307,7 @@ const FiltersConfigForm = ( }: FiltersConfigFormProps, ref: React.RefObject, ) => { + const [error, setError] = useState(''); const [metrics, setMetrics] = useState([]); const [activeTabKey, setActiveTabKey] = useState( FilterTabs.configuration.key, @@ -261,14 +315,13 @@ const FiltersConfigForm = ( const [activeFilterPanelKey, setActiveFilterPanelKey] = useState< string | string[] >(FilterPanels.basic.key); - const [hasDefaultValue, setHasDefaultValue] = useState( - !!filterToEdit?.defaultDataMask?.filterState?.value, - ); + const forceUpdate = useForceUpdate(); const [datasetDetails, setDatasetDetails] = useState>(); - const defaultFormFilter = useMemo(() => {}, []); + const defaultFormFilter = useMemo(() => ({}), []); const formFilter = form.getFieldValue('filters')?.[filterId] || defaultFormFilter; + const nativeFilterItems = getChartMetadataRegistry().items; const nativeFilterVizTypes = Object.entries(nativeFilterItems) // @ts-ignore @@ -277,22 +330,58 @@ const FiltersConfigForm = ( ) .map(([key]) => key); - const loadedDatasets = useSelector( + const loadedDatasets = useSelector( ({ datasources }) => datasources, ); + const charts = useSelector(({ charts }) => charts); + + const doLoadedDatasetsHaveTemporalColumns = useMemo( + () => + Object.values(loadedDatasets).some(dataset => + hasTemporalColumns(dataset), + ), + [loadedDatasets], + ); + + const showTimeRangePicker = useMemo(() => { + const currentDataset = Object.values(loadedDatasets).find( + dataset => dataset.id === formFilter.dataset?.value, + ); + + return currentDataset ? hasTemporalColumns(currentDataset) : true; + }, [formFilter.dataset?.value, loadedDatasets]); + // @ts-ignore const hasDataset = !!nativeFilterItems[formFilter?.filterType]?.value ?.datasourceCount; - const hasColumn = - hasDataset && !FILTERS_WITHOUT_COLUMN.includes(formFilter?.filterType); + + const datasetId = + formFilter?.dataset?.value ?? + filterToEdit?.targets[0]?.datasetId ?? + mostUsedDataset(loadedDatasets, charts); + + const { controlItems = {}, mainControlItems = {} } = formFilter + ? getControlItemsMap({ + datasetId, + disabled: false, + forceUpdate, + form, + filterId, + filterType: formFilter.filterType, + filterToEdit, + formFilter, + removed, + }) + : {}; + const hasColumn = !!mainControlItems.groupby; + + const nativeFilterItem = nativeFilterItems[formFilter?.filterType] ?? {}; // @ts-ignore - const enableNoResults = !!nativeFilterItems[formFilter?.filterType]?.value - ?.enableNoResults; - const datasetId = formFilter?.dataset?.value; + const enableNoResults = !!nativeFilterItem.value?.enableNoResults; useEffect(() => { - if (datasetId && hasColumn) { + if (datasetId) { cachedSupersetGet({ endpoint: `/api/v1/dataset/${datasetId}`, }) @@ -308,7 +397,7 @@ const FiltersConfigForm = ( addDangerToast(response.message); }); } - }, [datasetId, hasColumn]); + }, [datasetId]); useImperativeHandle(ref, () => ({ changeTab(tab: 'configuration' | 'scoping') { @@ -331,67 +420,99 @@ const FiltersConfigForm = ( useBackendFormUpdate(form, filterId); - const refreshHandler = useCallback(() => { - if (!hasDataset || !formFilter?.dataset?.value) { - forceUpdate(); - return; - } - const formData = getFormData({ - datasetId: formFilter?.dataset?.value, - groupby: formFilter?.column, - ...formFilter, - }); + const setNativeFilterFieldValuesWrapper = (values: object) => { + setNativeFilterFieldValues(form, filterId, values); + setError(''); + forceUpdate(); + }; + + const setErrorWrapper = (error: string) => { setNativeFilterFieldValues(form, filterId, { defaultValueQueriesData: null, - isDataDirty: false, }); + setError(error); forceUpdate(); - getChartDataRequest({ - formData, - force: false, - requestParams: { dashboardId: 0 }, - }).then(response => { - if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) { - // deal with getChartDataRequest transforming the response data - const result = 'result' in response ? response.result[0] : response; - waitForAsyncData(result) - .then((asyncResult: ChartDataResponseResult[]) => { - setNativeFilterFieldValues(form, filterId, { - defaultValueQueriesData: asyncResult, + }; + + const refreshHandler = useCallback( + (force = false) => { + if (!hasDataset || !formFilter?.dataset?.value) { + forceUpdate(); + return; + } + const formData = getFormData({ + datasetId: formFilter?.dataset?.value, + groupby: formFilter?.column, + ...formFilter, + }); + setNativeFilterFieldValuesWrapper({ + defaultValueQueriesData: null, + isDataDirty: false, + }); + getChartDataRequest({ + formData, + force, + requestParams: { dashboardId: 0 }, + }) + .then(({ response, json }) => { + if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) { + // deal with getChartDataRequest transforming the response data + const result = 'result' in json ? json.result[0] : json; + + if (response.status === 200) { + setNativeFilterFieldValuesWrapper({ + defaultValueQueriesData: [result], + }); + } else if (response.status === 202) { + waitForAsyncData(result) + .then((asyncResult: ChartDataResponseResult[]) => { + setNativeFilterFieldValuesWrapper({ + defaultValueQueriesData: asyncResult, + }); + }) + .catch((error: ClientErrorObject) => { + setError( + error.message || error.error || t('Check configuration'), + ); + }); + } else { + throw new Error( + `Received unexpected response status (${response.status}) while fetching chart data`, + ); + } + } else { + setNativeFilterFieldValuesWrapper({ + defaultValueQueriesData: json.result, }); - forceUpdate(); - }) - .catch((error: ClientErrorObject) => { - // TODO: show error once this logic is moved into new NativeFilter - // component - console.error( - error.message || error.error || t('Check configuration'), + } + }) + .catch((error: Response) => { + error.json().then(body => { + setErrorWrapper( + body.message || error.statusText || t('Check configuration'), ); }); - } else { - setNativeFilterFieldValues(form, filterId, { - defaultValueQueriesData: response.result, }); - forceUpdate(); - } - }); - }, [filterId, forceUpdate, form, formFilter, hasDataset]); - - const defaultDatasetSelectOptions = Object.values(loadedDatasets).map( - datasetToSelectOption, + }, + [filterId, forceUpdate, form, formFilter, hasDataset], ); - const initialDatasetId = - filterToEdit?.targets[0]?.datasetId ?? - (defaultDatasetSelectOptions.length === 1 - ? defaultDatasetSelectOptions[0].value - : undefined); - const initColumn = filterToEdit?.targets[0]?.column?.name; + const newFormData = getFormData({ datasetId, groupby: hasColumn ? formFilter?.column : undefined, ...formFilter, }); + const [ + hasDefaultValue, + isRequired, + defaultValueTooltip, + setHasDefaultValue, + ] = useDefaultValue(formFilter, filterToEdit); + + const showDataset = + !datasetId || datasetDetails || formFilter?.dataset?.label; + useEffect(() => { if (hasDataset && hasFilledDataset && hasDefaultValue && isDataDirty) { refreshHandler(); @@ -403,17 +524,24 @@ const FiltersConfigForm = ( formFilter, isDataDirty, refreshHandler, + showDataset, ]); - const onDatasetSelectError = useCallback( - ({ error, message }: ClientErrorObject) => { - let errorText = message || error || t('An error has occurred'); - if (message === 'Forbidden') { - errorText = t('You do not have permission to edit this dashboard'); - } - addDangerToast(errorText); + const formChanged = useCallback(() => { + form.setFields([ + { + name: 'changed', + value: true, + }, + ]); + }, [form]); + + const updateFormValues = useCallback( + (values: any) => { + setNativeFilterFieldValues(form, filterId, values); + formChanged(); }, - [], + [filterId, form, formChanged], ); const parentFilterOptions = parentFilters.map(filter => ({ @@ -433,19 +561,15 @@ const FiltersConfigForm = ( const hasSorting = typeof filterToEdit?.controlValues?.sortAscending === 'boolean'; - const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset); + let sort = filterToEdit?.controlValues?.sortAscending; + if (typeof formFilter?.controlValues?.sortAscending === 'boolean') { + sort = formFilter.controlValues.sortAscending; + } - const controlItems = formFilter - ? getControlItemsMap({ - disabled: false, - forceUpdate, - form, - filterId, - filterType: formFilter.filterType, - filterToEdit, - formFilter, - }) - : {}; + const showDefaultValue = + !hasDataset || + (!isDataDirty && hasFilledDataset) || + !mainControlItems.groupby; const onSortChanged = (value: boolean | undefined) => { const previous = form.getFieldValue('filters')?.[filterId].controlValues; @@ -458,6 +582,39 @@ const FiltersConfigForm = ( forceUpdate(); }; + const validatePreFilter = () => + setTimeout( + () => + form.validateFields([ + ['filters', filterId, 'adhoc_filters'], + ['filters', filterId, 'time_range'], + ]), + 0, + ); + + const hasTimeRange = + formFilter?.time_range && formFilter.time_range !== 'No filter'; + + const hasAdhoc = formFilter?.adhoc_filters?.length > 0; + + const defaultToFirstItem = formFilter?.controlValues?.defaultToFirstItem; + + const hasAdvancedSection = + formFilter?.filterType === 'filter_select' || + formFilter?.filterType === 'filter_range'; + + const initialDefaultValue = + formFilter.filterType === filterToEdit?.filterType + ? filterToEdit?.defaultDataMask + : null; + + const preFilterValidator = () => { + if (hasTimeRange || hasAdhoc) { + return Promise.resolve(); + } + return Promise.reject(new Error(t('Pre-filter is required'))); + }; + let hasCheckedAdvancedControl = hasParentFilter || hasPreFilter || hasSorting; if (!hasCheckedAdvancedControl) { hasCheckedAdvancedControl = Object.keys(controlItems) @@ -473,6 +630,44 @@ const FiltersConfigForm = ( setActiveFilterPanelKey(activeFilterPanelKey); }, [hasCheckedAdvancedControl]); + const initiallyExcludedCharts = useMemo(() => { + const excluded: number[] = []; + if (formFilter?.dataset?.value === undefined) { + return []; + } + + Object.values(charts).forEach((chart: Chart) => { + const chartDatasetUid = chart.formData?.datasource; + if (chartDatasetUid === undefined) { + return; + } + if (loadedDatasets[chartDatasetUid]?.id !== formFilter?.dataset?.value) { + excluded.push(chart.id); + } + }); + return excluded; + }, [ + JSON.stringify(charts), + formFilter?.dataset?.value, + JSON.stringify(loadedDatasets), + ]); + + const ParentSelect = ({ + value, + ...rest + }: { + value?: { value: string | number }; + }) => ( + { // @ts-ignore const name = nativeFilterItems[filterType]?.value.name; const mappedName = name ? FILTER_TYPE_NAME_MAPPING[name] : undefined; + const isDisabled = + FILTER_SUPPORTED_TYPES[filterType]?.length === 1 && + FILTER_SUPPORTED_TYPES[filterType]?.includes( + GenericDataType.TEMPORAL, + ) && + !doLoadedDatasetsHaveTemporalColumns; return { value: filterType, - label: mappedName || name, + label: isDisabled ? ( + + {mappedName || name} + + ) : ( + mappedName || name + ), + disabled: isDisabled, }; })} - onChange={({ value }: { value: string }) => { + onChange={value => { setNativeFilterFieldValues(form, filterId, { filterType: value, defaultDataMask: null, + column: null, }); forceUpdate(); }} @@ -528,63 +740,46 @@ const FiltersConfigForm = ( {hasDataset && ( - {t('Dataset')}} - rules={[ - { required: !removed, message: t('Dataset is required') }, - ]} - {...getFiltersConfigModalTestId('datasource-input')} - > - { - // We need reset column when dataset changed - if (datasetId && e?.value !== datasetId) { - setNativeFilterFieldValues(form, filterId, { - defaultDataMask: null, - column: null, - }); - } - forceUpdate(); - }} - /> - - {hasColumn && ( + {showDataset ? ( {t('Column')}} + name={['filters', filterId, 'dataset']} + label={{t('Dataset')}} + initialValue={ + datasetDetails + ? { + label: datasetDetails.table_name, + value: datasetDetails.id, + } + : undefined + } rules={[ - { required: !removed, message: t('Field is required') }, + { required: !removed, message: t('Dataset is required') }, ]} - data-test="field-input" + {...getFiltersConfigModalTestId('datasource-input')} > - { - // We need reset default value when when column changed - setNativeFilterFieldValues(form, filterId, { - defaultDataMask: null, - }); + { + // We need to reset the column when the dataset has changed + if (value.value !== datasetId) { + setNativeFilterFieldValues(form, filterId, { + dataset: value, + defaultDataMask: null, + column: null, + }); + } forceUpdate(); }} /> + ) : ( + {t('Dataset')}}> + + )} + {hasDataset && + Object.keys(mainControlItems).map( + key => mainControlItems[key].element, + )} )} - {hasFilledDataset && ( -
+ + ), + [ + columnSlice, + inputValue, + lists.columns.length, + lists.metrics.length, + metricSlice, + search, + showAllColumns, + showAllMetrics, + ], ); return ( diff --git a/superset-frontend/src/explore/components/ExploreChartHeader.jsx b/superset-frontend/src/explore/components/ExploreChartHeader.jsx index fdcf7d275569..5d057cabaec7 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader.jsx @@ -20,8 +20,17 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import PropTypes from 'prop-types'; +import Icons from 'src/components/Icons'; import { styled, t } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; +import ReportModal from 'src/components/ReportModal'; +import { + fetchUISpecificReport, + toggleActive, + deleteActiveReport, +} from 'src/reports/actions/reports'; +import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import HeaderReportActionsDropdown from 'src/components/ReportModal/HeaderReportActionsDropdown'; import { chartPropShape } from '../../dashboard/util/propShapes'; import ExploreActionButtons from './ExploreActionButtons'; import RowCountLabel from './RowCountLabel'; @@ -80,6 +89,12 @@ const StyledHeader = styled.div` margin-left: ${({ theme }) => theme.gridUnit}px; } } + + .action-button { + color: ${({ theme }) => theme.colors.grayscale.base}; + margin: 0 ${({ theme }) => theme.gridUnit * 1.5}px 0 + ${({ theme }) => theme.gridUnit}px; + } `; const StyledButtons = styled.span` @@ -92,9 +107,26 @@ export class ExploreChartHeader extends React.PureComponent { super(props); this.state = { isPropertiesModalOpen: false, + showingReportModal: false, }; this.openPropertiesModal = this.openPropertiesModal.bind(this); this.closePropertiesModal = this.closePropertiesModal.bind(this); + this.showReportModal = this.showReportModal.bind(this); + this.hideReportModal = this.hideReportModal.bind(this); + this.renderReportModal = this.renderReportModal.bind(this); + } + + componentDidMount() { + if (this.canAddReports()) { + const { user, chart } = this.props; + // this is in the case that there is an anonymous user. + this.props.fetchUISpecificReport( + user.userId, + 'chart_id', + 'charts', + chart.id, + ); + } } getSliceName() { @@ -123,8 +155,58 @@ export class ExploreChartHeader extends React.PureComponent { }); } + showReportModal() { + this.setState({ showingReportModal: true }); + } + + hideReportModal() { + this.setState({ showingReportModal: false }); + } + + renderReportModal() { + const attachedReportExists = !!Object.keys(this.props.reports).length; + return attachedReportExists ? ( + + ) : ( + <> + + + + + ); + } + + canAddReports() { + if (!isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) { + return false; + } + const { user } = this.props; + if (!user) { + // this is in the case that there is an anonymous user. + return false; + } + const roles = Object.keys(user.roles || []); + const permissions = roles.map(key => + user.roles[key].filter( + perms => perms[0] === 'menu_access' && perms[1] === 'Manage', + ), + ); + return permissions[0].length > 0; + } + render() { - const formData = this.props.form_data; + const { user, form_data: formData } = this.props; const { chartStatus, chartUpdateEndTime, @@ -148,7 +230,7 @@ export class ExploreChartHeader extends React.PureComponent { {this.props.slice && ( - {this.props.userId && ( + {user.userId && ( + {this.canAddReports() && this.renderReportModal()} + { getFromLocalStorage(STORAGE_KEYS.sizes, INITIAL_SIZES), ); + const { slice } = props; + const updateQueryContext = useCallback( + async function fetchChartData() { + if (slice && slice.query_context === null) { + const queryContext = buildV1ChartDataPayload({ + formData: slice.form_data, + force: false, + resultFormat: 'json', + resultType: 'full', + setDataMask: null, + ownState: null, + }); + + await SupersetClient.put({ + endpoint: `/api/v1/chart/${slice.slice_id}`, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query_context: JSON.stringify(queryContext), + query_context_generation: true, + }), + }); + } + }, + [slice], + ); + useEffect(() => { + updateQueryContext(); + }, [updateQueryContext]); + const calcSectionHeight = useCallback( percent => { let headerHeight; @@ -254,7 +284,8 @@ const ExploreChartPanel = props => { form_data={props.form_data} timeout={props.timeout} chart={props.chart} - userId={props.userId} + user={props.user} + reports={props.reports} /> ); diff --git a/superset-frontend/src/explore/components/ExploreViewContainer.jsx b/superset-frontend/src/explore/components/ExploreViewContainer.jsx index 63efc2b189ff..27a6aa25ab9d 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer.jsx @@ -17,7 +17,7 @@ * under the License. */ /* eslint camelcase: 0 */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; @@ -25,7 +25,7 @@ import { styled, t, css, useTheme } from '@superset-ui/core'; import { debounce } from 'lodash'; import { Resizable } from 're-resizable'; -import { useDynamicPluginContext } from 'src/components/DynamicPlugins'; +import { usePluginContext } from 'src/components/DynamicPlugins'; import { Global } from '@emotion/react'; import { Tooltip } from 'src/components/Tooltip'; import { usePrevious } from 'src/common/hooks/usePrevious'; @@ -81,7 +81,7 @@ const Styles = styled.div` text-align: left; position: relative; width: 100%; - height: 100%; + max-height: 100%; display: flex; flex-direction: row; flex-wrap: nowrap; @@ -159,12 +159,17 @@ function useWindowSize({ delayMs = 250 } = {}) { } function ExploreViewContainer(props) { - const dynamicPluginContext = useDynamicPluginContext(); - const dynamicPlugin = dynamicPluginContext.plugins[props.vizType]; - const isDynamicPluginLoading = dynamicPlugin && dynamicPlugin.loading; + const dynamicPluginContext = usePluginContext(); + const dynamicPlugin = dynamicPluginContext.dynamicPlugins[props.vizType]; + const isDynamicPluginLoading = dynamicPlugin && dynamicPlugin.mounting; const wasDynamicPluginLoading = usePrevious(isDynamicPluginLoading); + /** the state of controls in the previous render */ const previousControls = usePrevious(props.controls); + /** the state of controls last time a query was triggered */ + const [lastQueriedControls, setLastQueriedControls] = useState( + props.controls, + ); const windowSize = useWindowSize(); const [showingModal, setShowingModal] = useState(false); @@ -187,29 +192,32 @@ function ExploreViewContainer(props) { datasource_width: 300, }; - function addHistory({ isReplace = false, title } = {}) { - const payload = { ...props.form_data }; - const longUrl = getExploreLongUrl( - props.form_data, - props.standalone ? URL_PARAMS.standalone.name : null, - false, - ); - try { - if (isReplace) { - window.history.replaceState(payload, title, longUrl); - } else { - window.history.pushState(payload, title, longUrl); - } - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - 'Failed at altering browser history', - payload, - title, - longUrl, + const addHistory = useCallback( + ({ isReplace = false, title } = {}) => { + const payload = { ...props.form_data }; + const longUrl = getExploreLongUrl( + props.form_data, + props.standalone ? URL_PARAMS.standalone.name : null, + false, ); - } - } + try { + if (isReplace) { + window.history.replaceState(payload, title, longUrl); + } else { + window.history.pushState(payload, title, longUrl); + } + } catch (e) { + // eslint-disable-next-line no-console + console.warn( + 'Failed at altering browser history', + payload, + title, + longUrl, + ); + } + }, + [props.form_data, props.standalone], + ); function handlePopstate() { const formData = window.history.state; @@ -223,11 +231,11 @@ function ExploreViewContainer(props) { ); } } - - function onQuery() { + const onQuery = useCallback(() => { props.actions.triggerQuery(true, props.chart.id); addHistory(); - } + setLastQueriedControls(props.controls); + }, [props.controls, addHistory, props.actions, props.chart.id]); function handleKeydown(event) { const controlOrCommand = event.ctrlKey || event.metaKey; @@ -338,13 +346,13 @@ function ExploreViewContainer(props) { }, [props.controls, props.ownState]); const chartIsStale = useMemo(() => { - if (previousControls) { + if (lastQueriedControls) { const changedControlKeys = Object.keys(props.controls).filter( key => - typeof previousControls[key] !== 'undefined' && + typeof lastQueriedControls[key] !== 'undefined' && !areObjectsEqual( props.controls[key].value, - previousControls[key].value, + lastQueriedControls[key].value, ), ); @@ -355,7 +363,7 @@ function ExploreViewContainer(props) { ); } return false; - }, [previousControls, props.controls]); + }, [lastQueriedControls, props.controls]); useEffect(() => { if (props.ownState !== undefined) { @@ -440,6 +448,7 @@ function ExploreViewContainer(props) { margin-bottom: 0; } body { + height: 100vh; max-height: 100vh; overflow: hidden; } @@ -450,7 +459,7 @@ function ExploreViewContainer(props) { #app { flex-basis: 100%; overflow: hidden; - height: 100vh; + height: 100%; } #app-menu { flex-shrink: 0; @@ -572,7 +581,7 @@ function ExploreViewContainer(props) { ExploreViewContainer.propTypes = propTypes; function mapStateToProps(state) { - const { explore, charts, impressionId, dataMask } = state; + const { explore, charts, impressionId, dataMask, reports } = state; const form_data = getFormDataFromControls(explore.controls); form_data.extra_form_data = mergeExtraFormData( { ...form_data.extra_form_data }, @@ -612,6 +621,8 @@ function mapStateToProps(state) { ownState: dataMask[form_data.slice_id ?? 0]?.ownState, // 0 - unsaved chart impressionId, userId: explore.user_id, + user: explore.user, + reports, }; } diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx index 0c46f6ba792f..ab1e075a45cb 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx @@ -175,7 +175,7 @@ export default function PropertiesModal({ buttonStyle="primary" // @ts-ignore onClick={onSubmit} - disabled={!owners || submitting || !name} + disabled={submitting || !name} cta > {t('Save')} diff --git a/superset-frontend/src/explore/components/controls/BoundsControl.jsx b/superset-frontend/src/explore/components/controls/BoundsControl.jsx index ff22cebf3b18..39a0d560bc33 100644 --- a/superset-frontend/src/explore/components/controls/BoundsControl.jsx +++ b/superset-frontend/src/explore/components/controls/BoundsControl.jsx @@ -97,8 +97,8 @@ export default class BoundsControl extends React.Component { onChange() { const mm = this.state.minMax; - const min = parseFloat(mm[0]) || null; - const max = parseFloat(mm[1]) || null; + const min = Number.isNaN(parseFloat(mm[0])) ? null : parseFloat(mm[0]); + const max = Number.isNaN(parseFloat(mm[1])) ? null : parseFloat(mm[1]); this.props.onChange([min, max]); } diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx index c6e712840c96..384d23ed03a5 100644 --- a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx @@ -27,7 +27,7 @@ import { SortableElement, arrayMove, } from 'react-sortable-hoc'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { HeaderContainer, AddIconButton, @@ -80,8 +80,9 @@ class CollectionControl extends React.Component { } onChange(i, value) { - Object.assign(this.props.value[i], value); - this.props.onChange(this.props.value); + const newValue = [...this.props.value]; + newValue[i] = { ...this.props.value[i], ...value }; + this.props.onChange(newValue); } onAdd() { @@ -156,11 +157,9 @@ class CollectionControl extends React.Component { - diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx index d26e91fb687e..cf1d4255208a 100644 --- a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx +++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { isFunction } from 'lodash'; -import { Select } from 'src/components/Select'; +import { Select } from 'src/components'; import { Tooltip } from 'src/components/Tooltip'; import ControlHeader from '../ControlHeader'; @@ -54,14 +54,13 @@ export default class ColorSchemeControl extends React.PureComponent { this.renderOption = this.renderOption.bind(this); } - onChange(option) { - const optionValue = option ? option.value : null; - this.props.onChange(optionValue); + onChange(value) { + this.props.onChange(value); } - renderOption(key) { + renderOption(value) { const { isLinear } = this.props; - const currentScheme = this.schemes[key.value]; + const currentScheme = this.schemes[value]; // For categorical scheme, display all the colors // For sequential scheme, show 10 or interpolate to 10. @@ -106,34 +105,28 @@ export default class ColorSchemeControl extends React.PureComponent { } render() { - const { schemes, choices, labelMargin = 0 } = this.props; + const { schemes, choices } = this.props; // save parsed schemes for later this.schemes = isFunction(schemes) ? schemes() : schemes; + const options = (isFunction(choices) ? choices() : choices).map( - ([value, label]) => ({ + ([value]) => ({ value, - // use scheme label if available - label: this.schemes[value]?.label || label, + label: this.renderOption(value), }), ); const selectProps = { - multi: false, + allowClear: this.props.clearable, + defaultValue: this.props.default, name: `select-${this.props.name}`, - placeholder: `Select (${options.length})`, - default: this.props.default, + onChange: this.onChange, options, + placeholder: `Select (${options.length})`, + showSearch: true, value: this.props.value, - autosize: false, - clearable: this.props.clearable, - onChange: this.onChange, - optionRenderer: this.renderOption, - valueRenderer: this.renderOption, }; return ( -
- - } {...selectProps} /> ); } } diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx new file mode 100644 index 000000000000..401364e0dfc0 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useCallback, useEffect, useState } from 'react'; +import { styled, css, t, useTheme } from '@superset-ui/core'; +import Icons from 'src/components/Icons'; +import ControlHeader from 'src/explore/components/ControlHeader'; +import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate'; +import { FormattingPopover } from './FormattingPopover'; +import { + COMPARATOR, + ConditionalFormattingConfig, + ConditionalFormattingControlProps, +} from './types'; +import { + AddControlLabel, + CaretContainer, + Label, + OptionControlContainer, +} from '../OptionControls'; + +const FormattersContainer = styled.div` + ${({ theme }) => css` + padding: ${theme.gridUnit}px; + border: solid 1px ${theme.colors.grayscale.light2}; + border-radius: ${theme.gridUnit}px; + `} +`; + +export const FormatterContainer = styled(OptionControlContainer)` + &, + & > div { + margin-bottom: ${({ theme }) => theme.gridUnit}px; + :last-child { + margin-bottom: 0; + } + } +`; + +export const CloseButton = styled.button` + ${({ theme }) => css` + color: ${theme.colors.grayscale.light1}; + height: 100%; + width: ${theme.gridUnit * 6}px; + border: none; + border-right: solid 1px ${theme.colors.grayscale.dark2}0C; + padding: 0; + outline: none; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + `} +`; + +const ConditionalFormattingControl = ({ + value, + onChange, + columnOptions, + verboseMap, + ...props +}: ConditionalFormattingControlProps) => { + const theme = useTheme(); + const [ + conditionalFormattingConfigs, + setConditionalFormattingConfigs, + ] = useState(value ?? []); + + useEffect(() => { + if (onChange) { + onChange(conditionalFormattingConfigs); + } + }, [conditionalFormattingConfigs, onChange]); + + // remove formatter when corresponding column is removed from controls + const removeFormattersWhenColumnsChange = useCallback(() => { + const newFormattingConfigs = conditionalFormattingConfigs.filter(config => + columnOptions.some(option => option?.value === config?.column), + ); + if ( + newFormattingConfigs.length !== conditionalFormattingConfigs.length && + onChange + ) { + setConditionalFormattingConfigs(newFormattingConfigs); + onChange(newFormattingConfigs); + } + }, [JSON.stringify(columnOptions)]); + useComponentDidUpdate(removeFormattersWhenColumnsChange); + + const onDelete = (index: number) => { + setConditionalFormattingConfigs(prevConfigs => + prevConfigs.filter((_, i) => i !== index), + ); + }; + + const onSave = (config: ConditionalFormattingConfig) => { + setConditionalFormattingConfigs(prevConfigs => [...prevConfigs, config]); + }; + + const onEdit = (newConfig: ConditionalFormattingConfig, index: number) => { + const newConfigs = [...conditionalFormattingConfigs]; + newConfigs.splice(index, 1, newConfig); + setConditionalFormattingConfigs(newConfigs); + }; + + const createLabel = ({ + column, + operator, + targetValue, + targetValueLeft, + targetValueRight, + }: ConditionalFormattingConfig) => { + const columnName = (column && verboseMap?.[column]) ?? column; + switch (operator) { + case COMPARATOR.NONE: + return `${columnName}`; + case COMPARATOR.BETWEEN: + return `${targetValueLeft} ${COMPARATOR.LESS_THAN} ${columnName} ${COMPARATOR.LESS_THAN} ${targetValueRight}`; + case COMPARATOR.BETWEEN_OR_EQUAL: + return `${targetValueLeft} ${COMPARATOR.LESS_OR_EQUAL} ${columnName} ${COMPARATOR.LESS_OR_EQUAL} ${targetValueRight}`; + case COMPARATOR.BETWEEN_OR_LEFT_EQUAL: + return `${targetValueLeft} ${COMPARATOR.LESS_OR_EQUAL} ${columnName} ${COMPARATOR.LESS_THAN} ${targetValueRight}`; + case COMPARATOR.BETWEEN_OR_RIGHT_EQUAL: + return `${targetValueLeft} ${COMPARATOR.LESS_THAN} ${columnName} ${COMPARATOR.LESS_OR_EQUAL} ${targetValueRight}`; + default: + return `${columnName} ${operator} ${targetValue}`; + } + }; + + return ( +
+ + + {conditionalFormattingConfigs.map((config, index) => ( + + onDelete(index)}> + + + + onEdit(newConfig, index) + } + destroyTooltipOnHide + > + + + + + + + + + ))} + + + + {t('Add new color formatter')} + + + +
+ ); +}; + +export default ConditionalFormattingControl; diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx new file mode 100644 index 000000000000..c44ca7623546 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopover.tsx @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useCallback, useState } from 'react'; +import Popover from 'src/components/Popover'; +import { FormattingPopoverContent } from './FormattingPopoverContent'; +import { ConditionalFormattingConfig, FormattingPopoverProps } from './types'; + +export const FormattingPopover = ({ + title, + columns, + onChange, + config, + children, + ...props +}: FormattingPopoverProps) => { + const [visible, setVisible] = useState(false); + + const handleSave = useCallback( + (newConfig: ConditionalFormattingConfig) => { + setVisible(false); + onChange(newConfig); + }, + [onChange], + ); + + return ( + + } + visible={visible} + onVisibleChange={setVisible} + trigger={['click']} + overlayStyle={{ width: '450px' }} + {...props} + > + {children} + + ); +}; diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx new file mode 100644 index 000000000000..85f406c08d18 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx @@ -0,0 +1,229 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { styled, t } from '@superset-ui/core'; +import { Form, FormItem, FormProps } from 'src/components/Form'; +import { Select } from 'src/components'; +import { Col, InputNumber, Row } from 'src/common/components'; +import Button from 'src/components/Button'; +import { + COMPARATOR, + ConditionalFormattingConfig, + MULTIPLE_VALUE_COMPARATORS, +} from './types'; + +const FullWidthInputNumber = styled(InputNumber)` + width: 100%; +`; + +const JustifyEnd = styled.div` + display: flex; + justify-content: flex-end; +`; + +const colorSchemeOptions = [ + { value: 'rgb(0,255,0)', label: t('green') }, + { value: 'rgb(255,255,0)', label: t('yellow') }, + { value: 'rgb(255,0,0)', label: t('red') }, +]; + +const operatorOptions = [ + { value: COMPARATOR.NONE, label: 'None' }, + { value: COMPARATOR.GREATER_THAN, label: '>' }, + { value: COMPARATOR.LESS_THAN, label: '<' }, + { value: COMPARATOR.GREATER_OR_EQUAL, label: '≥' }, + { value: COMPARATOR.LESS_OR_EQUAL, label: '≤' }, + { value: COMPARATOR.EQUAL, label: '=' }, + { value: COMPARATOR.NOT_EQUAL, label: '≠' }, + { value: COMPARATOR.BETWEEN, label: '< x <' }, + { value: COMPARATOR.BETWEEN_OR_EQUAL, label: '≤ x ≤' }, + { value: COMPARATOR.BETWEEN_OR_LEFT_EQUAL, label: '≤ x <' }, + { value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤' }, +]; + +const targetValueValidator = ( + compare: (targetValue: number, compareValue: number) => boolean, + rejectMessage: string, +) => (targetValue: number | string) => ( + _: any, + compareValue: number | string, +) => { + if ( + !targetValue || + !compareValue || + compare(Number(targetValue), Number(compareValue)) + ) { + return Promise.resolve(); + } + return Promise.reject(new Error(rejectMessage)); +}; + +const targetValueLeftValidator = targetValueValidator( + (target: number, val: number) => target > val, + t('This value should be smaller than the right target value'), +); + +const targetValueRightValidator = targetValueValidator( + (target: number, val: number) => target < val, + t('This value should be greater than the left target value'), +); + +const isOperatorMultiValue = (operator?: COMPARATOR) => + operator && MULTIPLE_VALUE_COMPARATORS.includes(operator); + +const isOperatorNone = (operator?: COMPARATOR) => + !operator || operator === COMPARATOR.NONE; + +const rulesRequired = [{ required: true, message: t('Required') }]; + +type GetFieldValue = Pick['form'], 'getFieldValue'>; +const rulesTargetValueLeft = [ + { required: true, message: t('Required') }, + ({ getFieldValue }: GetFieldValue) => ({ + validator: targetValueLeftValidator(getFieldValue('targetValueRight')), + }), +]; + +const rulesTargetValueRight = [ + { required: true, message: t('Required') }, + ({ getFieldValue }: GetFieldValue) => ({ + validator: targetValueRightValidator(getFieldValue('targetValueLeft')), + }), +]; + +const targetValueLeftDeps = ['targetValueRight']; +const targetValueRightDeps = ['targetValueLeft']; + +const shouldFormItemUpdate = ( + prevValues: ConditionalFormattingConfig, + currentValues: ConditionalFormattingConfig, +) => + isOperatorNone(prevValues.operator) !== + isOperatorNone(currentValues.operator) || + isOperatorMultiValue(prevValues.operator) !== + isOperatorMultiValue(currentValues.operator); + +const operatorField = ( + + + + + + + value === frame)} + value={frame} onChange={onChangeFrame} - className="frame-dropdown" /> {frame !== 'No filter' && } {frame === 'Common' && ( @@ -303,10 +316,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) { {validTimeRange &&
{evalResponse}
} {!validTimeRange && ( - + {evalResponse} )} @@ -338,7 +348,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) { const title = ( - + {t('Edit time range')} ); @@ -353,7 +363,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) { { + if (!CALENDAR_RANGE_SET.has(value as CalendarRangeType)) { + onChange(PreviousCalendarWeek); + } + }, [onChange, value]); + + if (!CALENDAR_RANGE_SET.has(value as CalendarRangeType)) { + return null; } return ( @@ -43,8 +46,8 @@ export function CalendarFrame(props: FrameComponentProps) { {t('Configure Time Range: Previous...')}
props.onChange(e.target.value)} + value={value} + onChange={(e: any) => onChange(e.target.value)} > {CALENDAR_RANGE_OPTIONS.map(({ value, label }) => ( diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.test.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.test.tsx index 94efa8df5dea..6cb5c4f56cd0 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.test.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.test.tsx @@ -96,10 +96,10 @@ test('triggers onChange when the value changes', () => { test('triggers onChange when the mode changes', () => { const onChange = jest.fn(); render(); - userEvent.click(screen.getByText('Midnight')); - userEvent.click(screen.getByText('Relative Date/Time')); - userEvent.click(screen.getByText('Now')); - userEvent.click(screen.getByText('Specific Date/Time')); + userEvent.click(screen.getByTitle('Midnight')); + userEvent.click(screen.getByTitle('Relative Date/Time')); + userEvent.click(screen.getAllByTitle('Now')[1]); + userEvent.click(screen.getAllByTitle('Specific Date/Time')[1]); expect(onChange).toHaveBeenCalledTimes(2); }); diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx index b9269a440487..8125f0e9a6a7 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/components/CustomFrame.tsx @@ -23,7 +23,7 @@ import { isInteger } from 'lodash'; import { Col, InputNumber, Row } from 'src/common/components'; import { DatePicker } from 'src/components/DatePicker'; import { Radio } from 'src/components/Radio'; -import { Select } from 'src/components/Select'; +import { Select } from 'src/components'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import { SINCE_GRAIN_OPTIONS, @@ -38,7 +38,6 @@ import { } from 'src/explore/components/controls/DateFilterControl/utils'; import { CustomRangeKey, - SelectOptionType, FrameComponentProps, } from 'src/explore/components/controls/DateFilterControl/types'; @@ -118,13 +117,10 @@ export function CustomFrame(props: FrameComponentProps) { /> option.value === sinceGrain, - )} - onChange={(option: SelectOptionType) => - onChange('sinceGrain', option.value) - } + value={sinceGrain} + onChange={(value: string) => onChange('sinceGrain', value)} /> @@ -176,13 +169,10 @@ export function CustomFrame(props: FrameComponentProps) { /> option.value === untilGrain, - )} - onChange={(option: SelectOptionType) => - onChange('untilGrain', option.value) - } + value={untilGrain} + onChange={(value: string) => onChange('untilGrain', value)} /> diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx new file mode 100644 index 000000000000..c58b3d382e6d --- /dev/null +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx @@ -0,0 +1,223 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-disable camelcase */ +import React, { useCallback, useMemo, useState } from 'react'; +import Tabs from 'src/components/Tabs'; +import Button from 'src/components/Button'; +import { NativeSelect as Select } from 'src/components/Select'; +import { t, styled } from '@superset-ui/core'; + +import { Form, FormItem } from 'src/components/Form'; +import { StyledColumnOption } from 'src/explore/components/optionRenderers'; +import { ColumnMeta } from '@superset-ui/chart-controls'; + +const StyledSelect = styled(Select)` + .metric-option { + & > svg { + min-width: ${({ theme }) => `${theme.gridUnit * 4}px`}; + } + & > .option-label { + overflow: hidden; + text-overflow: ellipsis; + } + } +`; + +interface ColumnSelectPopoverProps { + columns: ColumnMeta[]; + editedColumn?: ColumnMeta; + onChange: (column: ColumnMeta) => void; + onClose: () => void; +} + +const ColumnSelectPopover = ({ + columns, + editedColumn, + onChange, + onClose, +}: ColumnSelectPopoverProps) => { + const [ + initialCalculatedColumn, + initialSimpleColumn, + ] = editedColumn?.expression + ? [editedColumn, undefined] + : [undefined, editedColumn]; + const [selectedCalculatedColumn, setSelectedCalculatedColumn] = useState( + initialCalculatedColumn, + ); + const [selectedSimpleColumn, setSelectedSimpleColumn] = useState( + initialSimpleColumn, + ); + + const [calculatedColumns, simpleColumns] = useMemo( + () => + columns?.reduce( + (acc: [ColumnMeta[], ColumnMeta[]], column: ColumnMeta) => { + if (column.expression) { + acc[0].push(column); + } else { + acc[1].push(column); + } + return acc; + }, + [[], []], + ), + [columns], + ); + + const onCalculatedColumnChange = useCallback( + selectedColumnName => { + const selectedColumn = calculatedColumns.find( + col => col.column_name === selectedColumnName, + ); + setSelectedCalculatedColumn(selectedColumn); + setSelectedSimpleColumn(undefined); + }, + [calculatedColumns], + ); + + const onSimpleColumnChange = useCallback( + selectedColumnName => { + const selectedColumn = simpleColumns.find( + col => col.column_name === selectedColumnName, + ); + setSelectedCalculatedColumn(undefined); + setSelectedSimpleColumn(selectedColumn); + }, + [simpleColumns], + ); + + const defaultActiveTabKey = + initialSimpleColumn || calculatedColumns.length === 0 ? 'simple' : 'saved'; + + const onSave = useCallback(() => { + const selectedColumn = selectedCalculatedColumn || selectedSimpleColumn; + if (!selectedColumn) { + return; + } + onChange(selectedColumn); + onClose(); + }, [onChange, onClose, selectedCalculatedColumn, selectedSimpleColumn]); + + const onResetStateAndClose = useCallback(() => { + setSelectedCalculatedColumn(initialCalculatedColumn); + setSelectedSimpleColumn(initialSimpleColumn); + onClose(); + }, [initialCalculatedColumn, initialSimpleColumn, onClose]); + + const stateIsValid = selectedCalculatedColumn || selectedSimpleColumn; + const hasUnsavedChanges = + selectedCalculatedColumn?.column_name !== + initialCalculatedColumn?.column_name || + selectedSimpleColumn?.column_name !== initialSimpleColumn?.column_name; + + const filterOption = useCallback( + (input, option) => + option?.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0, + [], + ); + + const getPopupContainer = useCallback( + (triggerNode: any) => triggerNode.parentNode, + [], + ); + + return ( +
+ + + + + {calculatedColumns.map(calculatedColumn => ( + + + + ))} + + + + + + + + + +
+ + +
+
+ ); +}; + +export default ColumnSelectPopover; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx new file mode 100644 index 000000000000..4caf086d4642 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useCallback, useMemo, useState } from 'react'; +import { ColumnMeta } from '@superset-ui/chart-controls'; +import Popover from 'src/components/Popover'; +import { ExplorePopoverContent } from 'src/explore/components/ExploreContentPopover'; +import ColumnSelectPopover from './ColumnSelectPopover'; + +interface ColumnSelectPopoverTriggerProps { + columns: ColumnMeta[]; + editedColumn?: ColumnMeta; + onColumnEdit: (editedColumn: ColumnMeta) => void; + isControlledComponent?: boolean; + visible?: boolean; + togglePopover?: (visible: boolean) => void; + closePopover?: () => void; + children: React.ReactNode; +} + +const ColumnSelectPopoverTrigger = ({ + columns, + editedColumn, + onColumnEdit, + isControlledComponent, + children, + ...props +}: ColumnSelectPopoverTriggerProps) => { + const [popoverVisible, setPopoverVisible] = useState(false); + + const togglePopover = useCallback((visible: boolean) => { + setPopoverVisible(visible); + }, []); + + const closePopover = useCallback(() => { + setPopoverVisible(false); + }, []); + + const { + visible, + handleTogglePopover, + handleClosePopover, + } = isControlledComponent + ? { + visible: props.visible, + handleTogglePopover: props.togglePopover!, + handleClosePopover: props.closePopover!, + } + : { + visible: popoverVisible, + handleTogglePopover: togglePopover, + handleClosePopover: closePopover, + }; + + const overlayContent = useMemo( + () => ( + + + + ), + [columns, editedColumn, handleClosePopover, onColumnEdit], + ); + + return ( + + {children} + + ); +}; + +export default ColumnSelectPopoverTrigger; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx index 3a1325875cf7..93d00a70592d 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.test.tsx @@ -29,7 +29,7 @@ const defaultProps: LabelProps = { test('renders with default props', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop columns')).toBeInTheDocument(); + expect(screen.getByText('Drop columns here')).toBeInTheDocument(); }); test('renders with value', () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx index 10c8bd60d27e..69cc6442e26a 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; -import { tn } from '@superset-ui/core'; +import React, { useCallback, useMemo, useState } from 'react'; +import { FeatureFlag, isFeatureEnabled, tn } from '@superset-ui/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; import { isEmpty } from 'lodash'; import { LabelProps } from 'src/explore/components/controls/DndColumnSelectControl/types'; @@ -26,62 +26,207 @@ import OptionWrapper from 'src/explore/components/controls/DndColumnSelectContro import { OptionSelector } from 'src/explore/components/controls/DndColumnSelectControl/utils'; import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types'; import { DndItemType } from 'src/explore/components/DndItemType'; -import { StyledColumnOption } from 'src/explore/components/optionRenderers'; +import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate'; +import ColumnSelectPopoverTrigger from './ColumnSelectPopoverTrigger'; export const DndColumnSelect = (props: LabelProps) => { - const { value, options, multi = true } = props; - const optionSelector = new OptionSelector(options, value); - const [values, setValues] = useState(optionSelector.values); - - const onDrop = (item: DatasourcePanelDndItem) => { - const column = item.value as ColumnMeta; - if (!optionSelector.isArray && !isEmpty(optionSelector.values)) { - optionSelector.replace(0, column.column_name); - } else { - optionSelector.add(column.column_name); + const { + value, + options, + multi = true, + onChange, + canDelete = true, + ghostButtonText, + name, + label, + } = props; + const [newColumnPopoverVisible, setNewColumnPopoverVisible] = useState(false); + + const optionSelector = useMemo( + () => new OptionSelector(options, multi, value), + [multi, options, value], + ); + + // synchronize values in case of dataset changes + const handleOptionsChange = useCallback(() => { + const optionSelectorValues = optionSelector.getValues(); + if (typeof value !== typeof optionSelectorValues) { + onChange(optionSelectorValues); } - setValues(optionSelector.values); - props.onChange(optionSelector.getValues()); - }; - - const canDrop = (item: DatasourcePanelDndItem) => - (multi || optionSelector.values.length === 0) && - !optionSelector.has((item.value as ColumnMeta).column_name); - - const onClickClose = (index: number) => { - optionSelector.del(index); - setValues(optionSelector.values); - props.onChange(optionSelector.getValues()); - }; - - const onShiftOptions = (dragIndex: number, hoverIndex: number) => { - optionSelector.swap(dragIndex, hoverIndex); - setValues(optionSelector.values); - props.onChange(optionSelector.getValues()); - }; - - const valuesRenderer = () => - values.map((column, idx) => ( - - - - )); + if ( + typeof value === 'string' && + typeof optionSelectorValues === 'string' && + value !== optionSelectorValues + ) { + onChange(optionSelectorValues); + } + if ( + Array.isArray(optionSelectorValues) && + Array.isArray(value) && + (optionSelectorValues.length !== value.length || + optionSelectorValues.every((val, index) => val === value[index])) + ) { + onChange(optionSelectorValues); + } + }, [JSON.stringify(value), JSON.stringify(optionSelector.getValues())]); + + // useComponentDidUpdate to avoid running this for the first render, to avoid + // calling onChange when the initial value is not valid for the dataset + useComponentDidUpdate(handleOptionsChange); + + const onDrop = useCallback( + (item: DatasourcePanelDndItem) => { + const column = item.value as ColumnMeta; + if (!optionSelector.multi && !isEmpty(optionSelector.values)) { + optionSelector.replace(0, column.column_name); + } else { + optionSelector.add(column.column_name); + } + onChange(optionSelector.getValues()); + }, + [onChange, optionSelector], + ); + + const canDrop = useCallback( + (item: DatasourcePanelDndItem) => { + const columnName = (item.value as ColumnMeta).column_name; + return ( + columnName in optionSelector.options && !optionSelector.has(columnName) + ); + }, + [optionSelector], + ); + + const onClickClose = useCallback( + (index: number) => { + optionSelector.del(index); + onChange(optionSelector.getValues()); + }, + [onChange, optionSelector], + ); + + const onShiftOptions = useCallback( + (dragIndex: number, hoverIndex: number) => { + optionSelector.swap(dragIndex, hoverIndex); + onChange(optionSelector.getValues()); + }, + [onChange, optionSelector], + ); + + const popoverOptions = useMemo( + () => + Object.values(options).filter( + col => + !optionSelector.values + .map(val => val.column_name) + .includes(col.column_name), + ), + [optionSelector.values, options], + ); + + const valuesRenderer = useCallback( + () => + optionSelector.values.map((column, idx) => + isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) ? ( + { + optionSelector.replace(idx, newColumn.column_name); + onChange(optionSelector.getValues()); + }} + editedColumn={column} + > + + + ) : ( + + ), + ), + [ + canDelete, + label, + name, + onChange, + onClickClose, + onShiftOptions, + optionSelector, + popoverOptions, + ], + ); + + const addNewColumnWithPopover = useCallback( + (newColumn: ColumnMeta) => { + optionSelector.add(newColumn.column_name); + onChange(optionSelector.getValues()); + }, + [onChange, optionSelector], + ); + + const togglePopover = useCallback((visible: boolean) => { + setNewColumnPopoverVisible(visible); + }, []); + + const closePopover = useCallback(() => { + togglePopover(false); + }, [togglePopover]); + + const openPopover = useCallback(() => { + togglePopover(true); + }, [togglePopover]); + + const defaultGhostButtonText = isFeatureEnabled( + FeatureFlag.ENABLE_DND_WITH_CLICK_UX, + ) + ? tn( + 'Drop a column here or click', + 'Drop columns here or click', + multi ? 2 : 1, + ) + : tn('Drop column here', 'Drop columns here', multi ? 2 : 1); return ( - - onDrop={onDrop} - canDrop={canDrop} - valuesRenderer={valuesRenderer} - accept={DndItemType.Column} - displayGhostButton={multi || optionSelector.values.length === 0} - ghostButtonText={tn('Drop column', 'Drop columns', multi ? 2 : 1)} - {...props} - /> +
+ + onDrop={onDrop} + canDrop={canDrop} + valuesRenderer={valuesRenderer} + accept={DndItemType.Column} + displayGhostButton={multi || optionSelector.values.length === 0} + ghostButtonText={ghostButtonText || defaultGhostButtonText} + onClickGhostButton={ + isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) + ? openPopover + : undefined + } + {...props} + /> + +
+ +
); }; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx index bf7d135b7884..bddb80a9aa21 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx @@ -38,7 +38,7 @@ const defaultProps = { test('renders with default props', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument(); + expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); test('renders with value', () => { @@ -56,7 +56,7 @@ test('renders options with saved metric', () => { render(, { useDnd: true, }); - expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument(); + expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); test('renders options with column', () => { @@ -76,7 +76,7 @@ test('renders options with column', () => { useDnd: true, }, ); - expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument(); + expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); test('renders options with adhoc metric', () => { @@ -87,5 +87,5 @@ test('renders options with adhoc metric', () => { render(, { useDnd: true, }); - expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument(); + expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx index 0ba8dd31ba06..a9e327bfd6bf 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.tsx @@ -16,11 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useMemo, useState } from 'react'; -import { logging, SupersetClient, t, Metric } from '@superset-ui/core'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { + FeatureFlag, + isFeatureEnabled, + logging, + Metric, + SupersetClient, + t, +} from '@superset-ui/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; -import { Tooltip } from 'src/components/Tooltip'; -import { OPERATORS } from 'src/explore/constants'; +import { + OPERATOR_ENUM_TO_OPERATOR_TYPE, + Operators, +} from 'src/explore/constants'; import { OptionSortType } from 'src/explore/types'; import { DndFilterSelectProps, @@ -40,10 +49,19 @@ import { } from 'src/explore/components/DatasourcePanel/types'; import { DndItemType } from 'src/explore/components/DndItemType'; +const DND_ACCEPTED_TYPES = [ + DndItemType.Column, + DndItemType.Metric, + DndItemType.MetricOption, + DndItemType.AdhocMetricOption, +]; + const isDictionaryForAdhocFilter = (value: OptionValueType) => !(value instanceof AdhocFilter) && value?.expressionType; export const DndFilterSelect = (props: DndFilterSelectProps) => { + const { datasource, onChange } = props; + const propsValues = Array.from(props.value ?? []); const [values, setValues] = useState( propsValues.map((filter: OptionValueType) => @@ -107,7 +125,6 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { ); useEffect(() => { - const { datasource } = props; if (datasource && datasource.type === 'table') { const dbId = datasource.database?.id; const { @@ -139,7 +156,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { }); } } - }, []); + }, [datasource]); useEffect(() => { setOptions(optionsForSelect(props.columns, props.formData)); @@ -153,132 +170,173 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { ); }, [props.value]); - const onClickClose = (index: number) => { - const valuesCopy = [...values]; - valuesCopy.splice(index, 1); - setValues(valuesCopy); - props.onChange(valuesCopy); - }; + const onClickClose = useCallback( + (index: number) => { + const valuesCopy = [...values]; + valuesCopy.splice(index, 1); + setValues(valuesCopy); + onChange(valuesCopy); + }, + [onChange, values], + ); - const onShiftOptions = (dragIndex: number, hoverIndex: number) => { - const newValues = [...values]; - [newValues[hoverIndex], newValues[dragIndex]] = [ - newValues[dragIndex], - newValues[hoverIndex], - ]; - setValues(newValues); - }; + const onShiftOptions = useCallback( + (dragIndex: number, hoverIndex: number) => { + const newValues = [...values]; + [newValues[hoverIndex], newValues[dragIndex]] = [ + newValues[dragIndex], + newValues[hoverIndex], + ]; + setValues(newValues); + }, + [values], + ); - const getMetricExpression = (savedMetricName: string) => - props.savedMetrics.find( - (savedMetric: Metric) => savedMetric.metric_name === savedMetricName, - )?.expression; + const getMetricExpression = useCallback( + (savedMetricName: string) => + props.savedMetrics.find( + (savedMetric: Metric) => savedMetric.metric_name === savedMetricName, + )?.expression, + [props.savedMetrics], + ); - const mapOption = (option: OptionValueType) => { - // already a AdhocFilter, skip - if (option instanceof AdhocFilter) { - return option; - } - const filterOptions = option as Record; - // via datasource saved metric - if (filterOptions.saved_metric_name) { - return new AdhocFilter({ - expressionType: - props.datasource.type === 'druid' - ? EXPRESSION_TYPES.SIMPLE - : EXPRESSION_TYPES.SQL, - subject: - props.datasource.type === 'druid' - ? filterOptions.saved_metric_name - : getMetricExpression(filterOptions.saved_metric_name), - operator: OPERATORS['>'], - comparator: 0, - clause: CLAUSES.HAVING, - }); - } - // has a custom label, meaning it's custom column - if (filterOptions.label) { - return new AdhocFilter({ - expressionType: - props.datasource.type === 'druid' - ? EXPRESSION_TYPES.SIMPLE - : EXPRESSION_TYPES.SQL, - subject: - props.datasource.type === 'druid' - ? filterOptions.label - : new AdhocMetric(option).translateToSql(), - operator: OPERATORS['>'], - comparator: 0, - clause: CLAUSES.HAVING, - }); - } - // add a new filter item - if (filterOptions.column_name) { - return new AdhocFilter({ - expressionType: EXPRESSION_TYPES.SIMPLE, - subject: filterOptions.column_name, - operator: OPERATORS['=='], - comparator: '', - clause: CLAUSES.WHERE, - isNew: true, - }); - } - return null; - }; + const mapOption = useCallback( + (option: OptionValueType) => { + // already a AdhocFilter, skip + if (option instanceof AdhocFilter) { + return option; + } + const filterOptions = option as Record; + // via datasource saved metric + if (filterOptions.saved_metric_name) { + return new AdhocFilter({ + expressionType: + datasource.type === 'druid' + ? EXPRESSION_TYPES.SIMPLE + : EXPRESSION_TYPES.SQL, + subject: + datasource.type === 'druid' + ? filterOptions.saved_metric_name + : getMetricExpression(filterOptions.saved_metric_name), + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, + operatorId: Operators.GREATER_THAN, + comparator: 0, + clause: CLAUSES.HAVING, + }); + } + // has a custom label, meaning it's custom column + if (filterOptions.label) { + return new AdhocFilter({ + expressionType: + datasource.type === 'druid' + ? EXPRESSION_TYPES.SIMPLE + : EXPRESSION_TYPES.SQL, + subject: + datasource.type === 'druid' + ? filterOptions.label + : new AdhocMetric(option).translateToSql(), + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, + operatorId: Operators.GREATER_THAN, + comparator: 0, + clause: CLAUSES.HAVING, + }); + } + // add a new filter item + if (filterOptions.column_name) { + return new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: filterOptions.column_name, + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.EQUALS].operation, + operatorId: Operators.EQUALS, + comparator: '', + clause: CLAUSES.WHERE, + isNew: true, + }); + } + return null; + }, + [datasource.type, getMetricExpression], + ); - const onFilterEdit = (changedFilter: AdhocFilter) => { - props.onChange( - values.map((value: AdhocFilter) => { - if (value.filterOptionName === changedFilter.filterOptionName) { - return changedFilter; - } - return value; - }), - ); - }; + const onFilterEdit = useCallback( + (changedFilter: AdhocFilter) => { + onChange( + values.map((value: AdhocFilter) => { + if (value.filterOptionName === changedFilter.filterOptionName) { + return changedFilter; + } + return value; + }), + ); + }, + [onChange, values], + ); - const onNewFilter = (newFilter: AdhocFilter) => { - const mappedOption = mapOption(newFilter); - if (mappedOption) { - const newValues = [...values, mappedOption]; - setValues(newValues); - props.onChange(newValues); - } - }; + const onNewFilter = useCallback( + (newFilter: AdhocFilter) => { + const mappedOption = mapOption(newFilter); + if (mappedOption) { + const newValues = [...values, mappedOption]; + setValues(newValues); + onChange(newValues); + } + }, + [mapOption, onChange, values], + ); - const togglePopover = (visible: boolean) => { + const togglePopover = useCallback((visible: boolean) => { setNewFilterPopoverVisible(visible); - }; + }, []); - const closePopover = () => { + const closePopover = useCallback(() => { togglePopover(false); - }; + }, [togglePopover]); - const valuesRenderer = () => - values.map((adhocFilter: AdhocFilter, index: number) => { - const label = adhocFilter.getDefaultLabel(); - return ( - - + values.map((adhocFilter: AdhocFilter, index: number) => { + const label = adhocFilter.getDefaultLabel(); + const tooltipTitle = adhocFilter.getTooltipTitle(); + return ( + - {label} - - - ); - }); + + + ); + }), + [ + onClickClose, + onFilterEdit, + onShiftOptions, + options, + partitionColumn, + datasource, + values, + ], + ); + + const handleClickGhostButton = useCallback(() => { + setDroppedItem(null); + togglePopover(true); + }, [togglePopover]); const adhocFilter = useMemo(() => { if (droppedItem?.metric_name) { @@ -295,40 +353,54 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => { sqlExpression: (droppedItem as AdhocMetric)?.translateToSql(), }); } - return new AdhocFilter({ + const config: Partial = { subject: (droppedItem as ColumnMeta)?.column_name, - }); + }; + if (config.subject && isFeatureEnabled(FeatureFlag.UX_BETA)) { + config.operator = OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.IN].operation; + config.operatorId = Operators.IN; + } + return new AdhocFilter(config); }, [droppedItem]); + const canDrop = useCallback(() => true, []); + const handleDrop = useCallback( + (item: DatasourcePanelDndItem) => { + setDroppedItem(item.value); + togglePopover(true); + }, + [togglePopover], + ); + + const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) + ? t('Drop columns/metrics here or click') + : t('Drop columns or metrics here'); + return ( <> - onDrop={(item: DatasourcePanelDndItem) => { - setDroppedItem(item.value); - togglePopover(true); - }} - canDrop={() => true} + onDrop={handleDrop} + canDrop={canDrop} valuesRenderer={valuesRenderer} - accept={[ - DndItemType.Column, - DndItemType.Metric, - DndItemType.MetricOption, - DndItemType.AdhocMetricOption, - ]} - ghostButtonText={t('Drop columns or metrics')} + accept={DND_ACCEPTED_TYPES} + ghostButtonText={ghostButtonText} + onClickGhostButton={ + isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) + ? handleClickGhostButton + : undefined + } {...props} />
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx index 51fb9aac3ba2..71cf4bf5480b 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.test.tsx @@ -31,10 +31,10 @@ const defaultProps = { test('renders with default props', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop column or metric')).toBeInTheDocument(); + expect(screen.getByText('Drop column or metric here')).toBeInTheDocument(); }); test('renders with default props and multi = true', () => { render(, { useDnd: true }); - expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument(); + expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument(); }); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx index a3daeaf70211..8f3850525ff0 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndMetricSelect.tsx @@ -18,7 +18,14 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { ensureIsArray, Metric, tn } from '@superset-ui/core'; +import { + ensureIsArray, + FeatureFlag, + GenericDataType, + isFeatureEnabled, + Metric, + tn, +} from '@superset-ui/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; import { isEqual } from 'lodash'; import { usePrevious } from 'src/common/hooks/usePrevious'; @@ -30,6 +37,10 @@ import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/t import { DndItemType } from 'src/explore/components/DndItemType'; import DndSelectLabel from 'src/explore/components/controls/DndColumnSelectControl/DndSelectLabel'; import { savedMetricType } from 'src/explore/components/controls/MetricControl/types'; +import { AGGREGATES } from 'src/explore/constants'; + +const EMPTY_OBJECT = {}; +const DND_ACCEPTED_TYPES = [DndItemType.Column, DndItemType.Metric]; const isDictionaryForAdhocMetric = (value: any) => value && !(value instanceof AdhocMetric) && value.expressionType; @@ -154,112 +165,191 @@ export const DndMetricSelect = (props: any) => { onChange, ]); - const canDrop = (item: DatasourcePanelDndItem) => { - const isMetricAlreadyInValues = - item.type === 'metric' ? value.includes(item.value.metric_name) : false; - return (props.multi || value.length === 0) && !isMetricAlreadyInValues; - }; + const canDrop = useCallback( + (item: DatasourcePanelDndItem) => { + const isMetricAlreadyInValues = + item.type === 'metric' ? value.includes(item.value.metric_name) : false; + return !isMetricAlreadyInValues; + }, + [value], + ); - const onNewMetric = (newMetric: Metric) => { - const newValue = [...value, newMetric]; - setValue(newValue); - handleChange(newValue); - }; + const onNewMetric = useCallback( + (newMetric: Metric) => { + const newValue = props.multi ? [...value, newMetric] : [newMetric]; + setValue(newValue); + handleChange(newValue); + }, + [handleChange, props.multi, value], + ); - const onMetricEdit = ( - changedMetric: Metric | AdhocMetric, - oldMetric: Metric | AdhocMetric, - ) => { - const newValue = value.map(value => { - if ( - // compare saved metrics - value === (oldMetric as Metric).metric_name || - // compare adhoc metrics - typeof (value as AdhocMetric).optionName !== 'undefined' - ? (value as AdhocMetric).optionName === - (oldMetric as AdhocMetric).optionName - : false - ) { - return changedMetric; + const onMetricEdit = useCallback( + (changedMetric: Metric | AdhocMetric, oldMetric: Metric | AdhocMetric) => { + if (oldMetric instanceof AdhocMetric && oldMetric.equals(changedMetric)) { + return; } - return value; - }); - setValue(newValue); - handleChange(newValue); - }; + const newValue = value.map(value => { + if ( + // compare saved metrics + value === (oldMetric as Metric).metric_name || + // compare adhoc metrics + typeof (value as AdhocMetric).optionName !== 'undefined' + ? (value as AdhocMetric).optionName === + (oldMetric as AdhocMetric).optionName + : false + ) { + return changedMetric; + } + return value; + }); + setValue(newValue); + handleChange(newValue); + }, + [handleChange, value], + ); - const onRemoveMetric = (index: number) => { - if (!Array.isArray(value)) { - return; - } - const valuesCopy = [...value]; - valuesCopy.splice(index, 1); - setValue(valuesCopy); - onChange(valuesCopy); - }; + const onRemoveMetric = useCallback( + (index: number) => { + if (!Array.isArray(value)) { + return; + } + const valuesCopy = [...value]; + valuesCopy.splice(index, 1); + setValue(valuesCopy); + onChange(valuesCopy); + }, + [onChange, value], + ); - const moveLabel = (dragIndex: number, hoverIndex: number) => { - const newValues = [...value]; - [newValues[hoverIndex], newValues[dragIndex]] = [ - newValues[dragIndex], - newValues[hoverIndex], - ]; - setValue(newValues); - }; + const moveLabel = useCallback( + (dragIndex: number, hoverIndex: number) => { + const newValues = [...value]; + [newValues[hoverIndex], newValues[dragIndex]] = [ + newValues[dragIndex], + newValues[hoverIndex], + ]; + setValue(newValues); + }, + [value], + ); + + const newSavedMetricOptions = useMemo( + () => getOptionsForSavedMetrics(props.savedMetrics, props.value), + [props.savedMetrics, props.value], + ); - const valueRenderer = ( - option: Metric | AdhocMetric | string, - index: number, - ) => ( - onRemoveMetric(index)} - columns={props.columns} - savedMetrics={props.savedMetrics} - savedMetricsOptions={getOptionsForSavedMetrics( + const getSavedMetricOptionsForMetric = useCallback( + index => + getOptionsForSavedMetrics( props.savedMetrics, props.value, props.value?.[index], - )} - datasourceType={props.datasourceType} - onMoveLabel={moveLabel} - onDropLabel={() => onChange(value)} - /> + ), + [props.savedMetrics, props.value], ); - const valuesRenderer = () => - value.map((value, index) => valueRenderer(value, index)); + const handleDropLabel = useCallback( + () => onChange(multi ? value : value[0]), + [multi, onChange, value], + ); + + const valueRenderer = useCallback( + (option: Metric | AdhocMetric | string, index: number) => ( + + ), + [ + getSavedMetricOptionsForMetric, + handleDropLabel, + moveLabel, + multi, + onMetricEdit, + onRemoveMetric, + props.columns, + props.datasourceType, + props.label, + props.name, + props.savedMetrics, + ], + ); + + const valuesRenderer = useCallback( + () => value.map((value, index) => valueRenderer(value, index)), + [value, valueRenderer], + ); - const togglePopover = (visible: boolean) => { + const togglePopover = useCallback((visible: boolean) => { setNewMetricPopoverVisible(visible); - }; + }, []); - const closePopover = () => { + const closePopover = useCallback(() => { togglePopover(false); - }; + }, [togglePopover]); - const handleDrop = (item: DatasourcePanelDndItem) => { - if (item.type === DndItemType.Metric) { - onNewMetric(item.value as Metric); - } - if (item.type === DndItemType.Column) { - setDroppedItem(item); - togglePopover(true); - } - }; + const handleDrop = useCallback( + (item: DatasourcePanelDndItem) => { + if (item.type === DndItemType.Metric) { + onNewMetric(item.value as Metric); + } + if (item.type === DndItemType.Column) { + setDroppedItem(item); + togglePopover(true); + } + }, + [onNewMetric, togglePopover], + ); + + const handleClickGhostButton = useCallback(() => { + setDroppedItem(null); + togglePopover(true); + }, [togglePopover]); const adhocMetric = useMemo(() => { if (droppedItem?.type === DndItemType.Column) { const itemValue = droppedItem?.value as ColumnMeta; - return new AdhocMetric({ + const config: Partial = { column: { column_name: itemValue?.column_name }, - }); + }; + if (isFeatureEnabled(FeatureFlag.UX_BETA)) { + if (itemValue.type_generic === GenericDataType.NUMERIC) { + config.aggregate = AGGREGATES.SUM; + } else if ( + itemValue.type_generic === GenericDataType.STRING || + itemValue.type_generic === GenericDataType.BOOLEAN || + itemValue.type_generic === GenericDataType.TEMPORAL + ) { + config.aggregate = AGGREGATES.COUNT_DISTINCT; + } + } + return new AdhocMetric(config); } return new AdhocMetric({ isNew: true }); - }, [droppedItem?.type, droppedItem?.value]); + }, [droppedItem]); + + const ghostButtonText = isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) + ? tn( + 'Drop a column/metric here or click', + 'Drop columns/metrics here or click', + multi ? 2 : 1, + ) + : tn( + 'Drop column or metric here', + 'Drop columns or metrics here', + multi ? 2 : 1, + ); return (
@@ -267,30 +357,27 @@ export const DndMetricSelect = (props: any) => { onDrop={handleDrop} canDrop={canDrop} valuesRenderer={valuesRenderer} - accept={[DndItemType.Column, DndItemType.Metric]} - ghostButtonText={tn( - 'Drop column or metric', - 'Drop columns or metrics', - multi ? 2 : 1, - )} + accept={DND_ACCEPTED_TYPES} + ghostButtonText={ghostButtonText} displayGhostButton={multi || value.length === 0} + onClickGhostButton={ + isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) + ? handleClickGhostButton + : undefined + } {...props} />
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.test.tsx index d6090e2a269d..d9689d7e8d23 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.test.tsx @@ -33,7 +33,7 @@ const defaultProps = { test('renders with default props', async () => { render(, { useDnd: true }); - expect(await screen.findByText('Drop columns')).toBeInTheDocument(); + expect(await screen.findByText('Drop columns here')).toBeInTheDocument(); }); test('renders ghost button when empty', async () => { diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx index daaeb9185285..d3bf95d09002 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx @@ -40,9 +40,11 @@ export default function DndSelectLabel({ drop: (item: DatasourcePanelDndItem) => { props.onDrop(item); + props.onDropValue?.(item.value); }, - canDrop: (item: DatasourcePanelDndItem) => props.canDrop(item), + canDrop: (item: DatasourcePanelDndItem) => + props.canDrop(item) && (props.canDropValue?.(item.value) ?? true), collect: monitor => ({ isOver: monitor.isOver(), @@ -53,9 +55,12 @@ export default function DndSelectLabel({ function renderGhostButton() { return ( - + - {t(props.ghostButtonText || 'Drop columns')} + {t(props.ghostButtonText || 'Drop columns here')} ); } diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx index 7a85997dbfe1..744fe03a0955 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx @@ -44,6 +44,17 @@ test('renders with caret', () => { expect(screen.getByRole('img', { name: 'caret-right' })).toBeInTheDocument(); }); +test('renders with extra triangle', () => { + render( + , + ); + expect( + screen.getByRole('button', { name: 'Show info tooltip' }), + ).toBeInTheDocument(); +}); + test('triggers onClose', () => { const clickClose = jest.fn(); render( diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx index 5b589f0972c3..3c7ee3c7c14b 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { useTheme } from '@superset-ui/core'; +import React, { useCallback } from 'react'; +import { styled, t, useTheme } from '@superset-ui/core'; import Icons from 'src/components/Icons'; import { CaretContainer, @@ -26,23 +26,52 @@ import { Label, } from 'src/explore/components/controls/OptionControls'; import { OptionProps } from 'src/explore/components/controls/DndColumnSelectControl/types'; +import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; -export default function Option(props: OptionProps) { +const StyledInfoTooltipWithTrigger = styled(InfoTooltipWithTrigger)` + margin: 0 ${({ theme }) => theme.gridUnit}px; +`; + +export default function Option({ + children, + index, + clickClose, + withCaret, + isExtra, + canDelete = true, +}: OptionProps) { const theme = useTheme(); + const onClickClose = useCallback( + e => { + e.stopPropagation(); + clickClose(index); + }, + [clickClose, index], + ); return ( - - props.clickClose(props.index)} - > - - - - {props.withCaret && ( + + {canDelete && ( + + + + )} + + {isExtra && ( + + )} + {withCaret && ( diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.test.tsx index c46f49be0107..e237cea989a5 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.test.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.test.tsx @@ -28,9 +28,8 @@ test('renders with default props', () => { clickClose={jest.fn()} type={'Column' as DndItemType} onShiftOptions={jest.fn()} - > - Option - , + label="Option" + />, { useDnd: true }, ); expect(container).toBeInTheDocument(); @@ -46,17 +45,15 @@ test('triggers onShiftOptions on drop', () => { clickClose={jest.fn()} type={'Column' as DndItemType} onShiftOptions={onShiftOptions} - > - Option 1 - + label="Option 1" + /> - Option 2 - + label="Option 2" + /> , { useDnd: true }, ); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.tsx index cf947074d17c..fc2399e81a81 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.tsx +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/OptionWrapper.tsx @@ -24,36 +24,50 @@ import { DragSourceMonitor, } from 'react-dnd'; import { DragContainer } from 'src/explore/components/controls/OptionControls'; -import { DndItemType } from 'src/explore/components/DndItemType'; import { OptionProps, OptionItemInterface, } from 'src/explore/components/controls/DndColumnSelectControl/types'; +import { Tooltip } from 'src/components/Tooltip'; +import { StyledColumnOption } from 'src/explore/components/optionRenderers'; +import { styled } from '@superset-ui/core'; +import { ColumnMeta } from '@superset-ui/chart-controls'; import Option from './Option'; +export const OptionLabel = styled.div` + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + export default function OptionWrapper( props: OptionProps & { - type: DndItemType; + type: string; onShiftOptions: (dragIndex: number, hoverIndex: number) => void; }, ) { const { index, + label, + tooltipTitle, + column, type, onShiftOptions, clickClose, withCaret, - children, + isExtra, + canDelete = true, ...rest } = props; const ref = useRef(null); + const labelRef = useRef(null); - const item: OptionItemInterface = { - dragIndex: index, - type, - }; - const [, drag] = useDrag({ - item, + const [{ isDragging }, drag] = useDrag({ + item: { + type, + dragIndex: index, + }, collect: (monitor: DragSourceMonitor) => ({ isDragging: monitor.isDragging(), }), @@ -81,8 +95,8 @@ export default function OptionWrapper( // Determine mouse position const clientOffset = monitor.getClientOffset(); // Get pixels to the top - const hoverClientY = clientOffset?.y - ? clientOffset?.y - hoverBoundingRect.top + const hoverClientY = clientOffset + ? clientOffset.y - hoverBoundingRect.top : 0; // Only perform the move when the mouse has crossed half of the items height // When dragging downwards, only move when the cursor is below 50% @@ -103,12 +117,63 @@ export default function OptionWrapper( }, }); + const shouldShowTooltip = + (!isDragging && tooltipTitle && label && tooltipTitle !== label) || + (!isDragging && + labelRef && + labelRef.current && + labelRef.current.scrollWidth > labelRef.current.clientWidth); + + const LabelContent = () => { + if (!shouldShowTooltip) { + return {label}; + } + return ( + + {label} + + ); + }; + + const ColumnOption = () => ( + + ); + + const Label = () => { + if (label) { + return ( + + + + ); + } + if (column) { + return ( + + + + ); + } + return null; + }; + drag(drop(ref)); return ( - ); diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts index 5ba1e06abb45..b77e9d263341 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/types.ts @@ -19,14 +19,22 @@ import { ReactNode } from 'react'; import { Metric } from '@superset-ui/core'; import { ColumnMeta } from '@superset-ui/chart-controls'; -import { DatasourcePanelDndItem } from '../../DatasourcePanel/types'; +import { + DatasourcePanelDndItem, + DndItemValue, +} from '../../DatasourcePanel/types'; import { DndItemType } from '../../DndItemType'; export interface OptionProps { - children: ReactNode; + children?: ReactNode; index: number; + label?: string; + tooltipTitle?: string; + column?: ColumnMeta; clickClose: (index: number) => void; withCaret?: boolean; + isExtra?: boolean; + canDelete?: boolean; } export interface OptionItemInterface { @@ -40,6 +48,9 @@ export interface LabelProps { onChange: (value?: T) => void; options: { string: ColumnMeta }; multi?: boolean; + canDelete?: boolean; + ghostButtonText?: string; + label?: string; } export interface DndColumnSelectProps< @@ -48,10 +59,13 @@ export interface DndColumnSelectProps< > extends LabelProps { onDrop: (item: DatasourcePanelDndItem) => void; canDrop: (item: DatasourcePanelDndItem) => boolean; + canDropValue?: (value: DndItemValue) => boolean; + onDropValue?: (value: DndItemValue) => void; valuesRenderer: () => ReactNode; accept: DndItemType | DndItemType[]; ghostButtonText?: string; displayGhostButton?: boolean; + onClickGhostButton?: () => void; } export type OptionValueType = Record; diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts index 37833d943ce6..532aa33fc632 100644 --- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts +++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/utils/optionSelector.ts @@ -17,28 +17,23 @@ * under the License. */ import { ColumnMeta } from '@superset-ui/chart-controls'; +import { ensureIsArray } from '@superset-ui/core'; export class OptionSelector { values: ColumnMeta[]; options: { string: ColumnMeta }; - isArray: boolean; + multi: boolean; constructor( options: { string: ColumnMeta }, + multi: boolean, initialValues?: string[] | string, ) { this.options = options; - let values: string[]; - if (Array.isArray(initialValues)) { - values = initialValues; - this.isArray = true; - } else { - values = initialValues ? [initialValues] : []; - this.isArray = false; - } - this.values = values + this.multi = multi; + this.values = ensureIsArray(initialValues) .map(value => { if (value in options) { return options[value]; @@ -68,12 +63,12 @@ export class OptionSelector { [this.values[a], this.values[b]] = [this.values[b], this.values[a]]; } - has(groupBy: string): boolean { - return !!this.getValues()?.includes(groupBy); + has(value: string): boolean { + return !!this.getValues()?.includes(value); } getValues(): string[] | string | undefined { - if (!this.isArray) { + if (!this.multi) { return this.values.length > 0 ? this.values[0].column_name : undefined; } return this.values.map(option => option.column_name); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js index 90dcdf0fbbe6..2aa5fd160418 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js @@ -20,6 +20,7 @@ import AdhocFilter, { EXPRESSION_TYPES, CLAUSES, } from 'src/explore/components/controls/FilterControl/AdhocFilter'; +import { Operators } from 'src/explore/constants'; describe('AdhocFilter', () => { it('sets filterOptionName in constructor', () => { @@ -188,6 +189,22 @@ describe('AdhocFilter', () => { }); // eslint-disable-next-line no-unused-expressions expect(adhocFilter8.isValid()).toBe(false); + + const adhocFilter9 = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operator: 'IS NULL', + clause: CLAUSES.WHERE, + }); + expect(adhocFilter9.isValid()).toBe(true); + const adhocFilter10 = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operator: 'IS NOT NULL', + clause: CLAUSES.WHERE, + }); + // eslint-disable-next-line no-unused-expressions + expect(adhocFilter10.isValid()).toBe(true); }); it('can translate from simple expressions to sql expressions', () => { @@ -209,4 +226,26 @@ describe('AdhocFilter', () => { }); expect(adhocFilter2.translateToSql()).toBe('SUM(value) <> 5'); }); + it('sets comparator to null when operator is IS_NULL', () => { + const adhocFilter2 = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'SUM(value)', + operator: 'IS NULL', + operatorId: Operators.IS_NULL, + comparator: '5', + clause: CLAUSES.HAVING, + }); + expect(adhocFilter2.comparator).toBe(null); + }); + it('sets comparator to null when operator is IS_NOT_NULL', () => { + const adhocFilter2 = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'SUM(value)', + operator: 'IS NOT NULL', + operatorId: Operators.IS_NOT_NULL, + comparator: '5', + clause: CLAUSES.HAVING, + }); + expect(adhocFilter2.comparator).toBe(null); + }); }); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js index c9c661b5b8a5..f6f00271ace6 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js @@ -16,7 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { CUSTOM_OPERATORS, OPERATORS } from 'src/explore/constants'; +import { + CUSTOM_OPERATORS, + Operators, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; import { getSimpleSQLExpression } from 'src/explore/exploreUtils'; export const EXPRESSION_TYPES = { @@ -49,11 +53,16 @@ const OPERATORS_TO_SQL = { `= '{{ presto.latest_partition('${datasource.schema}.${datasource.datasource_name}') }}'`, }; +const CUSTOM_OPERATIONS = [...CUSTOM_OPERATORS].map( + op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, +); + function translateToSql(adhocMetric, { useSimple } = {}) { if (adhocMetric.expressionType === EXPRESSION_TYPES.SIMPLE || useSimple) { const { subject, comparator } = adhocMetric; const operator = - adhocMetric.operator && CUSTOM_OPERATORS.has(adhocMetric.operator) + adhocMetric.operator && + CUSTOM_OPERATIONS.indexOf(adhocMetric.operator) >= 0 ? OPERATORS_TO_SQL[adhocMetric.operator](adhocMetric) : OPERATORS_TO_SQL[adhocMetric.operator]; return getSimpleSQLExpression(subject, operator, comparator); @@ -70,7 +79,22 @@ export default class AdhocFilter { if (this.expressionType === EXPRESSION_TYPES.SIMPLE) { this.subject = adhocFilter.subject; this.operator = adhocFilter.operator?.toUpperCase(); + this.operatorId = adhocFilter.operatorId; this.comparator = adhocFilter.comparator; + if ( + [Operators.IS_TRUE, Operators.IS_FALSE].indexOf( + adhocFilter.operatorId, + ) >= 0 + ) { + this.comparator = adhocFilter.operatorId === Operators.IS_TRUE; + } + if ( + [Operators.IS_NULL, Operators.IS_NOT_NULL].indexOf( + adhocFilter.operatorId, + ) >= 0 + ) { + this.comparator = null; + } this.clause = adhocFilter.clause || CLAUSES.WHERE; this.sqlExpression = null; } else if (this.expressionType === EXPRESSION_TYPES.SQL) { @@ -79,9 +103,13 @@ export default class AdhocFilter { ? adhocFilter.sqlExpression : translateToSql(adhocFilter, { useSimple: true }); this.clause = adhocFilter.clause; - if (adhocFilter.operator && CUSTOM_OPERATORS.has(adhocFilter.operator)) { + if ( + adhocFilter.operator && + CUSTOM_OPERATIONS.indexOf(adhocFilter.operator) >= 0 + ) { this.subject = adhocFilter.subject; this.operator = adhocFilter.operator; + this.operatorId = adhocFilter.operatorId; } else { this.subject = null; this.operator = null; @@ -112,24 +140,26 @@ export default class AdhocFilter { adhocFilter.expressionType === this.expressionType && adhocFilter.sqlExpression === this.sqlExpression && adhocFilter.operator === this.operator && + adhocFilter.operatorId === this.operatorId && adhocFilter.comparator === this.comparator && adhocFilter.subject === this.subject ); } isValid() { + const nullCheckOperators = [Operators.IS_NOT_NULL, Operators.IS_NULL].map( + op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, + ); + const truthCheckOperators = [Operators.IS_TRUE, Operators.IS_FALSE].map( + op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, + ); if (this.expressionType === EXPRESSION_TYPES.SIMPLE) { - if ( - [ - OPERATORS['IS TRUE'], - OPERATORS['IS FALSE'], - OPERATORS['IS NULL'], - OPERATORS['IS NOT NULL'], - ].indexOf(this.operator) >= 0 - ) { + if (nullCheckOperators.indexOf(this.operator) >= 0) { return !!(this.operator && this.subject); } - + if (truthCheckOperators.indexOf(this.operator) >= 0) { + return !!(this.subject && this.comparator !== null); + } if (this.operator && this.subject && this.clause) { if (Array.isArray(this.comparator)) { if (this.comparator.length > 0) { diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/AdhocFilterControl.test.jsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/AdhocFilterControl.test.jsx index eb3f5e8496c6..c7b13d55fa95 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/AdhocFilterControl.test.jsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/AdhocFilterControl.test.jsx @@ -27,14 +27,18 @@ import AdhocFilter, { CLAUSES, } from 'src/explore/components/controls/FilterControl/AdhocFilter'; import { LabelsContainer } from 'src/explore/components/controls/OptionControls'; -import { AGGREGATES, OPERATORS } from 'src/explore/constants'; +import { + AGGREGATES, + Operators, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; import AdhocFilterControl from '.'; const simpleAdhocFilter = new AdhocFilter({ expressionType: EXPRESSION_TYPES.SIMPLE, subject: 'value', - operator: '>', + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, comparator: '10', clause: CLAUSES.WHERE, }); @@ -92,7 +96,8 @@ describe('AdhocFilterControl', () => { new AdhocFilter({ expressionType: EXPRESSION_TYPES.SQL, subject: savedMetric.expression, - operator: OPERATORS['>'], + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, comparator: 0, clause: CLAUSES.HAVING, }), @@ -111,7 +116,8 @@ describe('AdhocFilterControl', () => { new AdhocFilter({ expressionType: EXPRESSION_TYPES.SQL, subject: sumValueAdhocMetric.label, - operator: OPERATORS['>'], + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, comparator: 0, clause: CLAUSES.HAVING, }), @@ -134,7 +140,7 @@ describe('AdhocFilterControl', () => { new AdhocFilter({ expressionType: EXPRESSION_TYPES.SIMPLE, subject: columns[0].column_name, - operator: OPERATORS['=='], + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.EQUALS].operation, comparator: '', clause: CLAUSES.WHERE, }), diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/index.jsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/index.jsx index 55e926c1800c..414745467e57 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterControl/index.jsx @@ -30,7 +30,10 @@ import ControlHeader from 'src/explore/components/ControlHeader'; import adhocMetricType from 'src/explore/components/controls/MetricControl/adhocMetricType'; import savedMetricType from 'src/explore/components/controls/MetricControl/savedMetricType'; import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; -import { OPERATORS } from 'src/explore/constants'; +import { + Operators, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; import FilterDefinitionOption from 'src/explore/components/controls/MetricControl/FilterDefinitionOption'; import { AddControlLabel, @@ -242,7 +245,8 @@ class AdhocFilterControl extends React.Component { this.props.datasource.type === 'druid' ? option.saved_metric_name : this.getMetricExpression(option.saved_metric_name), - operator: OPERATORS['>'], + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, comparator: 0, clause: CLAUSES.HAVING, }); @@ -258,7 +262,8 @@ class AdhocFilterControl extends React.Component { this.props.datasource.type === 'druid' ? option.label : new AdhocMetric(option).translateToSql(), - operator: OPERATORS['>'], + operator: + OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, comparator: 0, clause: CLAUSES.HAVING, }); @@ -268,7 +273,7 @@ class AdhocFilterControl extends React.Component { return new AdhocFilter({ expressionType: EXPRESSION_TYPES.SIMPLE, subject: option.column_name, - operator: OPERATORS['=='], + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.EQUALS].operation, comparator: '', clause: CLAUSES.WHERE, isNew: true, @@ -324,7 +329,6 @@ class AdhocFilterControl extends React.Component { options={this.state.options} onFilterEdit={this.onNewFilter} partitionColumn={this.state.partitionColumn} - createNew > {trigger} diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.jsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.jsx deleted file mode 100644 index 51678db676b9..000000000000 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.jsx +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -/* eslint-disable no-unused-expressions */ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import AdhocFilter, { - EXPRESSION_TYPES, - CLAUSES, -} from 'src/explore/components/controls/FilterControl/AdhocFilter'; -import { AGGREGATES } from 'src/explore/constants'; -import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; -import AdhocFilterEditPopoverSimpleTabContent from '.'; - -const simpleAdhocFilter = new AdhocFilter({ - expressionType: EXPRESSION_TYPES.SIMPLE, - subject: 'value', - operator: '>', - comparator: '10', - clause: CLAUSES.WHERE, -}); - -const simpleMultiAdhocFilter = new AdhocFilter({ - expressionType: EXPRESSION_TYPES.SIMPLE, - subject: 'value', - operator: 'IN', - comparator: ['10'], - clause: CLAUSES.WHERE, -}); - -const sumValueAdhocMetric = new AdhocMetric({ - expressionType: EXPRESSION_TYPES.SIMPLE, - column: { type: 'VARCHAR(255)', column_name: 'source' }, - aggregate: AGGREGATES.SUM, -}); - -const simpleCustomFilter = new AdhocFilter({ - expressionType: EXPRESSION_TYPES.SIMPLE, - subject: 'ds', - operator: 'LATEST PARTITION', -}); - -const options = [ - { type: 'VARCHAR(255)', column_name: 'source', id: 1 }, - { type: 'VARCHAR(255)', column_name: 'target', id: 2 }, - { type: 'DOUBLE', column_name: 'value', id: 3 }, - { saved_metric_name: 'my_custom_metric', id: 4 }, - sumValueAdhocMetric, -]; - -function setup(overrides) { - const onChange = sinon.spy(); - const onHeightChange = sinon.spy(); - const props = { - adhocFilter: simpleAdhocFilter, - onChange, - onHeightChange, - options, - datasource: {}, - ...overrides, - }; - const wrapper = shallow( - , - ); - return { wrapper, onChange, onHeightChange }; -} - -describe('AdhocFilterEditPopoverSimpleTabContent', () => { - it('renders the simple tab form', () => { - const { wrapper } = setup(); - expect(wrapper).toExist(); - }); - - it('passes the new adhocFilter to onChange after onSubjectChange', () => { - const { wrapper, onChange } = setup(); - wrapper.instance().onSubjectChange(1); - expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0]).toEqual( - simpleAdhocFilter.duplicateWith({ subject: 'source' }), - ); - }); - - it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => { - const { wrapper, onChange } = setup(); - wrapper.instance().onSubjectChange(sumValueAdhocMetric.optionName); - expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0]).toEqual( - simpleAdhocFilter.duplicateWith({ - subject: sumValueAdhocMetric.label, - clause: CLAUSES.HAVING, - }), - ); - }); - - it('will convert from individual comparator to array if the operator changes to multi', () => { - const { wrapper, onChange } = setup(); - wrapper.instance().onOperatorChange('IN'); - expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0]).toEqual( - simpleAdhocFilter.duplicateWith({ operator: 'IN', comparator: ['10'] }), - ); - }); - - it('will convert from array to individual comparators if the operator changes from multi', () => { - const { wrapper, onChange } = setup({ - adhocFilter: simpleMultiAdhocFilter, - }); - wrapper.instance().onOperatorChange('<'); - expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0]).toEqual( - simpleMultiAdhocFilter.duplicateWith({ operator: '<', comparator: '10' }), - ); - }); - - it('passes the new adhocFilter to onChange after onComparatorChange', () => { - const { wrapper, onChange } = setup(); - wrapper.instance().onComparatorChange('20'); - expect(onChange.calledOnce).toBe(true); - expect(onChange.lastCall.args[0]).toEqual( - simpleAdhocFilter.duplicateWith({ comparator: '20' }), - ); - }); - - it('will filter operators for table datasources', () => { - const { wrapper } = setup({ datasource: { type: 'table' } }); - expect(wrapper.instance().isOperatorRelevant('REGEX')).toBe(false); - expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(true); - }); - - it('will filter operators for druid datasources', () => { - const { wrapper } = setup({ datasource: { type: 'druid' } }); - expect(wrapper.instance().isOperatorRelevant('REGEX')).toBe(true); - expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(false); - }); - - it('will show LATEST PARTITION operator', () => { - const { wrapper } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - }, - adhocFilter: simpleCustomFilter, - partitionColumn: 'ds', - }); - - expect( - wrapper.instance().isOperatorRelevant('LATEST PARTITION', 'ds'), - ).toBe(true); - expect( - wrapper.instance().isOperatorRelevant('LATEST PARTITION', 'value'), - ).toBe(false); - }); - - it('will generate custom sqlExpression for LATEST PARTITION operator', () => { - const testAdhocFilter = new AdhocFilter({ - expressionType: EXPRESSION_TYPES.SIMPLE, - subject: 'ds', - }); - const { wrapper, onChange } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - }, - adhocFilter: testAdhocFilter, - partitionColumn: 'ds', - }); - - wrapper.instance().onOperatorChange('LATEST PARTITION'); - expect(onChange.lastCall.args[0]).toEqual( - testAdhocFilter.duplicateWith({ - subject: 'ds', - operator: 'LATEST PARTITION', - comparator: null, - clause: 'WHERE', - expressionType: 'SQL', - sqlExpression: "ds = '{{ presto.latest_partition('schema.table1') }}'", - }), - ); - }); - it('will display boolean operators only when column type is boolean', () => { - const { wrapper } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - columns: [{ column_name: 'value', type: 'BOOL' }], - }, - adhocFilter: simpleAdhocFilter, - }); - const booleanOnlyOperators = [ - 'IS TRUE', - 'IS FALSE', - 'IS NULL', - 'IS NOT NULL', - ]; - booleanOnlyOperators.forEach(operator => { - expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe( - true, - ); - }); - }); - it('will display boolean operators when column type is number', () => { - const { wrapper } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - columns: [{ column_name: 'value', type: 'INT' }], - }, - adhocFilter: simpleAdhocFilter, - }); - const booleanOnlyOperators = ['IS TRUE', 'IS FALSE']; - booleanOnlyOperators.forEach(operator => { - expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe( - true, - ); - }); - }); - it('will not display boolean operators when column type is string', () => { - const { wrapper } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - columns: [{ column_name: 'value', type: 'STRING' }], - }, - adhocFilter: simpleAdhocFilter, - }); - const booleanOnlyOperators = ['IS TRUE', 'IS FALSE']; - booleanOnlyOperators.forEach(operator => { - expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe( - false, - ); - }); - }); - it('will display boolean operators when column is an expression', () => { - const { wrapper } = setup({ - datasource: { - type: 'table', - datasource_name: 'table1', - schema: 'schema', - columns: [ - { - column_name: 'value', - expression: 'case when value is 0 then "NO"', - }, - ], - }, - adhocFilter: simpleAdhocFilter, - }); - const booleanOnlyOperators = ['IS TRUE', 'IS FALSE']; - booleanOnlyOperators.forEach(operator => { - expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe( - true, - ); - }); - }); -}); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx new file mode 100644 index 000000000000..947614c6c11c --- /dev/null +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx @@ -0,0 +1,322 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import AdhocFilter, { + EXPRESSION_TYPES, + CLAUSES, +} from 'src/explore/components/controls/FilterControl/AdhocFilter'; +import { + AGGREGATES, + Operators, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; +import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric'; +import AdhocFilterEditPopoverSimpleTabContent, { + useSimpleTabFilterProps, +} from '.'; + +const simpleAdhocFilter = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operatorId: Operators.GREATER_THAN, + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.GREATER_THAN].operation, + comparator: '10', + clause: CLAUSES.WHERE, +}); + +const simpleMultiAdhocFilter = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operatorId: Operators.IN, + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.IN].operation, + comparator: ['10'], + clause: CLAUSES.WHERE, +}); + +const sumValueAdhocMetric = new AdhocMetric({ + expressionType: EXPRESSION_TYPES.SIMPLE, + column: { type: 'VARCHAR(255)', column_name: 'source' }, + aggregate: AGGREGATES.SUM, +}); + +const simpleCustomFilter = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'ds', + operator: 'LATEST PARTITION', + operatorId: Operators.LATEST_PARTITION, +}); + +const options = [ + { type: 'VARCHAR(255)', column_name: 'source', id: 1 }, + { type: 'VARCHAR(255)', column_name: 'target', id: 2 }, + { type: 'DOUBLE', column_name: 'value', id: 3 }, + { saved_metric_name: 'my_custom_metric', id: 4 }, + sumValueAdhocMetric, +]; + +function setup(overrides?: Record) { + const onChange = sinon.spy(); + const props = { + adhocFilter: simpleAdhocFilter, + onChange, + options, + datasource: { + id: 'test-id', + columns: [], + type: 'postgres', + filter_select: false, + }, + partitionColumn: 'test', + ...overrides, + }; + const wrapper = shallow( + , + ); + return { wrapper, props }; +} + +describe('AdhocFilterEditPopoverSimpleTabContent', () => { + it('renders the simple tab form', () => { + const { wrapper } = setup(); + expect(wrapper).toExist(); + }); + + it('shows boolean only operators when subject is boolean', () => { + const { props } = setup({ + adhocFilter: new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operatorId: null, + operator: null, + comparator: null, + clause: null, + }), + datasource: { + columns: [ + { + id: 3, + column_name: 'value', + type: 'BOOL', + }, + ], + }, + }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + [ + Operators.IS_TRUE, + Operators.IS_FALSE, + Operators.IS_NULL, + Operators.IS_FALSE, + ].map(operator => expect(isOperatorRelevant(operator, 'value')).toBe(true)); + }); + it('shows boolean only operators when subject is number', () => { + const { props } = setup({ + adhocFilter: new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'value', + operatorId: null, + operator: null, + comparator: null, + clause: null, + }), + datasource: { + columns: [ + { + id: 3, + column_name: 'value', + type: 'INT', + }, + ], + }, + }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + [ + Operators.IS_TRUE, + Operators.IS_FALSE, + Operators.IS_NULL, + Operators.IS_NOT_NULL, + ].map(operator => expect(isOperatorRelevant(operator, 'value')).toBe(true)); + }); + + it('will convert from individual comparator to array if the operator changes to multi', () => { + const { props } = setup(); + const { onOperatorChange } = useSimpleTabFilterProps(props); + onOperatorChange(Operators.IN); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0].comparator).toEqual(['10']); + expect(props.onChange.lastCall.args[0].operatorId).toEqual(Operators.IN); + }); + + it('will convert from array to individual comparators if the operator changes from multi', () => { + const { props } = setup({ + adhocFilter: simpleMultiAdhocFilter, + }); + const { onOperatorChange } = useSimpleTabFilterProps(props); + onOperatorChange(Operators.LESS_THAN); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0]).toEqual( + simpleMultiAdhocFilter.duplicateWith({ + operatorId: Operators.LESS_THAN, + operator: '<', + comparator: '10', + }), + ); + }); + + it('passes the new adhocFilter to onChange after onComparatorChange', () => { + const { props } = setup(); + const { onComparatorChange } = useSimpleTabFilterProps(props); + onComparatorChange('20'); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0]).toEqual( + simpleAdhocFilter.duplicateWith({ comparator: '20' }), + ); + }); + + it('will filter operators for table datasources', () => { + const { props } = setup({ datasource: { type: 'table' } }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + expect(isOperatorRelevant(Operators.REGEX, 'value')).toBe(false); + expect(isOperatorRelevant(Operators.LIKE, 'value')).toBe(true); + }); + + it('will filter operators for druid datasources', () => { + const { props } = setup({ datasource: { type: 'druid' } }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + expect(isOperatorRelevant(Operators.REGEX, 'value')).toBe(true); + expect(isOperatorRelevant(Operators.LIKE, 'value')).toBe(false); + }); + + it('will show LATEST PARTITION operator', () => { + const { props } = setup({ + datasource: { + type: 'table', + datasource_name: 'table1', + schema: 'schema', + }, + adhocFilter: simpleCustomFilter, + partitionColumn: 'ds', + }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + expect(isOperatorRelevant(Operators.LATEST_PARTITION, 'ds')).toBe(true); + expect(isOperatorRelevant(Operators.LATEST_PARTITION, 'value')).toBe(false); + }); + + it('will generate custom sqlExpression for LATEST PARTITION operator', () => { + const testAdhocFilter = new AdhocFilter({ + expressionType: EXPRESSION_TYPES.SIMPLE, + subject: 'ds', + }); + const { props } = setup({ + datasource: { + type: 'table', + datasource_name: 'table1', + schema: 'schema', + }, + adhocFilter: testAdhocFilter, + partitionColumn: 'ds', + }); + const { onOperatorChange } = useSimpleTabFilterProps(props); + onOperatorChange(Operators.LATEST_PARTITION); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0]).toEqual( + testAdhocFilter.duplicateWith({ + subject: 'ds', + operator: 'LATEST PARTITION', + operatorId: Operators.LATEST_PARTITION, + comparator: null, + clause: 'WHERE', + expressionType: 'SQL', + sqlExpression: "ds = '{{ presto.latest_partition('schema.table1') }}'", + }), + ); + }); + it('will not display boolean operators when column type is string', () => { + const { props } = setup({ + datasource: { + type: 'table', + datasource_name: 'table1', + schema: 'schema', + columns: [{ column_name: 'value', type: 'STRING' }], + }, + adhocFilter: simpleAdhocFilter, + }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + const booleanOnlyOperators = [Operators.IS_TRUE, Operators.IS_FALSE]; + booleanOnlyOperators.forEach(operator => { + expect(isOperatorRelevant(operator, 'value')).toBe(false); + }); + }); + it('will display boolean operators when column is an expression', () => { + const { props } = setup({ + datasource: { + type: 'table', + datasource_name: 'table1', + schema: 'schema', + columns: [ + { + column_name: 'value', + expression: 'case when value is 0 then "NO"', + }, + ], + }, + adhocFilter: simpleAdhocFilter, + }); + const { isOperatorRelevant } = useSimpleTabFilterProps(props); + const booleanOnlyOperators = [Operators.IS_TRUE, Operators.IS_FALSE]; + booleanOnlyOperators.forEach(operator => { + expect(isOperatorRelevant(operator, 'value')).toBe(true); + }); + }); + it('sets comparator to true when operator is IS_TRUE', () => { + const { props } = setup(); + const { onOperatorChange } = useSimpleTabFilterProps(props); + onOperatorChange(Operators.IS_TRUE); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IS_TRUE); + expect(props.onChange.lastCall.args[0].operator).toBe('=='); + expect(props.onChange.lastCall.args[0].comparator).toBe(true); + }); + it('sets comparator to false when operator is IS_FALSE', () => { + const { props } = setup(); + const { onOperatorChange } = useSimpleTabFilterProps(props); + onOperatorChange(Operators.IS_FALSE); + expect(props.onChange.calledOnce).toBe(true); + expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IS_FALSE); + expect(props.onChange.lastCall.args[0].operator).toBe('=='); + expect(props.onChange.lastCall.args[0].comparator).toBe(false); + }); + it('sets comparator to null when operator is IS_NULL or IS_NOT_NULL', () => { + const { props } = setup(); + const { onOperatorChange } = useSimpleTabFilterProps(props); + [Operators.IS_NULL, Operators.IS_NOT_NULL].forEach(op => { + onOperatorChange(op); + expect(props.onChange.called).toBe(true); + expect(props.onChange.lastCall.args[0].operatorId).toBe(op); + expect(props.onChange.lastCall.args[0].operator).toBe( + OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation, + ); + expect(props.onChange.lastCall.args[0].comparator).toBe(null); + }); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.jsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.jsx deleted file mode 100644 index 23f6cf9e02ca..000000000000 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.jsx +++ /dev/null @@ -1,457 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { NativeSelect as Select } from 'src/components/Select'; -import { Input } from 'src/common/components'; -import { t, SupersetClient, styled } from '@superset-ui/core'; - -import adhocMetricType from 'src/explore/components/controls/MetricControl/adhocMetricType'; -import { - OPERATORS, - OPERATORS_OPTIONS, - TABLE_ONLY_OPERATORS, - DRUID_ONLY_OPERATORS, - HAVING_OPERATORS, - MULTI_OPERATORS, - CUSTOM_OPERATORS, - DISABLE_INPUT_OPERATORS, -} from 'src/explore/constants'; -import FilterDefinitionOption from 'src/explore/components/controls/MetricControl/FilterDefinitionOption'; -import AdhocFilter, { - EXPRESSION_TYPES, - CLAUSES, -} from 'src/explore/components/controls/FilterControl/AdhocFilter'; -import columnType from 'src/explore/components/controls/FilterControl/columnType'; -import Icons from 'src/components/Icons'; - -const SelectWithLabel = styled(Select)` - .ant-select-selector { - margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; - } - - .ant-select-selector::after { - content: '${({ labelText }) => labelText || '\\A0'}'; - display: inline-block; - white-space: nowrap; - color: ${({ theme }) => theme.colors.grayscale.light1}; - width: max-content; - } -`; - -const propTypes = { - adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired, - onChange: PropTypes.func.isRequired, - options: PropTypes.arrayOf( - PropTypes.oneOfType([ - columnType, - PropTypes.shape({ saved_metric_name: PropTypes.string.isRequired }), - adhocMetricType, - ]), - ).isRequired, - onHeightChange: PropTypes.func.isRequired, - datasource: PropTypes.object, - partitionColumn: PropTypes.string, - popoverRef: PropTypes.object, -}; - -const defaultProps = { - datasource: {}, -}; - -function translateOperator(operator) { - if (operator === OPERATORS['==']) { - return 'equals'; - } - if (operator === OPERATORS['!=']) { - return 'not equal to'; - } - if (operator === OPERATORS.LIKE) { - return 'LIKE'; - } - if (operator === OPERATORS.ILIKE) { - return 'LIKE (case insensitive)'; - } - if (operator === OPERATORS['LATEST PARTITION']) { - return 'use latest_partition template'; - } - return operator; -} - -export default class AdhocFilterEditPopoverSimpleTabContent extends React.Component { - constructor(props) { - super(props); - this.onSubjectChange = this.onSubjectChange.bind(this); - this.onOperatorChange = this.onOperatorChange.bind(this); - this.onComparatorChange = this.onComparatorChange.bind(this); - this.onInputComparatorChange = this.onInputComparatorChange.bind(this); - this.isOperatorRelevant = this.isOperatorRelevant.bind(this); - this.refreshComparatorSuggestions = this.refreshComparatorSuggestions.bind( - this, - ); - this.clearSuggestionSearch = this.clearSuggestionSearch.bind(this); - - this.state = { - suggestions: [], - abortActiveRequest: null, - currentSuggestionSearch: '', - }; - - this.selectProps = { - name: 'select-column', - showSearch: true, - }; - } - - UNSAFE_componentWillMount() { - this.refreshComparatorSuggestions(); - } - - componentDidUpdate(prevProps) { - if (prevProps.adhocFilter.subject !== this.props.adhocFilter.subject) { - this.refreshComparatorSuggestions(); - } - } - - onSubjectChange(id) { - const option = this.props.options.find( - option => option.id === id || option.optionName === id, - ); - - let subject; - let clause; - // infer the new clause based on what subject was selected. - if (option && option.column_name) { - subject = option.column_name; - clause = CLAUSES.WHERE; - } else if (option && (option.saved_metric_name || option.label)) { - subject = option.saved_metric_name || option.label; - clause = CLAUSES.HAVING; - } - const { operator } = this.props.adhocFilter; - this.props.onChange( - this.props.adhocFilter.duplicateWith({ - subject, - clause, - operator: - operator && this.isOperatorRelevant(operator, subject) - ? operator - : null, - expressionType: EXPRESSION_TYPES.SIMPLE, - }), - ); - } - - onOperatorChange(operator) { - const currentComparator = this.props.adhocFilter.comparator; - let newComparator; - // convert between list of comparators and individual comparators - // (e.g. `in ('North America', 'Africa')` to `== 'North America'`) - if (MULTI_OPERATORS.has(operator)) { - newComparator = Array.isArray(currentComparator) - ? currentComparator - : [currentComparator].filter(element => element); - } else { - newComparator = Array.isArray(currentComparator) - ? currentComparator[0] - : currentComparator; - } - - if (operator && CUSTOM_OPERATORS.has(operator)) { - this.props.onChange( - this.props.adhocFilter.duplicateWith({ - subject: this.props.adhocFilter.subject, - clause: CLAUSES.WHERE, - operator, - expressionType: EXPRESSION_TYPES.SQL, - datasource: this.props.datasource, - }), - ); - } else { - this.props.onChange( - this.props.adhocFilter.duplicateWith({ - operator, - comparator: newComparator, - expressionType: EXPRESSION_TYPES.SIMPLE, - }), - ); - } - } - - onInputComparatorChange(event) { - this.onComparatorChange(event.target.value); - } - - onComparatorChange(comparator) { - this.props.onChange( - this.props.adhocFilter.duplicateWith({ - comparator, - expressionType: EXPRESSION_TYPES.SIMPLE, - }), - ); - } - - refreshComparatorSuggestions() { - const { datasource } = this.props; - const col = this.props.adhocFilter.subject; - const having = this.props.adhocFilter.clause === CLAUSES.HAVING; - - if (col && datasource && datasource.filter_select && !having) { - if (this.state.abortActiveRequest) { - this.state.abortActiveRequest(); - } - - const controller = new AbortController(); - const { signal } = controller; - this.setState({ abortActiveRequest: controller.abort, loading: true }); - - SupersetClient.get({ - signal, - endpoint: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`, - }) - .then(({ json }) => { - this.setState(() => ({ - suggestions: json, - abortActiveRequest: null, - loading: false, - })); - }) - .catch(() => { - this.setState(() => ({ - suggestions: [], - abortActiveRequest: null, - loading: false, - })); - }); - } - } - - isOperatorRelevant(operator, subject) { - const column = this.props.datasource.columns?.find( - col => col.column_name === subject, - ); - const isColumnBoolean = - !!column && (column.type === 'BOOL' || column.type === 'BOOLEAN'); - const isColumnNumber = !!column && column.type === 'INT'; - const isColumnFunction = !!column && !!column.expression; - - if (operator && CUSTOM_OPERATORS.has(operator)) { - const { partitionColumn } = this.props; - return partitionColumn && subject && subject === partitionColumn; - } - if ( - operator === OPERATORS['IS TRUE'] || - operator === OPERATORS['IS FALSE'] - ) { - return isColumnBoolean || isColumnNumber || isColumnFunction; - } - if (isColumnBoolean) { - return ( - operator === OPERATORS['IS NULL'] || - operator === OPERATORS['IS NOT NULL'] - ); - } - return !( - (this.props.datasource.type === 'druid' && - TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) || - (this.props.datasource.type === 'table' && - DRUID_ONLY_OPERATORS.indexOf(operator) >= 0) || - (this.props.adhocFilter.clause === CLAUSES.HAVING && - HAVING_OPERATORS.indexOf(operator) === -1) - ); - } - - focusComparator(ref, shouldFocus) { - if (ref && shouldFocus) { - ref.focus(); - } - } - - optionsRemaining() { - const { suggestions } = this.state; - const { comparator } = this.props.adhocFilter; - // if select is multi/value is array, we show the options not selected - const valuesFromSuggestionsLength = Array.isArray(comparator) - ? comparator.filter(v => suggestions.includes(v)).length - : 0; - return suggestions?.length - valuesFromSuggestionsLength ?? 0; - } - - createSuggestionsPlaceholder() { - const optionsRemaining = this.optionsRemaining(); - const placeholder = t('%s option(s)', optionsRemaining); - return optionsRemaining ? placeholder : ''; - } - - renderSubjectOptionLabel(option) { - return ; - } - - clearSuggestionSearch() { - this.setState({ currentSuggestionSearch: '' }); - } - - render() { - const { adhocFilter, options, datasource } = this.props; - const { currentSuggestionSearch } = this.state; - let columns = options; - const { subject, operator, comparator } = adhocFilter; - const subjectSelectProps = { - value: subject ?? undefined, - onChange: this.onSubjectChange, - notFoundContent: t( - 'No such column found. To filter on a metric, try the Custom SQL tab.', - ), - filterOption: (input, option) => - option.filterBy.toLowerCase().indexOf(input.toLowerCase()) >= 0, - autoFocus: !subject, - }; - - if (datasource.type === 'druid') { - subjectSelectProps.placeholder = t( - '%s column(s) and metric(s)', - columns.length, - ); - } else { - // we cannot support simple ad-hoc filters for metrics because we don't know what type - // the value should be cast to (without knowing the output type of the aggregate, which - // becomes a rather complicated problem) - subjectSelectProps.placeholder = - adhocFilter.clause === CLAUSES.WHERE - ? t('%s column(s)', columns.length) - : t('To filter on a metric, use Custom SQL tab.'); - columns = options.filter(option => option.column_name); - } - - const operatorSelectProps = { - placeholder: t( - '%s operator(s)', - OPERATORS_OPTIONS.filter(op => this.isOperatorRelevant(op, subject)) - .length, - ), - // like AGGREGATES_OPTIONS, operator options are string - value: operator, - onChange: this.onOperatorChange, - filterOption: (input, option) => - option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0, - autoFocus: !!subjectSelectProps.value && !operator, - }; - - const focusComparator = - !!subjectSelectProps.value && !!operatorSelectProps.value; - const comparatorSelectProps = { - allowClear: true, - showSearch: true, - mode: MULTI_OPERATORS.has(operator) && 'tags', - tokenSeparators: [',', '\n', '\t', ';'], - loading: this.state.loading, - value: comparator, - onChange: this.onComparatorChange, - notFoundContent: t('Type a value here'), - disabled: DISABLE_INPUT_OPERATORS.includes(operator), - placeholder: this.createSuggestionsPlaceholder(), - labelText: comparator?.length > 0 && this.createSuggestionsPlaceholder(), - autoFocus: focusComparator, - }; - const Icon = - operator === 'NOT IN' ? Icons.StopOutlined : Icons.CheckOutlined; - - return ( - <> - - - {MULTI_OPERATORS.has(operator) || this.state.suggestions.length > 0 ? ( - triggerNode.parentNode} - onSearch={val => this.setState({ currentSuggestionSearch: val })} - onSelect={this.clearSuggestionSearch} - onBlur={this.clearSuggestionSearch} - menuItemSelectedIcon={} - > - {this.state.suggestions.map(suggestion => ( - - {suggestion} - - ))} - - {/* enable selecting an option not included in suggestions */} - {currentSuggestionSearch && - !this.state.suggestions.some( - suggestion => suggestion === currentSuggestionSearch, - ) && ( - - {`${t('Create "%s"', currentSuggestionSearch)}`} - - )} - - ) : ( - this.focusComparator(ref, focusComparator)} - onChange={this.onInputComparatorChange} - value={comparator} - placeholder={t('Filter value (case sensitive)')} - disabled={DISABLE_INPUT_OPERATORS.includes(operator)} - /> - )} - - ); - } -} -AdhocFilterEditPopoverSimpleTabContent.propTypes = propTypes; -AdhocFilterEditPopoverSimpleTabContent.defaultProps = defaultProps; diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx new file mode 100644 index 000000000000..f6ef44b5a10c --- /dev/null +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx @@ -0,0 +1,463 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useEffect, useState } from 'react'; +import { NativeSelect as Select } from 'src/components/Select'; +import { t, SupersetClient, styled } from '@superset-ui/core'; +import { + Operators, + OPERATORS_OPTIONS, + TABLE_ONLY_OPERATORS, + DRUID_ONLY_OPERATORS, + HAVING_OPERATORS, + MULTI_OPERATORS, + CUSTOM_OPERATORS, + DISABLE_INPUT_OPERATORS, + AGGREGATES, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; +import FilterDefinitionOption from 'src/explore/components/controls/MetricControl/FilterDefinitionOption'; +import AdhocFilter, { + EXPRESSION_TYPES, + CLAUSES, +} from 'src/explore/components/controls/FilterControl/AdhocFilter'; +import { Input, SelectProps } from 'src/common/components'; + +const SelectWithLabel = styled(Select)` + .ant-select-selector { + margin-bottom: ${({ theme }) => theme.gridUnit * 4}px; + } + + .ant-select-selector::after { + content: '${( + pr: SelectProps & { + labelText: string | boolean; + }, + ) => pr.labelText || '\\A0'}'; + display: inline-block; + white-space: nowrap; + color: ${({ theme }) => theme.colors.grayscale.light1}; + width: max-content; + } +`; + +export interface SimpleColumnType { + id: number; + column_name: string; + expression?: string; + type: string; + optionName?: string; + filterBy?: string; + value?: string; +} + +export interface SimpleExpressionType { + expressionType: keyof typeof EXPRESSION_TYPES; + column: SimpleColumnType; + aggregate: keyof typeof AGGREGATES; + label: string; +} +export interface SQLExpressionType { + expressionType: keyof typeof EXPRESSION_TYPES; + sqlExpression: string; + label: string; +} + +export interface MetricColumnType { + saved_metric_name: string; +} + +export type ColumnType = + | SimpleColumnType + | SimpleExpressionType + | SQLExpressionType + | MetricColumnType; + +export interface Props { + adhocFilter: AdhocFilter; + onChange: (filter: AdhocFilter) => void; + options: ColumnType[]; + datasource: { + id: string; + columns: SimpleColumnType[]; + type: string; + filter_select: boolean; + }; + partitionColumn: string; +} +export const useSimpleTabFilterProps = (props: Props) => { + const isOperatorRelevant = (operator: Operators, subject: string) => { + const column = props.datasource.columns?.find( + col => col.column_name === subject, + ); + const isColumnBoolean = + !!column && (column.type === 'BOOL' || column.type === 'BOOLEAN'); + const isColumnNumber = + !!column && (column.type === 'INT' || column.type === 'INTEGER'); + const isColumnFunction = !!column && !!column.expression; + + if (operator && CUSTOM_OPERATORS.has(operator)) { + const { partitionColumn } = props; + return partitionColumn && subject && subject === partitionColumn; + } + if (operator === Operators.IS_TRUE || operator === Operators.IS_FALSE) { + return isColumnBoolean || isColumnNumber || isColumnFunction; + } + if (isColumnBoolean) { + return ( + operator === Operators.IS_NULL || operator === Operators.IS_NOT_NULL + ); + } + return !( + (props.datasource.type === 'druid' && + TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) || + (props.datasource.type === 'table' && + DRUID_ONLY_OPERATORS.indexOf(operator) >= 0) || + (props.adhocFilter.clause === CLAUSES.HAVING && + HAVING_OPERATORS.indexOf(operator) === -1) + ); + }; + const onSubjectChange = (id: string | number) => { + const option = props.options.find( + option => + ('id' in option && option.id === id) || + ('optionName' in option && option.optionName === id), + ); + + let subject = ''; + let clause; + // infer the new clause based on what subject was selected. + if (option && 'column_name' in option) { + subject = option.column_name; + clause = CLAUSES.WHERE; + } else if (option && 'saved_metric_name' in option) { + subject = option.saved_metric_name; + clause = CLAUSES.HAVING; + } else if (option && option.label) { + subject = option.label; + clause = CLAUSES.HAVING; + } + const { operator, operatorId } = props.adhocFilter; + props.onChange( + props.adhocFilter.duplicateWith({ + subject, + clause, + operator: + operator && isOperatorRelevant(operatorId, subject) + ? OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId].operation + : null, + expressionType: EXPRESSION_TYPES.SIMPLE, + operatorId, + }), + ); + }; + const onOperatorChange = (operatorId: Operators) => { + const currentComparator = props.adhocFilter.comparator; + let newComparator; + // convert between list of comparators and individual comparators + // (e.g. `in ('North America', 'Africa')` to `== 'North America'`) + if (MULTI_OPERATORS.has(operatorId)) { + newComparator = Array.isArray(currentComparator) + ? currentComparator + : [currentComparator].filter(element => element); + } else { + newComparator = Array.isArray(currentComparator) + ? currentComparator[0] + : currentComparator; + } + if (operatorId === Operators.IS_TRUE || operatorId === Operators.IS_FALSE) { + newComparator = Operators.IS_TRUE === operatorId; + } + if (operatorId && CUSTOM_OPERATORS.has(operatorId)) { + props.onChange( + props.adhocFilter.duplicateWith({ + subject: props.adhocFilter.subject, + clause: CLAUSES.WHERE, + operatorId, + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId].operation, + expressionType: EXPRESSION_TYPES.SQL, + datasource: props.datasource, + }), + ); + } else { + props.onChange( + props.adhocFilter.duplicateWith({ + operatorId, + operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId].operation, + comparator: newComparator, + expressionType: EXPRESSION_TYPES.SIMPLE, + }), + ); + } + }; + const onComparatorChange = (comparator: string) => { + props.onChange( + props.adhocFilter.duplicateWith({ + comparator, + expressionType: EXPRESSION_TYPES.SIMPLE, + }), + ); + }; + return { + onSubjectChange, + onOperatorChange, + onComparatorChange, + isOperatorRelevant, + }; +}; + +const AdhocFilterEditPopoverSimpleTabContent: React.FC = props => { + const selectProps = { + name: 'select-column', + showSearch: true, + }; + const { + onSubjectChange, + onOperatorChange, + isOperatorRelevant, + onComparatorChange, + } = useSimpleTabFilterProps(props); + const [suggestions, setSuggestions] = useState>([]); + const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState(''); + const [ + loadingComparatorSuggestions, + setLoadingComparatorSuggestions, + ] = useState(false); + + useEffect(() => { + const refreshComparatorSuggestions = () => { + const { datasource } = props; + const col = props.adhocFilter.subject; + const having = props.adhocFilter.clause === CLAUSES.HAVING; + + if (col && datasource && datasource.filter_select && !having) { + const controller = new AbortController(); + const { signal } = controller; + if (loadingComparatorSuggestions) { + controller.abort(); + } + setLoadingComparatorSuggestions(true); + SupersetClient.get({ + signal, + endpoint: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`, + }) + .then(({ json }) => { + setSuggestions(json); + setLoadingComparatorSuggestions(false); + }) + .catch(() => { + setSuggestions([]); + setLoadingComparatorSuggestions(false); + }); + } + }; + refreshComparatorSuggestions(); + }, [props.adhocFilter.subject]); + + const onInputComparatorChange = ( + event: React.ChangeEvent, + ) => { + onComparatorChange(event.target.value); + }; + + const renderSubjectOptionLabel = (option: ColumnType) => ( + + ); + + const clearSuggestionSearch = () => { + setCurrentSuggestionSearch(''); + }; + + const getOptionsRemaining = () => { + const { comparator } = props.adhocFilter; + // if select is multi/value is array, we show the options not selected + const valuesFromSuggestionsLength = Array.isArray(comparator) + ? comparator.filter(v => suggestions.includes(v)).length + : 0; + return suggestions?.length - valuesFromSuggestionsLength ?? 0; + }; + const createSuggestionsPlaceholder = () => { + const optionsRemaining = getOptionsRemaining(); + const placeholder = t('%s option(s)', optionsRemaining); + return optionsRemaining ? placeholder : ''; + }; + + let columns = props.options; + const { subject, operator, comparator, operatorId } = props.adhocFilter; + const subjectSelectProps = { + value: subject ?? undefined, + onChange: onSubjectChange, + notFoundContent: t( + 'No such column found. To filter on a metric, try the Custom SQL tab.', + ), + autoFocus: !subject, + placeholder: '', + }; + + if (props.datasource.type === 'druid') { + subjectSelectProps.placeholder = t( + '%s column(s) and metric(s)', + columns.length, + ); + } else { + // we cannot support simple ad-hoc filters for metrics because we don't know what type + // the value should be cast to (without knowing the output type of the aggregate, which + // becomes a rather complicated problem) + subjectSelectProps.placeholder = + props.adhocFilter.clause === CLAUSES.WHERE + ? t('%s column(s)', columns.length) + : t('To filter on a metric, use Custom SQL tab.'); + columns = props.options.filter( + option => 'column_name' in option && option.column_name, + ); + } + + const operatorSelectProps = { + placeholder: t( + '%s operator(s)', + OPERATORS_OPTIONS.filter(op => isOperatorRelevant(op, subject)).length, + ), + value: OPERATOR_ENUM_TO_OPERATOR_TYPE[operatorId]?.display, + onChange: onOperatorChange, + autoFocus: !!subjectSelectProps.value && !operator, + name: 'select-operator', + }; + + const shouldFocusComparator = + !!subjectSelectProps.value && !!operatorSelectProps.value; + + const comparatorSelectProps: SelectProps & { + labelText: string | boolean; + } = { + allowClear: true, + showSearch: true, + mode: MULTI_OPERATORS.has(operatorId) ? 'tags' : undefined, + tokenSeparators: [',', '\n', '\t', ';'], + loading: loadingComparatorSuggestions, + value: comparator, + onChange: onComparatorChange, + notFoundContent: t('Type a value here'), + disabled: DISABLE_INPUT_OPERATORS.includes(operatorId), + placeholder: createSuggestionsPlaceholder(), + labelText: + comparator && comparator.length > 0 && createSuggestionsPlaceholder(), + autoFocus: shouldFocusComparator, + }; + + return ( + <> + + + {MULTI_OPERATORS.has(operatorId) || suggestions.length > 0 ? ( + triggerNode.parentNode} + onSearch={val => setCurrentSuggestionSearch(val)} + onSelect={clearSuggestionSearch} + onBlur={clearSuggestionSearch} + > + {suggestions.map((suggestion: string) => ( + + {String(suggestion)} + + ))} + + {/* enable selecting an option not included in suggestions */} + {currentSuggestionSearch && + !suggestions.some( + (suggestion: string) => suggestion === currentSuggestionSearch, + ) && ( + + {currentSuggestionSearch} + + )} + + ) : ( + { + if (ref && shouldFocusComparator) { + ref.focus(); + } + }} + onChange={onInputComparatorChange} + value={comparator} + placeholder={t('Filter value (case sensitive)')} + disabled={DISABLE_INPUT_OPERATORS.includes(operatorId)} + /> + )} + + ); +}; + +export default AdhocFilterEditPopoverSimpleTabContent; diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger/index.tsx index 530c1bb4fff6..e6477863173c 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger/index.tsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterPopoverTrigger/index.tsx @@ -29,7 +29,6 @@ interface AdhocFilterPopoverTriggerProps { datasource: Record; onFilterEdit: (editedFilter: AdhocFilter) => void; partitionColumn?: string; - createNew?: boolean; isControlledComponent?: boolean; visible?: boolean; togglePopover?: (visible: boolean) => void; @@ -104,7 +103,7 @@ class AdhocFilterPopoverTrigger extends React.PureComponent< defaultVisible={visible} visible={visible} onVisibleChange={togglePopover} - destroyTooltipOnHide={this.props.createNew} + destroyTooltipOnHide > {this.props.children} diff --git a/superset-frontend/src/explore/components/controls/FilterControl/adhocFilterType.js b/superset-frontend/src/explore/components/controls/FilterControl/adhocFilterType.js index f036fc0fca7a..c9eef05926fc 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/adhocFilterType.js +++ b/superset-frontend/src/explore/components/controls/FilterControl/adhocFilterType.js @@ -17,8 +17,6 @@ * under the License. */ import PropTypes from 'prop-types'; - -import { OPERATORS } from 'src/explore/constants'; import { EXPRESSION_TYPES, CLAUSES } from './AdhocFilter'; export default PropTypes.oneOfType([ @@ -26,7 +24,6 @@ export default PropTypes.oneOfType([ expressionType: PropTypes.oneOf([EXPRESSION_TYPES.SIMPLE]).isRequired, clause: PropTypes.oneOf([CLAUSES.HAVING, CLAUSES.WHERE]).isRequired, subject: PropTypes.string.isRequired, - operator: PropTypes.oneOf(Object.keys(OPERATORS)).isRequired, comparator: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string), diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx index 9a9fd2002a93..bbe544dd8fed 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricOption.jsx @@ -36,6 +36,8 @@ const propTypes = { onMoveLabel: PropTypes.func, onDropLabel: PropTypes.func, index: PropTypes.number, + type: PropTypes.string, + multi: PropTypes.bool, }; class AdhocMetricOption extends React.PureComponent { @@ -46,7 +48,7 @@ class AdhocMetricOption extends React.PureComponent { onRemoveMetric(e) { e.stopPropagation(); - this.props.onRemoveMetric(); + this.props.onRemoveMetric(this.props.index); } render() { @@ -60,6 +62,8 @@ class AdhocMetricOption extends React.PureComponent { onMoveLabel, onDropLabel, index, + type, + multi, } = this.props; return ( @@ -79,9 +83,10 @@ class AdhocMetricOption extends React.PureComponent { onMoveLabel={onMoveLabel} onDropLabel={onDropLabel} index={index} - type={DndItemType.AdhocMetricOption} + type={type ?? DndItemType.AdhocMetricOption} withCaret isFunction + multi={multi} /> ); diff --git a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx index 3a5e2fb5cda1..2b3b557793fa 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricPopoverTrigger.tsx @@ -35,7 +35,6 @@ export type AdhocMetricPopoverTriggerProps = { savedMetric: savedMetricType; datasourceType: string; children: ReactNode; - createNew?: boolean; isControlledComponent?: boolean; visible?: boolean; togglePopover?: (visible: boolean) => void; @@ -43,6 +42,7 @@ export type AdhocMetricPopoverTriggerProps = { }; export type AdhocMetricPopoverTriggerState = { + adhocMetric: AdhocMetric; popoverVisible: boolean; title: { label: string; hasCustomLabel: boolean }; currentLabel: string; @@ -65,6 +65,7 @@ class AdhocMetricPopoverTrigger extends React.PureComponent< this.onChange = this.onChange.bind(this); this.state = { + adhocMetric: props.adhocMetric, popoverVisible: false, title: { label: props.adhocMetric.label, @@ -76,6 +77,26 @@ class AdhocMetricPopoverTrigger extends React.PureComponent< }; } + static getDerivedStateFromProps( + nextProps: AdhocMetricPopoverTriggerProps, + prevState: AdhocMetricPopoverTriggerState, + ) { + if (prevState.adhocMetric.optionName !== nextProps.adhocMetric.optionName) { + return { + adhocMetric: nextProps.adhocMetric, + title: { + label: nextProps.adhocMetric.label, + hasCustomLabel: nextProps.adhocMetric.hasCustomLabel, + }, + currentLabel: '', + labelModified: false, + }; + } + return { + adhocMetric: nextProps.adhocMetric, + }; + } + onLabelChange(e: any) { const { verbose_name, metric_name } = this.props.savedMetric; const defaultMetricLabel = this.props.adhocMetric?.getDefaultLabel(); @@ -180,7 +201,6 @@ class AdhocMetricPopoverTrigger extends React.PureComponent< {this.props.children} diff --git a/superset-frontend/src/explore/components/controls/MetricControl/FilterDefinitionOption.jsx b/superset-frontend/src/explore/components/controls/MetricControl/FilterDefinitionOption.jsx index 0491090ed90a..7063c4c69452 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/FilterDefinitionOption.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/FilterDefinitionOption.jsx @@ -51,5 +51,6 @@ export default function FilterDefinitionOption({ option }) { /> ); } + return null; } FilterDefinitionOption.propTypes = propTypes; diff --git a/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx b/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx index 44a6527548ee..7115f529437c 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/MetricDefinitionValue.jsx @@ -48,6 +48,8 @@ export default function MetricDefinitionValue({ onMoveLabel, onDropLabel, index, + type, + multi, }) { const getSavedMetricByName = metricName => savedMetrics.find(metric => metric.metric_name === metricName); @@ -74,6 +76,8 @@ export default function MetricDefinitionValue({ onDropLabel, index, savedMetric: savedMetric ?? {}, + type, + multi, }; return ; diff --git a/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx b/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx index c8b7205c5237..4e4925b9cd20 100644 --- a/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx +++ b/superset-frontend/src/explore/components/controls/MetricControl/MetricsControl.jsx @@ -16,16 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { t, withTheme } from '@superset-ui/core'; -import { isEqual } from 'lodash'; +import { ensureIsArray, t, useTheme } from '@superset-ui/core'; import ControlHeader from 'src/explore/components/ControlHeader'; -import { - AGGREGATES_OPTIONS, - sqlaAutoGeneratedMetricNameRegex, - druidAutoGeneratedMetricRegex, -} from 'src/explore/constants'; import Icons from 'src/components/Icons'; import { AddIconButton, @@ -34,7 +28,6 @@ import { LabelsContainer, } from 'src/explore/components/controls/OptionControls'; import columnType from './columnType'; -import MetricDefinitionOption from './MetricDefinitionOption'; import MetricDefinitionValue from './MetricDefinitionValue'; import AdhocMetric from './AdhocMetric'; import savedMetricType from './savedMetricType'; @@ -82,9 +75,9 @@ function isDictionaryForAdhocMetric(value) { return value && !(value instanceof AdhocMetric) && value.expressionType; } -function columnsContainAllMetrics(value, nextProps) { +function columnsContainAllMetrics(value, columns, savedMetrics) { const columnNames = new Set( - [...(nextProps.columns || []), ...(nextProps.savedMetrics || [])] + [...(columns || []), ...(savedMetrics || [])] // eslint-disable-next-line camelcase .map(({ column_name, metric_name }) => column_name || metric_name), ); @@ -123,294 +116,227 @@ function coerceAdhocMetrics(value) { }); } -class MetricsControl extends React.PureComponent { - constructor(props) { - super(props); - this.onChange = this.onChange.bind(this); - this.onMetricEdit = this.onMetricEdit.bind(this); - this.onNewMetric = this.onNewMetric.bind(this); - this.onRemoveMetric = this.onRemoveMetric.bind(this); - this.moveLabel = this.moveLabel.bind(this); - this.checkIfAggregateInInput = this.checkIfAggregateInInput.bind(this); - this.optionsForSelect = this.optionsForSelect.bind(this); - this.selectFilterOption = this.selectFilterOption.bind(this); - this.isAutoGeneratedMetric = this.isAutoGeneratedMetric.bind(this); - this.optionRenderer = option => ; - this.valueRenderer = (option, index) => ( - this.onRemoveMetric(index)} - columns={this.props.columns} - datasource={this.props.datasource} - savedMetrics={this.props.savedMetrics} - savedMetricsOptions={getOptionsForSavedMetrics( - this.props.savedMetrics, - this.props.value, - this.props.value?.[index], - )} - datasourceType={this.props.datasourceType} - onMoveLabel={this.moveLabel} - onDropLabel={() => this.props.onChange(this.state.value)} - /> - ); - this.select = null; - this.selectRef = ref => { - if (ref) { - this.select = ref.select; - } else { - this.select = null; - } - }; - this.state = { - aggregateInInput: null, - options: this.optionsForSelect(this.props), - value: coerceAdhocMetrics(this.props.value), - }; - } +const emptySavedMetric = { metric_name: '', expression: '' }; - UNSAFE_componentWillReceiveProps(nextProps) { - const { value } = this.props; - if ( - !isEqual(this.props.columns, nextProps.columns) || - !isEqual(this.props.savedMetrics, nextProps.savedMetrics) - ) { - this.setState({ options: this.optionsForSelect(nextProps) }); +const MetricsControl = ({ + onChange, + multi, + value: propsValue, + columns, + savedMetrics, + datasource, + datasourceType, + ...props +}) => { + const [value, setValue] = useState(coerceAdhocMetrics(propsValue)); + const theme = useTheme(); - // Remove all metrics if selected value no longer a valid column - // in the dataset. Must use `nextProps` here because Redux reducers may - // have already updated the value for this control. - if (!columnsContainAllMetrics(nextProps.value, nextProps)) { - this.props.onChange([]); + const handleChange = useCallback( + opts => { + // if clear out options + if (opts === null) { + onChange(null); + return; } - } - if (value !== nextProps.value) { - this.setState({ value: coerceAdhocMetrics(nextProps.value) }); - } - } - - onNewMetric(newMetric) { - this.setState( - prevState => ({ - ...prevState, - value: [...prevState.value, newMetric], - }), - () => { - this.onChange(this.state.value); - }, - ); - } - onMetricEdit(changedMetric, oldMetric) { - this.setState( - prevState => ({ - value: prevState.value.map(value => { - if ( - // compare saved metrics - value === oldMetric.metric_name || - // compare adhoc metrics - typeof value.optionName !== 'undefined' - ? value.optionName === oldMetric.optionName - : false - ) { - return changedMetric; + const transformedOpts = ensureIsArray(opts); + const optionValues = transformedOpts + .map(option => { + // pre-defined metric + if (option.metric_name) { + return option.metric_name; } - return value; - }), - }), - () => { - this.onChange(this.state.value); - }, - ); - } - - onRemoveMetric(index) { - if (!Array.isArray(this.state.value)) { - return; - } - const valuesCopy = [...this.state.value]; - valuesCopy.splice(index, 1); - this.setState(prevState => ({ - ...prevState, - value: valuesCopy, - })); - this.props.onChange(valuesCopy); - } + return option; + }) + .filter(option => option); + onChange(multi ? optionValues : optionValues[0]); + }, + [multi, onChange], + ); - onChange(opts) { - // if clear out options - if (opts === null) { - this.props.onChange(null); - return; - } + const onNewMetric = useCallback( + newMetric => { + const newValue = [...value, newMetric]; + setValue(newValue); + handleChange(newValue); + }, + [handleChange, value], + ); - let transformedOpts; - if (Array.isArray(opts)) { - transformedOpts = opts; - } else { - transformedOpts = opts ? [opts] : []; - } - const optionValues = transformedOpts - .map(option => { - // pre-defined metric - if (option.metric_name) { - return option.metric_name; + const onMetricEdit = useCallback( + (changedMetric, oldMetric) => { + const newValue = value.map(val => { + if ( + // compare saved metrics + val === oldMetric.metric_name || + // compare adhoc metrics + typeof val.optionName !== 'undefined' + ? val.optionName === oldMetric.optionName + : false + ) { + return changedMetric; } - return option; - }) - .filter(option => option); - this.props.onChange(this.props.multi ? optionValues : optionValues[0]); - } - - moveLabel(dragIndex, hoverIndex) { - const { value } = this.state; - - const newValues = [...value]; - [newValues[hoverIndex], newValues[dragIndex]] = [ - newValues[dragIndex], - newValues[hoverIndex], - ]; - this.setState({ value: newValues }); - } - - isAddNewMetricDisabled() { - return !this.props.multi && this.state.value.length > 0; - } - - addNewMetricPopoverTrigger(trigger) { - if (this.isAddNewMetricDisabled()) { - return trigger; - } - return ( - - {trigger} - - ); - } + return val; + }); + setValue(newValue); + handleChange(newValue); + }, + [handleChange, value], + ); - checkIfAggregateInInput(input) { - const lowercaseInput = input.toLowerCase(); - const aggregateInInput = - AGGREGATES_OPTIONS.find(x => - lowercaseInput.startsWith(`${x.toLowerCase()}(`), - ) || null; - this.clearedAggregateInInput = this.state.aggregateInInput; - this.setState({ aggregateInInput }); - } + const onRemoveMetric = useCallback( + index => { + if (!Array.isArray(value)) { + return; + } + const valuesCopy = [...value]; + valuesCopy.splice(index, 1); + setValue(valuesCopy); + handleChange(valuesCopy); + }, + [handleChange, value], + ); - optionsForSelect(props) { - const { columns, savedMetrics } = props; - const aggregates = - columns && columns.length - ? AGGREGATES_OPTIONS.map(aggregate => ({ - aggregate_name: aggregate, - })) - : []; - const options = [ - ...(columns || []), - ...aggregates, - ...(savedMetrics || []), - ]; + const moveLabel = useCallback( + (dragIndex, hoverIndex) => { + const newValues = [...value]; + [newValues[hoverIndex], newValues[dragIndex]] = [ + newValues[dragIndex], + newValues[hoverIndex], + ]; + setValue(newValues); + }, + [value], + ); - return options.reduce((results, option) => { - if (option.metric_name) { - results.push({ ...option, optionName: option.metric_name }); - } else if (option.column_name) { - results.push({ ...option, optionName: `_col_${option.column_name}` }); - } else if (option.aggregate_name) { - results.push({ - ...option, - optionName: `_aggregate_${option.aggregate_name}`, - }); - } - return results; - }, []); - } + const isAddNewMetricDisabled = useCallback(() => !multi && value.length > 0, [ + multi, + value.length, + ]); - isAutoGeneratedMetric(savedMetric) { - if (this.props.datasourceType === 'druid') { - return druidAutoGeneratedMetricRegex.test(savedMetric.verbose_name); - } - return sqlaAutoGeneratedMetricNameRegex.test(savedMetric.metric_name); - } + const savedMetricOptions = useMemo( + () => getOptionsForSavedMetrics(savedMetrics, propsValue, null), + [propsValue, savedMetrics], + ); - selectFilterOption({ data: option }, filterValue) { - if (this.state.aggregateInInput) { - let endIndex = filterValue.length; - if (filterValue.endsWith(')')) { - endIndex = filterValue.length - 1; + const newAdhocMetric = useMemo(() => new AdhocMetric({ isNew: true }), [ + value, + ]); + const addNewMetricPopoverTrigger = useCallback( + trigger => { + if (isAddNewMetricDisabled()) { + return trigger; } - const valueAfterAggregate = filterValue.substring( - filterValue.indexOf('(') + 1, - endIndex, - ); return ( - option.column_name && - option.column_name.toLowerCase().indexOf(valueAfterAggregate) >= 0 + + {trigger} + ); + }, + [ + columns, + datasource, + datasourceType, + isAddNewMetricDisabled, + newAdhocMetric, + onNewMetric, + savedMetricOptions, + ], + ); + + useEffect(() => { + // Remove all metrics if selected value no longer a valid column + // in the dataset. Must use `nextProps` here because Redux reducers may + // have already updated the value for this control. + if (!columnsContainAllMetrics(propsValue, columns, savedMetrics)) { + handleChange([]); } - return ( - option.optionName && - (!option.metric_name || - !this.isAutoGeneratedMetric(option) || - option.verbose_name) && - (option.optionName.toLowerCase().indexOf(filterValue) >= 0 || - (option.verbose_name && - option.verbose_name.toLowerCase().indexOf(filterValue) >= 0)) - ); - } + }, [columns, savedMetrics]); - render() { - const { theme } = this.props; - return ( -
- - - {this.addNewMetricPopoverTrigger( - - - , - )} - - - {this.state.value.length > 0 - ? this.state.value.map((value, index) => - this.valueRenderer(value, index), - ) - : this.addNewMetricPopoverTrigger( - - - {t('Add metric')} - , - )} - -
- ); - } -} + useEffect(() => { + setValue(coerceAdhocMetrics(propsValue)); + }, [propsValue]); + + const onDropLabel = useCallback(() => handleChange(value), [ + handleChange, + value, + ]); + + const valueRenderer = useCallback( + (option, index) => ( + + ), + [ + columns, + datasource, + datasourceType, + moveLabel, + multi, + onDropLabel, + onMetricEdit, + onRemoveMetric, + savedMetrics, + value, + ], + ); + + return ( +
+ + + {addNewMetricPopoverTrigger( + + + , + )} + + + {value.length > 0 + ? value.map((value, index) => valueRenderer(value, index)) + : addNewMetricPopoverTrigger( + + + {t('Add metric')} + , + )} + +
+ ); +}; MetricsControl.propTypes = propTypes; MetricsControl.defaultProps = defaultProps; -export default withTheme(MetricsControl); +export default MetricsControl; diff --git a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx index 0469b4b916a7..716e10a908b7 100644 --- a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx +++ b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx @@ -45,11 +45,10 @@ export const OptionControlContainer = styled.div<{ border-radius: 3px; cursor: ${({ withCaret }) => (withCaret ? 'pointer' : 'default')}; `; - export const Label = styled.div` ${({ theme }) => ` display: flex; - max-width: 100%; + width: 100%; overflow: hidden; text-overflow: ellipsis; align-items: center; @@ -71,6 +70,11 @@ export const Label = styled.div` `} `; +const LabelText = styled.span` + overflow: hidden; + text-overflow: ellipsis; +`; + export const CaretContainer = styled.div` height: 100%; border-left: solid 1px ${({ theme }) => theme.colors.grayscale.dark2}0C; @@ -177,6 +181,7 @@ export const OptionControlLabel = ({ index, isExtra, tooltipTitle, + multi = true, ...props }: { label: string | React.ReactNode; @@ -192,15 +197,24 @@ export const OptionControlLabel = ({ index: number; isExtra?: boolean; tooltipTitle: string; + multi?: boolean; }) => { const theme = useTheme(); const ref = useRef(null); + const labelRef = useRef(null); + const hasMetricName = savedMetric?.metric_name; const [, drop] = useDrop({ accept: type, drop() { + if (!multi) { + return; + } onDropLabel?.(); }, hover(item: DragItem, monitor: DropTargetMonitor) { + if (!multi) { + return; + } if (!ref.current) { return; } @@ -242,7 +256,7 @@ export const OptionControlLabel = ({ item.index = hoverIndex; }, }); - const [, drag] = useDrag({ + const [{ isDragging }, drag] = useDrag({ item: { type, index, @@ -254,10 +268,34 @@ export const OptionControlLabel = ({ }); const getLabelContent = () => { - if (savedMetric?.metric_name) { - return ; + const shouldShowTooltip = + (!isDragging && + typeof label === 'string' && + tooltipTitle && + label && + tooltipTitle !== label) || + (!isDragging && + labelRef && + labelRef.current && + labelRef.current.scrollWidth > labelRef.current.clientWidth); + + if (savedMetric && hasMetricName) { + return ( + + ); + } + if (!shouldShowTooltip) { + return {label}; } - return {label}; + return ( + + {label} + + ); }; const getOptionControlContent = () => ( diff --git a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx index cfbda997a3ab..4e21b080d984 100644 --- a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx +++ b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx @@ -62,21 +62,27 @@ const defaultProps = { export default class TextAreaControl extends React.Component { constructor() { super(); + this.state = { + value: '', + }; this.onAceChangeDebounce = debounce(value => { this.onAceChange(value); }, FAST_DEBOUNCE); } onControlChange(event) { - this.props.onChange(event.target.value); + const { value } = event.target; + this.setState({ value }); + this.props.onChange(value); } onAceChange(value) { + this.setState({ value }); this.props.onChange(value); } renderEditor(inModal = false) { - const value = this.props.value || ''; + const value = this.state.value || this.props.value; const minLines = inModal ? 40 : this.props.minLines || 12; if (this.props.language) { const style = { border: '1px solid #CCC' }; diff --git a/superset-frontend/src/explore/components/controls/ViewQueryModal.tsx b/superset-frontend/src/explore/components/controls/ViewQueryModal.tsx index 1234613b5052..88391bf995c0 100644 --- a/superset-frontend/src/explore/components/controls/ViewQueryModal.tsx +++ b/superset-frontend/src/explore/components/controls/ViewQueryModal.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useEffect, useState } from 'react'; -import { styled, t } from '@superset-ui/core'; +import { ensureIsArray, styled, t } from '@superset-ui/core'; import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light'; import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import CopyToClipboard from 'src/components/CopyToClipboard'; @@ -45,9 +45,13 @@ interface Props { latestQueryFormData: object; } +type Result = { + query: string; + language: string; +}; + const ViewQueryModal: React.FC = props => { - const [language, setLanguage] = useState(null); - const [query, setQuery] = useState(null); + const [result, setResult] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -58,11 +62,8 @@ const ViewQueryModal: React.FC = props => { resultFormat: 'json', resultType, }) - .then(response => { - // Only displaying the first query is currently supported - const result = response.result[0]; - setLanguage(result.language); - setQuery(result.query); + .then(({ json }) => { + setResult(ensureIsArray(json.result)); setIsLoading(false); setError(null); }) @@ -80,7 +81,7 @@ const ViewQueryModal: React.FC = props => { }; useEffect(() => { loadChartData('query'); - }, [props.latestQueryFormData]); + }, [JSON.stringify(props.latestQueryFormData)]); if (isLoading) { return ; @@ -88,25 +89,31 @@ const ViewQueryModal: React.FC = props => { if (error) { return
{error}
; } - if (query) { - return ( -
- - - - } - /> - - {query} - -
- ); - } - return null; + return ( + <> + {result.map(item => + item.query ? ( +
+ + + + } + /> + + {item.query} + +
+ ) : null, + )} + + ); }; export default ViewQueryModal; diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx index 35dd245bb659..b13075cd0f49 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import { Preset } from '@superset-ui/core'; -import { render, cleanup, screen } from 'spec/helpers/testing-library'; +import { render, cleanup, screen, act } from 'spec/helpers/testing-library'; import { Provider } from 'react-redux'; import { getMockStore, @@ -26,6 +26,7 @@ import { } from 'spec/fixtures/mockStore'; import React from 'react'; import userEvent from '@testing-library/user-event'; +import { DynamicPluginProvider } from 'src/components/DynamicPlugins'; import { testWithId } from 'src/utils/testUtils'; import { EchartsMixedTimeseriesChartPlugin, @@ -57,6 +58,15 @@ class MainPreset extends Preset { const getTestId = testWithId(VIZ_TYPE_CONTROL_TEST_ID, true); +/** + * AntD and/or the Icon component seems to be doing some kind of async changes, + * so even though the test passes, there is a warning an update to Icon was not + * wrapped in act(). This sufficiently act-ifies whatever side effects are going + * on and prevents those warnings. + */ +const waitForEffects = () => + act(() => new Promise(resolve => setTimeout(resolve, 0))); + describe('VizTypeControl', () => { new MainPreset().register(); const newVizTypeControlProps = { @@ -64,21 +74,26 @@ describe('VizTypeControl', () => { label: '', name: '', value: '', - labelType: '', + labelType: 'primary', onChange: jest.fn(), - }; + isModalOpenInit: true, + } as const; - const renderWrapper = ( + const renderWrapper = async ( props = newVizTypeControlProps, state: object = stateWithoutNativeFilters, - ) => + ) => { render( - + + + , ); + await waitForEffects(); + }; afterEach(() => { cleanup(); @@ -86,14 +101,13 @@ describe('VizTypeControl', () => { }); it('Search visualization type', async () => { - renderWrapper(); + await renderWrapper(); const visualizations = screen.getByTestId(getTestId('viz-row')); + userEvent.click(screen.getByRole('button', { name: 'ballot All charts' })); + expect(visualizations).toHaveTextContent(/Time-series Table/); - expect(visualizations).toHaveTextContent(/Time-series Chart/); - expect(visualizations).toHaveTextContent(/Mixed timeseries chart/); - expect(visualizations).toHaveTextContent(/Line Chart/); const searchInputText = 'time series'; @@ -102,10 +116,11 @@ describe('VizTypeControl', () => { screen.getByTestId(getTestId('search-input')), searchInputText, ); + await waitForEffects(); expect(visualizations).toHaveTextContent(/Time-series Table/); expect(visualizations).toHaveTextContent(/Time-series Chart/); - expect(visualizations).toHaveTextContent(/Mixed timeseries chart/); + expect(visualizations).toHaveTextContent(/Mixed Time-Series/); expect(visualizations).not.toHaveTextContent(/Line Chart/); }); }); diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx new file mode 100644 index 000000000000..7f2933428bf2 --- /dev/null +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx @@ -0,0 +1,784 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { + ChangeEventHandler, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import Fuse from 'fuse.js'; +import cx from 'classnames'; +import { + t, + styled, + css, + ChartMetadata, + SupersetTheme, + useTheme, +} from '@superset-ui/core'; +import { Collapse, Input } from 'src/common/components'; +import Label from 'src/components/Label'; +import { usePluginContext } from 'src/components/DynamicPlugins'; +import Icons from 'src/components/Icons'; +import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; +import scrollIntoView from 'scroll-into-view-if-needed'; + +interface VizTypeGalleryProps { + onChange: (vizType: string | null) => void; + selectedViz: string | null; + className?: string; +} + +type VizEntry = { + key: string; + value: ChartMetadata; +}; + +enum SECTIONS { + ALL_CHARTS = 'ALL_CHARTS', + CATEGORY = 'CATEGORY', + TAGS = 'TAGS', + RECOMMENDED_TAGS = 'RECOMMENDED_TAGS', +} + +const DEFAULT_ORDER = [ + 'line', + 'big_number', + 'big_number_total', + 'table', + 'pivot_table_v2', + 'echarts_timeseries_line', + 'echarts_area', + 'echarts_timeseries_bar', + 'echarts_timeseries_scatter', + 'pie', + 'mixed_timeseries', + 'filter_box', + 'dist_bar', + 'area', + 'bar', + 'deck_polygon', + 'time_table', + 'histogram', + 'deck_scatter', + 'deck_hex', + 'time_pivot', + 'deck_arc', + 'heatmap', + 'deck_grid', + 'dual_line', + 'deck_screengrid', + 'line_multi', + 'treemap', + 'box_plot', + 'sunburst', + 'sankey', + 'word_cloud', + 'mapbox', + 'kepler', + 'cal_heatmap', + 'rose', + 'bubble', + 'deck_geojson', + 'horizon', + 'deck_multi', + 'compare', + 'partition', + 'event_flow', + 'deck_path', + 'graph_chart', + 'world_map', + 'paired_ttest', + 'para', + 'country_map', +]; + +const typesWithDefaultOrder = new Set(DEFAULT_ORDER); + +const THUMBNAIL_GRID_UNITS = 24; + +export const MAX_ADVISABLE_VIZ_GALLERY_WIDTH = 1090; + +const OTHER_CATEGORY = t('Other'); + +const ALL_CHARTS = t('All charts'); + +const RECOMMENDED_TAGS = [t('Popular'), t('ECharts'), t('Advanced-Analytics')]; + +export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control'; + +const VizPickerLayout = styled.div` + display: grid; + grid-template-rows: auto minmax(100px, 1fr) minmax(200px, 35%); + // em is used here because the sidebar should be sized to fit the longest standard tag + grid-template-columns: minmax(14em, auto) 5fr; + grid-template-areas: + 'sidebar search' + 'sidebar main' + 'details details'; + height: 70vh; + overflow: auto; +`; + +const SectionTitle = styled.h3` + margin-top: 0; + margin-bottom: ${({ theme }) => theme.gridUnit * 2}px; + font-size: ${({ theme }) => theme.typography.sizes.l}px; + font-weight: ${({ theme }) => theme.typography.weights.bold}; + line-height: ${({ theme }) => theme.gridUnit * 6}px; +`; + +const LeftPane = styled.div` + grid-area: sidebar; + display: flex; + flex-direction: column; + border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + overflow: auto; + + .ant-collapse .ant-collapse-item { + .ant-collapse-header { + font-size: ${({ theme }) => theme.typography.sizes.s}px; + color: ${({ theme }) => theme.colors.grayscale.base}; + padding-left: ${({ theme }) => theme.gridUnit * 2}px; + padding-bottom: ${({ theme }) => theme.gridUnit}px; + } + .ant-collapse-content .ant-collapse-content-box { + display: flex; + flex-direction: column; + padding: 0 ${({ theme }) => theme.gridUnit * 2}px; + } + } +`; + +const RightPane = styled.div` + grid-area: main; + overflow-y: auto; +`; + +const SearchWrapper = styled.div` + ${({ theme }) => ` + grid-area: search; + margin-top: ${theme.gridUnit * 3}px; + margin-bottom: ${theme.gridUnit}px; + margin-left: ${theme.gridUnit * 3}px; + margin-right: ${theme.gridUnit * 3}px; + .ant-input-affix-wrapper { + padding-left: ${theme.gridUnit * 2}px; + } + `} +`; + +/** Styles to line up prefix/suffix icons in the search input */ +const InputIconAlignment = styled.div` + display: flex; + justify-content: center; + align-items: center; + color: ${({ theme }) => theme.colors.grayscale.base}; +`; + +const SelectorLabel = styled.button` + ${({ theme }) => ` + all: unset; // remove default button styles + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; + margin: ${theme.gridUnit}px 0; + padding: 0 ${theme.gridUnit}px; + border-radius: ${theme.borderRadius}px; + line-height: 2em; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + + &:focus { + outline: initial; + } + + &.selected { + background-color: ${theme.colors.primary.dark1}; + color: ${theme.colors.primary.light5}; + + svg { + color: ${theme.colors.primary.light5}; + } + + &:hover { + .cancel { + visibility: visible; + } + } + } + + & span:first-of-type svg { + margin-top: ${theme.gridUnit * 1.5}px; + } + + .cancel { + visibility: hidden; + } + `} +`; + +const IconsPane = styled.div` + overflow: auto; + display: grid; + grid-template-columns: repeat( + auto-fill, + ${({ theme }) => theme.gridUnit * THUMBNAIL_GRID_UNITS}px + ); + grid-auto-rows: max-content; + justify-content: space-evenly; + grid-gap: ${({ theme }) => theme.gridUnit * 2}px; + justify-items: center; + // for some reason this padding doesn't seem to apply at the bottom of the container. Why is a mystery. + padding: ${({ theme }) => theme.gridUnit * 2}px; +`; + +const DetailsPane = (theme: SupersetTheme) => css` + grid-area: details; + border-top: 1px solid ${theme.colors.grayscale.light2}; +`; + +const DetailsPopulated = (theme: SupersetTheme) => css` + padding: ${theme.gridUnit * 4}px; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto 1fr; + grid-template-areas: + 'viz-name examples-header' + 'viz-tags examples' + 'description examples'; +`; + +const DetailsEmpty = (theme: SupersetTheme) => css` + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-style: italic; + color: ${theme.colors.grayscale.light1}; +`; + +// overflow hidden on the details pane and overflow auto on the description +// (plus grid layout) enables the description to scroll while the header stays in place. +const TagsWrapper = styled.div` + grid-area: viz-tags; + width: ${({ theme }) => theme.gridUnit * 120}px; + padding-right: ${({ theme }) => theme.gridUnit * 14}px; + padding-bottom: ${({ theme }) => theme.gridUnit * 2}px; +`; + +const Description = styled.p` + grid-area: description; + overflow: auto; + padding-right: ${({ theme }) => theme.gridUnit * 14}px; + margin: 0; +`; + +const Examples = styled.div` + grid-area: examples; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow: auto; + gap: ${({ theme }) => theme.gridUnit * 4}px; + + img { + height: 100%; + border-radius: ${({ theme }) => theme.gridUnit}px; + border: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + } +`; + +const thumbnailContainerCss = (theme: SupersetTheme) => css` + cursor: pointer; + width: ${theme.gridUnit * THUMBNAIL_GRID_UNITS}px; + + img { + min-width: ${theme.gridUnit * THUMBNAIL_GRID_UNITS}px; + min-height: ${theme.gridUnit * THUMBNAIL_GRID_UNITS}px; + border: 1px solid ${theme.colors.grayscale.light2}; + border-radius: ${theme.gridUnit}px; + transition: border-color ${theme.transitionTiming}; + } + + &.selected img { + border: 2px solid ${theme.colors.primary.light2}; + } + + &:hover:not(.selected) img { + border: 1px solid ${theme.colors.grayscale.light1}; + } + + .viztype-label { + margin-top: ${theme.gridUnit * 2}px; + text-align: center; + } +`; + +function vizSortFactor(entry: VizEntry) { + if (typesWithDefaultOrder.has(entry.key)) { + return DEFAULT_ORDER.indexOf(entry.key); + } + return DEFAULT_ORDER.length; +} + +interface ThumbnailProps { + entry: VizEntry; + selectedViz: string | null; + setSelectedViz: (viz: string) => void; +} + +const Thumbnail: React.FC = ({ + entry, + selectedViz, + setSelectedViz, +}) => { + const theme = useTheme(); + const { key, value: type } = entry; + const isSelected = selectedViz === entry.key; + + return ( +
setSelectedViz(key)} + data-test="viztype-selector-container" + > + {type.name} +
+ {type.name} +
+
+ ); +}; + +interface ThumbnailGalleryProps { + vizEntries: VizEntry[]; + selectedViz: string | null; + setSelectedViz: (viz: string) => void; +} + +/** A list of viz thumbnails, used within the viz picker modal */ +const ThumbnailGallery: React.FC = ({ + vizEntries, + ...props +}) => ( + + {vizEntries.map(entry => ( + + ))} + +); + +const Selector: React.FC<{ + selector: string; + sectionId: string; + icon: JSX.Element; + isSelected: boolean; + onClick: (selector: string, sectionId: string) => void; + className?: string; +}> = ({ selector, sectionId, icon, isSelected, onClick, className }) => { + const btnRef = useRef(null); + + // see Element.scrollIntoViewIfNeeded() + // see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded + useEffect(() => { + if (isSelected) { + // We need to wait for the modal to open and then scroll, so we put it in the microtask queue + queueMicrotask(() => + scrollIntoView(btnRef.current as HTMLButtonElement, { + behavior: 'smooth', + scrollMode: 'if-needed', + }), + ); + } + }, []); + + return ( + onClick(selector, sectionId)} + > + {icon} + {selector} + + ); +}; + +const doesVizMatchSelector = (viz: ChartMetadata, selector: string) => + selector === viz.category || + (selector === OTHER_CATEGORY && viz.category == null) || + (viz.tags || []).indexOf(selector) > -1; + +export default function VizTypeGallery(props: VizTypeGalleryProps) { + const { selectedViz, onChange, className } = props; + const { mountedPluginMetadata } = usePluginContext(); + const searchInputRef = useRef(); + const [searchInputValue, setSearchInputValue] = useState(''); + const [isSearchFocused, setIsSearchFocused] = useState(true); + const isActivelySearching = isSearchFocused && !!searchInputValue; + + const selectedVizMetadata: ChartMetadata | null = selectedViz + ? mountedPluginMetadata[selectedViz] + : null; + + const chartMetadata: VizEntry[] = useMemo(() => { + const result = Object.entries(mountedPluginMetadata) + .map(([key, value]) => ({ key, value })) + .filter( + ({ value }) => + nativeFilterGate(value.behaviors || []) && !value.deprecated, + ); + result.sort((a, b) => vizSortFactor(a) - vizSortFactor(b)); + return result; + }, [mountedPluginMetadata]); + + const chartsByCategory = useMemo(() => { + const result: Record = {}; + chartMetadata.forEach(entry => { + const category = entry.value.category || OTHER_CATEGORY; + if (!result[category]) { + result[category] = []; + } + result[category].push(entry); + }); + return result; + }, [chartMetadata]); + + const categories = useMemo( + () => + Object.keys(chartsByCategory).sort((a, b) => { + // make sure Other goes at the end + if (a === OTHER_CATEGORY) return 1; + if (b === OTHER_CATEGORY) return -1; + // sort alphabetically + return a.localeCompare(b); + }), + [chartsByCategory], + ); + + const chartsByTags = useMemo(() => { + const result: Record = {}; + chartMetadata.forEach(entry => { + const tags = entry.value.tags || []; + tags.forEach(tag => { + if (!result[tag]) { + result[tag] = []; + } + result[tag].push(entry); + }); + }); + return result; + }, [chartMetadata]); + + const tags = useMemo( + () => + Object.keys(chartsByTags) + .sort((a, b) => + // sort alphabetically + a.localeCompare(b), + ) + .filter(tag => RECOMMENDED_TAGS.indexOf(tag) === -1), + [chartsByTags], + ); + + const sortedMetadata = useMemo( + () => chartMetadata.sort((a, b) => a.key.localeCompare(b.key)), + [chartMetadata], + ); + + const [activeSelector, setActiveSelector] = useState( + () => selectedVizMetadata?.category || RECOMMENDED_TAGS[0], + ); + + const [activeSection, setActiveSection] = useState(() => + selectedVizMetadata?.category + ? SECTIONS.CATEGORY + : SECTIONS.RECOMMENDED_TAGS, + ); + + // get a fuse instance for fuzzy search + const fuse = useMemo( + () => + new Fuse(chartMetadata, { + ignoreLocation: true, + threshold: 0.3, + keys: ['value.name', 'value.tags', 'value.description'], + }), + [chartMetadata], + ); + + const searchResults = useMemo(() => { + if (searchInputValue.trim() === '') { + return []; + } + return fuse.search(searchInputValue).map(result => result.item); + }, [searchInputValue, fuse]); + + const focusSearch = useCallback(() => { + // "start searching" is actually a two-stage process. + // When you first click on the search bar, the input is focused and nothing else happens. + // Once you begin typing, the selected category is cleared and the displayed viz entries change. + setIsSearchFocused(true); + }, []); + + const changeSearch: ChangeEventHandler = useCallback( + event => setSearchInputValue(event.target.value), + [], + ); + + const stopSearching = useCallback(() => { + // stopping a search takes you back to the category you were looking at before. + // Unlike focusSearch, this is a simple one-step process. + setIsSearchFocused(false); + setSearchInputValue(''); + searchInputRef.current!.blur(); + }, []); + + const clickSelector = useCallback( + (selector: string, sectionId: string) => { + if (isSearchFocused) { + stopSearching(); + } + setActiveSelector(selector); + setActiveSection(sectionId); + // clear the selected viz if it is not present in the new category or tags + const isSelectedVizCompatible = + selectedVizMetadata && + doesVizMatchSelector(selectedVizMetadata, selector); + if (selector !== activeSelector && !isSelectedVizCompatible) { + onChange(null); + } + }, + [ + stopSearching, + isSearchFocused, + activeSelector, + selectedVizMetadata, + onChange, + ], + ); + + const sectionMap = useMemo( + () => ({ + [SECTIONS.RECOMMENDED_TAGS]: { + title: t('Recommended tags'), + icon: , + selectors: RECOMMENDED_TAGS, + }, + [SECTIONS.CATEGORY]: { + title: t('Category'), + icon: , + selectors: categories, + }, + [SECTIONS.TAGS]: { + title: t('Tags'), + icon: , + selectors: tags, + }, + }), + [categories, tags], + ); + + const getVizEntriesToDisplay = () => { + if (isActivelySearching) { + return searchResults; + } + if ( + activeSelector === ALL_CHARTS && + activeSection === SECTIONS.ALL_CHARTS + ) { + return sortedMetadata; + } + if ( + activeSection === SECTIONS.CATEGORY && + chartsByCategory[activeSelector] + ) { + return chartsByCategory[activeSelector]; + } + if ( + (activeSection === SECTIONS.TAGS || + activeSection === SECTIONS.RECOMMENDED_TAGS) && + chartsByTags[activeSelector] + ) { + return chartsByTags[activeSelector]; + } + return []; + }; + + return ( + + + + // adjust style for not being inside a collapse + css` + margin: ${gridUnit * 2}px; + margin-bottom: 0; + ` + } + sectionId={SECTIONS.ALL_CHARTS} + selector={ALL_CHARTS} + icon={} + isSelected={ + !isActivelySearching && + ALL_CHARTS === activeSelector && + SECTIONS.ALL_CHARTS === activeSection + } + onClick={clickSelector} + /> + + {Object.keys(sectionMap).map(sectionId => { + const section = sectionMap[sectionId]; + + return ( + {section.title}} + key={sectionId} + > + {section.selectors.map((selector: string) => ( + + ))} + + ); + })} + + + + + + + + } + suffix={ + + {searchInputValue && ( + + )} + + } + /> + + + + + + + {selectedVizMetadata ? ( +
[ + DetailsPane(theme), + DetailsPopulated(theme), + ]} + > + <> + + {selectedVizMetadata?.name} + + + {selectedVizMetadata?.tags.map(tag => ( + + ))} + + + {selectedVizMetadata?.description || + t('No description available.')} + + + {!!selectedVizMetadata?.exampleGallery?.length && t('Examples')} + + + {(selectedVizMetadata?.exampleGallery || []).map(example => ( + {example.caption} + ))} + + +
+ ) : ( +
[ + DetailsPane(theme), + DetailsEmpty(theme), + ]} + > + {t('Select a visualization type')} +
+ )} +
+ ); +} diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx deleted file mode 100644 index b7efbd8bc709..000000000000 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React, { useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import { Input, Row, Col } from 'src/common/components'; -import { t, getChartMetadataRegistry } from '@superset-ui/core'; -import { useDynamicPluginContext } from 'src/components/DynamicPlugins'; -import Modal from 'src/components/Modal'; -import { Tooltip } from 'src/components/Tooltip'; -import Label from 'src/components/Label'; -import ControlHeader from 'src/explore/components/ControlHeader'; -import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; -import './VizTypeControl.less'; - -const propTypes = { - description: PropTypes.string, - label: PropTypes.string, - name: PropTypes.string.isRequired, - onChange: PropTypes.func, - value: PropTypes.string.isRequired, - labelType: PropTypes.string, -}; - -const defaultProps = { - onChange: () => {}, - labelType: 'default', -}; - -const registry = getChartMetadataRegistry(); - -const DEFAULT_ORDER = [ - 'line', - 'big_number', - 'table', - 'filter_box', - 'dist_bar', - 'area', - 'bar', - 'deck_polygon', - 'pie', - 'time_table', - 'pivot_table', - 'histogram', - 'big_number_total', - 'deck_scatter', - 'deck_hex', - 'time_pivot', - 'deck_arc', - 'heatmap', - 'deck_grid', - 'dual_line', - 'deck_screengrid', - 'line_multi', - 'treemap', - 'box_plot', - 'sunburst', - 'sankey', - 'word_cloud', - 'mapbox', - 'kepler', - 'cal_heatmap', - 'rose', - 'bubble', - 'deck_geojson', - 'horizon', - 'deck_multi', - 'compare', - 'partition', - 'event_flow', - 'deck_path', - 'graph_chart', - 'world_map', - 'paired_ttest', - 'para', - 'country_map', -]; - -const typesWithDefaultOrder = new Set(DEFAULT_ORDER); - -export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control'; - -function VizSupportValidation({ vizType }) { - const state = useDynamicPluginContext(); - if (state.loading || registry.has(vizType)) { - return null; - } - return ( -
- {' '} - {t('This visualization type is not supported.')} -
- ); -} - -const VizTypeControl = props => { - const [showModal, setShowModal] = useState(false); - const [filter, setFilter] = useState(''); - const searchRef = useRef(null); - - useEffect(() => { - if (showModal) { - setTimeout(() => searchRef?.current?.focus(), 200); - } - }, [showModal]); - - const onChange = vizType => { - props.onChange(vizType); - setShowModal(false); - }; - - const toggleModal = () => { - setShowModal(prevState => !prevState); - }; - - const changeSearch = event => { - setFilter(event.target.value); - }; - - const renderItem = entry => { - const { value } = props; - const { key, value: type } = entry; - const isSelected = key === value; - - return ( -
onChange(key)} - > - {type.name} -
- {type.name} -
-
- ); - }; - - const { value, labelType } = props; - const filterString = filter.toLowerCase(); - const filterStringParts = filterString.split(' '); - - const a = DEFAULT_ORDER.filter(type => registry.has(type)); - const filteredTypes = a - .filter(type => { - const behaviors = registry.get(type)?.behaviors || []; - return nativeFilterGate(behaviors); - }) - .map(type => ({ - key: type, - value: registry.get(type), - })) - .concat( - registry - .entries() - .filter(entry => { - const behaviors = entry.value?.behaviors || []; - return nativeFilterGate(behaviors); - }) - .filter(({ key }) => !typesWithDefaultOrder.has(key)), - ) - .filter(entry => - filterStringParts.every( - part => entry.value.name.toLowerCase().indexOf(part) !== -1, - ), - ); - - return ( -
- - - <> - - - - - -
- -
- - {filteredTypes.map(entry => ( - - {renderItem(entry)} - - ))} - -
-
- ); -}; - -VizTypeControl.propTypes = propTypes; -VizTypeControl.defaultProps = defaultProps; - -export default VizTypeControl; diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx new file mode 100644 index 000000000000..7837eb04ae9f --- /dev/null +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.tsx @@ -0,0 +1,152 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { useCallback, useState } from 'react'; +import PropTypes from 'prop-types'; +import { t, getChartMetadataRegistry, styled } from '@superset-ui/core'; +import { usePluginContext } from 'src/components/DynamicPlugins'; +import Modal from 'src/components/Modal'; +import { Tooltip } from 'src/components/Tooltip'; +import Label, { Type } from 'src/components/Label'; +import ControlHeader from 'src/explore/components/ControlHeader'; +import VizTypeGallery, { + MAX_ADVISABLE_VIZ_GALLERY_WIDTH, +} from './VizTypeGallery'; + +const propTypes = { + description: PropTypes.string, + label: PropTypes.string, + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + value: PropTypes.string.isRequired, + labelType: PropTypes.string, +}; + +interface VizTypeControlProps { + description?: string; + label?: string; + name: string; + onChange: (vizType: string | null) => void; + value: string | null; + labelType?: Type; + isModalOpenInit?: boolean; +} + +const defaultProps = { + onChange: () => {}, + labelType: 'default', +}; + +const metadataRegistry = getChartMetadataRegistry(); + +export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control'; + +function VizSupportValidation({ vizType }: { vizType: string }) { + const state = usePluginContext(); + if (state.loading || metadataRegistry.has(vizType)) { + return null; + } + return ( +
+ {' '} + {t('This visualization type is not supported.')} +
+ ); +} + +const UnpaddedModal = styled(Modal)` + .ant-modal-body { + padding: 0; + } +`; + +/** Manages the viz type and the viz picker modal */ +const VizTypeControl = (props: VizTypeControlProps) => { + const { value: initialValue, onChange, isModalOpenInit, labelType } = props; + const { mountedPluginMetadata } = usePluginContext(); + const [showModal, setShowModal] = useState(!!isModalOpenInit); + // a trick to force re-initialization of the gallery each time the modal opens, + // ensuring that the modal always opens to the correct category. + const [modalKey, setModalKey] = useState(0); + const [selectedViz, setSelectedViz] = useState(initialValue); + + const openModal = useCallback(() => { + setShowModal(true); + }, []); + + const onSubmit = useCallback(() => { + onChange(selectedViz); + setShowModal(false); + }, [selectedViz, onChange]); + + const onCancel = useCallback(() => { + setShowModal(false); + setModalKey(key => key + 1); + // make sure the modal re-opens to the last submitted viz + setSelectedViz(initialValue); + }, [initialValue]); + + const labelContent = initialValue + ? mountedPluginMetadata[initialValue]?.name || `${initialValue}` + : t('Select Viz Type'); + + return ( +
+ + + <> + + {initialValue && } + + + + + {/* When the key increments, it forces react to re-init the gallery component */} + + +
+ ); +}; + +VizTypeControl.propTypes = propTypes; +VizTypeControl.defaultProps = defaultProps; + +export default VizTypeControl; diff --git a/superset-frontend/src/explore/components/controls/index.js b/superset-frontend/src/explore/components/controls/index.js index e3ae9fa26611..a00a88f53dfc 100644 --- a/superset-frontend/src/explore/components/controls/index.js +++ b/superset-frontend/src/explore/components/controls/index.js @@ -40,6 +40,7 @@ import VizTypeControl from './VizTypeControl'; import MetricsControl from './MetricControl/MetricsControl'; import AdhocFilterControl from './FilterControl/AdhocFilterControl'; import FilterBoxItemControl from './FilterBoxItemControl'; +import ConditionalFormattingControl from './ConditionalFormattingControl'; import DndColumnSelectControl, { DndColumnSelect, DndFilterSelect, @@ -74,6 +75,7 @@ const controlMap = { MetricsControl, AdhocFilterControl, FilterBoxItemControl, + ConditionalFormattingControl, ...sharedControlComponents, }; export default controlMap; diff --git a/superset-frontend/src/explore/components/optionRenderers.tsx b/superset-frontend/src/explore/components/optionRenderers.tsx index 74d2891df910..fbd641db3742 100644 --- a/superset-frontend/src/explore/components/optionRenderers.tsx +++ b/superset-frontend/src/explore/components/optionRenderers.tsx @@ -27,6 +27,7 @@ import { } from '@superset-ui/chart-controls'; const OptionContainer = styled.div` + width: 100%; > span { display: flex; align-items: center; diff --git a/superset-frontend/src/explore/constants.ts b/superset-frontend/src/explore/constants.ts index b3af092eb9fe..c3b31f1de568 100644 --- a/superset-frontend/src/explore/constants.ts +++ b/superset-frontend/src/explore/constants.ts @@ -28,49 +28,78 @@ export const AGGREGATES = { }; export const AGGREGATES_OPTIONS = Object.values(AGGREGATES); -export const OPERATORS = { - '==': '==', - '!=': '!=', - '>': '>', - '<': '<', - '>=': '>=', - '<=': '<=', - IN: 'IN', - 'NOT IN': 'NOT IN', - ILIKE: 'ILIKE', - LIKE: 'LIKE', - REGEX: 'REGEX', - 'IS NOT NULL': 'IS NOT NULL', - 'IS NULL': 'IS NULL', - 'LATEST PARTITION': 'LATEST PARTITION', - 'IS TRUE': 'IS TRUE', - 'IS FALSE': 'IS FALSE', +export enum Operators { + EQUALS = 'EQUALS', + NOT_EQUALS = 'NOT_EQUALS', + LESS_THAN = 'LESS_THAN', + GREATER_THAN = 'GREATER_THAN', + LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL', + GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL', + IN = 'IN', + NOT_IN = 'NOT_IN', + ILIKE = 'ILIKE', + LIKE = 'LIKE', + REGEX = 'REGEX', + IS_NOT_NULL = 'IS_NOT_NULL', + IS_NULL = 'IS_NULL', + LATEST_PARTITION = 'LATEST_PARTITION', + IS_TRUE = 'IS_TRUE', + IS_FALSE = 'IS_FALSE', +} + +export interface OperatorType { + display: string; + operation: string; +} + +export const OPERATOR_ENUM_TO_OPERATOR_TYPE: { + [key in Operators]: OperatorType; +} = { + [Operators.EQUALS]: { display: 'equals', operation: '==' }, + [Operators.NOT_EQUALS]: { display: 'not equals', operation: '!=' }, + [Operators.GREATER_THAN]: { display: '>', operation: '>' }, + [Operators.LESS_THAN]: { display: '<', operation: '<' }, + [Operators.GREATER_THAN_OR_EQUAL]: { display: '>=', operation: '>=' }, + [Operators.LESS_THAN_OR_EQUAL]: { display: '<=', operation: '<=' }, + [Operators.IN]: { display: 'IN', operation: 'IN' }, + [Operators.NOT_IN]: { display: 'NOT IN', operation: 'NOT IN' }, + [Operators.LIKE]: { display: 'LIKE', operation: 'LIKE' }, + [Operators.ILIKE]: { display: 'LIKE (case insensitive)', operation: 'ILIKE' }, + [Operators.REGEX]: { display: 'REGEX', operation: 'REGEX' }, + [Operators.IS_NOT_NULL]: { display: 'IS NOT NULL', operation: 'IS NOT NULL' }, + [Operators.IS_NULL]: { display: 'IS NULL', operation: 'IS NULL' }, + [Operators.LATEST_PARTITION]: { + display: 'use latest_partition template', + operation: 'LATEST PARTITION', + }, + [Operators.IS_TRUE]: { display: 'IS TRUE', operation: '==' }, + [Operators.IS_FALSE]: { display: 'IS FALSE', operation: '==' }, }; -export const OPERATORS_OPTIONS = Object.values(OPERATORS); +export const OPERATORS_OPTIONS = Object.values(Operators) as Operators[]; -export const TABLE_ONLY_OPERATORS = [OPERATORS.LIKE, OPERATORS.ILIKE]; -export const DRUID_ONLY_OPERATORS = [OPERATORS.REGEX]; +export const TABLE_ONLY_OPERATORS = [Operators.LIKE, Operators.ILIKE]; +export const DRUID_ONLY_OPERATORS = [Operators.REGEX]; export const HAVING_OPERATORS = [ - OPERATORS['=='], - OPERATORS['!='], - OPERATORS['>'], - OPERATORS['<'], - OPERATORS['>='], - OPERATORS['<='], + Operators.EQUALS, + Operators.NOT_EQUALS, + Operators.GREATER_THAN, + Operators.LESS_THAN, + Operators.GREATER_THAN_OR_EQUAL, + Operators.LESS_THAN_OR_EQUAL, ]; -export const MULTI_OPERATORS = new Set([OPERATORS.IN, OPERATORS['NOT IN']]); +export const MULTI_OPERATORS = new Set([Operators.IN, Operators.NOT_IN]); // CUSTOM_OPERATORS will show operator in simple mode, // but will generate customized sqlExpression -export const CUSTOM_OPERATORS = new Set([OPERATORS['LATEST PARTITION']]); +export const CUSTOM_OPERATORS = new Set([Operators.LATEST_PARTITION]); // DISABLE_INPUT_OPERATORS will disable filter value input // in adhocFilter control export const DISABLE_INPUT_OPERATORS = [ - OPERATORS['IS NOT NULL'], - OPERATORS['IS NULL'], - OPERATORS['LATEST PARTITION'], - OPERATORS['IS TRUE'], - OPERATORS['IS FALSE'], + Operators.IS_NOT_NULL, + Operators.IS_NULL, + Operators.LATEST_PARTITION, + Operators.IS_TRUE, + Operators.IS_FALSE, ]; export const sqlaAutoGeneratedMetricNameRegex = /^(sum|min|max|avg|count|count_distinct)__.*$/i; @@ -107,3 +136,4 @@ export const TIME_FILTER_MAP = { // TODO: make this configurable per Superset installation export const DEFAULT_TIME_RANGE = 'No filter'; +export const NO_TIME_RANGE = 'No filter'; diff --git a/superset-frontend/src/explore/exploreUtils/index.js b/superset-frontend/src/explore/exploreUtils/index.js index c936590db095..ddcd95b82a43 100644 --- a/superset-frontend/src/explore/exploreUtils/index.js +++ b/superset-frontend/src/explore/exploreUtils/index.js @@ -29,7 +29,10 @@ import { import { availableDomains } from 'src/utils/hostNamesConfig'; import { safeStringify } from 'src/utils/safeStringify'; import { URL_PARAMS } from 'src/constants'; -import { MULTI_OPERATORS } from 'src/explore/constants'; +import { + MULTI_OPERATORS, + OPERATOR_ENUM_TO_OPERATOR_TYPE, +} from 'src/explore/constants'; import { DashboardStandaloneMode } from 'src/dashboard/util/constants'; const MAX_URL_LENGTH = 8000; @@ -86,6 +89,10 @@ export function getURIDirectory(endpointType = 'base') { return '/superset/explore/'; } +/** + * This gets the url of the explore page, with all the form data included explicitly. + * This includes any form data overrides from the dashboard. + */ export function getExploreLongUrl( formData, endpointType, @@ -135,6 +142,11 @@ export function getChartDataUri({ path, qs, allowDomainSharding = false }) { return uri; } +/** + * This gets the minimal url for the given form data. + * If there are dashboard overrides present in the form data, + * they will not be included in the url. + */ export function getExploreUrl({ formData, endpointType = 'base', @@ -319,7 +331,10 @@ export const useDebouncedEffect = (effect, delay, deps) => { }; export const getSimpleSQLExpression = (subject, operator, comparator) => { - const isMulti = MULTI_OPERATORS.has(operator); + const isMulti = + [...MULTI_OPERATORS] + .map(op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation) + .indexOf(operator) >= 0; let expression = subject ?? ''; if (subject && operator) { expression += ` ${operator}`; diff --git a/superset-frontend/src/explore/reducers/index.js b/superset-frontend/src/explore/reducers/index.js index 2a92b07d716e..e387c49c18a4 100644 --- a/superset-frontend/src/explore/reducers/index.js +++ b/superset-frontend/src/explore/reducers/index.js @@ -18,6 +18,7 @@ */ import { combineReducers } from 'redux'; +import reports from 'src/reports/reducers/reports'; import charts from '../../chart/chartReducer'; import saveModal from './saveModalReducer'; import explore from './exploreReducer'; @@ -33,4 +34,5 @@ export default combineReducers({ explore, impressionId, messageToasts, + reports, }); diff --git a/superset-frontend/src/filters/components/GroupBy/GroupByFilterPlugin.tsx b/superset-frontend/src/filters/components/GroupBy/GroupByFilterPlugin.tsx index ce67cdc72791..b8452cac4425 100644 --- a/superset-frontend/src/filters/components/GroupBy/GroupByFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/GroupBy/GroupByFilterPlugin.tsx @@ -18,12 +18,11 @@ */ import { ensureIsArray, ExtraFormData, t, tn } from '@superset-ui/core'; import React, { useEffect, useState } from 'react'; -import { Select } from 'src/common/components'; -import { Styles, StyledSelect } from '../common'; +import { FormItemProps } from 'antd/lib/form'; +import { Select } from 'src/components'; +import { FilterPluginStyle, StyledFormItem, StatusMessage } from '../common'; import { PluginFilterGroupByProps } from './types'; -const { Option } = Select; - export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) { const { data, @@ -63,35 +62,60 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) { // so we can process it like this `JSON.stringify` or start to use `Immer` }, [JSON.stringify(defaultValue), multiSelect]); - const columns = data || []; + const groupby = formData?.groupby?.[0]?.length + ? formData?.groupby?.[0] + : null; + + const withData = groupby + ? data.filter(dataItem => + // @ts-ignore + groupby.includes(dataItem.column_name), + ) + : data; + + const columns = data ? withData : []; + const placeholderText = columns.length === 0 ? t('No columns') : tn('%s option', '%s options', columns.length, columns.length); + + const formItemData: FormItemProps = {}; + if (filterState.validateMessage) { + formItemData.extra = ( + + {filterState.validateMessage} + + ); + } + const options = columns.map( + (row: { column_name: string; verbose_name: string | null }) => { + const { column_name: columnName, verbose_name: verboseName } = row; + return { + label: verboseName ?? columnName, + value: columnName, + }; + }, + ); return ( - - + - {columns.map( - (row: { column_name: string; verbose_name: string | null }) => { - const { column_name: columnName, verbose_name: verboseName } = row; - return ( - - ); - }, - )} - - + + + ); } diff --git a/superset-frontend/src/filters/components/Select/buildQuery.test.ts b/superset-frontend/src/filters/components/Select/buildQuery.test.ts index 2ad31b158248..08b7037f9cef 100644 --- a/superset-frontend/src/filters/components/Select/buildQuery.test.ts +++ b/superset-frontend/src/filters/components/Select/buildQuery.test.ts @@ -25,7 +25,7 @@ describe('Select buildQuery', () => { datasource: '5__table', groupby: ['my_col'], viz_type: 'filter_select', - sortAscending: false, + sortAscending: undefined, sortMetric: undefined, filters: undefined, enableEmptyFilter: false, @@ -42,12 +42,12 @@ describe('Select buildQuery', () => { expect(queryContext.queries.length).toEqual(1); const [query] = queryContext.queries; expect(query.groupby).toEqual(['my_col']); - expect(query.filters).toEqual([{ col: 'my_col', op: 'IS NOT NULL' }]); + expect(query.filters).toEqual([]); expect(query.metrics).toEqual([]); expect(query.orderby).toEqual([]); }); - it('should handle sort metric correctly', () => { + it('should sort descending by metric', () => { const queryContext = buildQuery({ ...formData, sortMetric: 'my_metric', @@ -60,6 +60,43 @@ describe('Select buildQuery', () => { expect(query.orderby).toEqual([['my_metric', false]]); }); + it('should sort ascending by metric', () => { + const queryContext = buildQuery({ + ...formData, + sortMetric: 'my_metric', + sortAscending: true, + }); + expect(queryContext.queries.length).toEqual(1); + const [query] = queryContext.queries; + expect(query.groupby).toEqual(['my_col']); + expect(query.metrics).toEqual(['my_metric']); + expect(query.orderby).toEqual([['my_metric', true]]); + }); + + it('should sort ascending by column', () => { + const queryContext = buildQuery({ + ...formData, + sortAscending: true, + }); + expect(queryContext.queries.length).toEqual(1); + const [query] = queryContext.queries; + expect(query.groupby).toEqual(['my_col']); + expect(query.metrics).toEqual([]); + expect(query.orderby).toEqual([['my_col', true]]); + }); + + it('should sort descending by column', () => { + const queryContext = buildQuery({ + ...formData, + sortAscending: false, + }); + expect(queryContext.queries.length).toEqual(1); + const [query] = queryContext.queries; + expect(query.groupby).toEqual(['my_col']); + expect(query.metrics).toEqual([]); + expect(query.orderby).toEqual([['my_col', false]]); + }); + it('should add text search parameter to query filter', () => { const queryContext = buildQuery(formData, { ownState: { diff --git a/superset-frontend/src/filters/components/Select/buildQuery.ts b/superset-frontend/src/filters/components/Select/buildQuery.ts index e442fcd6b09a..a66a855011f3 100644 --- a/superset-frontend/src/filters/components/Select/buildQuery.ts +++ b/superset-frontend/src/filters/components/Select/buildQuery.ts @@ -33,29 +33,28 @@ const buildQuery: BuildQuery = ( const { sortAscending, sortMetric } = { ...DEFAULT_FORM_DATA, ...formData }; return buildQueryContext(formData, baseQueryObject => { const { columns = [], filters = [] } = baseQueryObject; - const extra_filters: QueryObjectFilterClause[] = columns.map(column => { - if (search && coltypeMap[column] === GenericDataType.STRING) { - return { - col: column, - op: 'ILIKE', - val: `%${search}%`, - }; - } - if ( - search && - coltypeMap[column] === GenericDataType.NUMERIC && - !Number.isNaN(Number(search)) - ) { - // for numeric columns we apply a >= where clause - return { - col: column, - op: '>=', - val: Number(search), - }; - } - // if no search is defined, make sure the col value is not null - return { col: column, op: 'IS NOT NULL' }; - }); + const extraFilters: QueryObjectFilterClause[] = []; + if (search) { + columns.forEach(column => { + if (coltypeMap[column] === GenericDataType.STRING) { + extraFilters.push({ + col: column, + op: 'ILIKE', + val: `%${search}%`, + }); + } else if ( + coltypeMap[column] === GenericDataType.NUMERIC && + !Number.isNaN(Number(search)) + ) { + // for numeric columns we apply a >= where clause + extraFilters.push({ + col: column, + op: '>=', + val: Number(search), + }); + } + }); + } const sortColumns = sortMetric ? [sortMetric] : columns; const query: QueryObject[] = [ @@ -63,10 +62,10 @@ const buildQuery: BuildQuery = ( ...baseQueryObject, groupby: columns, metrics: sortMetric ? [sortMetric] : [], - filters: filters.concat(extra_filters), + filters: filters.concat(extraFilters), orderby: - sortMetric || sortAscending - ? sortColumns.map(column => [column, sortAscending]) + sortMetric || sortAscending !== undefined + ? sortColumns.map(column => [column, !!sortAscending]) : [], }, ]; diff --git a/superset-frontend/src/filters/components/Select/controlPanel.ts b/superset-frontend/src/filters/components/Select/controlPanel.ts index 74891b0ed946..c06d73c2dab1 100644 --- a/superset-frontend/src/filters/components/Select/controlPanel.ts +++ b/superset-frontend/src/filters/components/Select/controlPanel.ts @@ -18,6 +18,7 @@ */ import { t, validateNonEmpty } from '@superset-ui/core'; import { ControlPanelConfig, sections } from '@superset-ui/chart-controls'; +import { sharedControls } from '@superset-ui/chart-controls/lib'; import { DEFAULT_FORM_DATA } from './types'; const { @@ -36,7 +37,18 @@ const config: ControlPanelConfig = { { label: t('Query'), expanded: true, - controlSetRows: [['groupby']], + controlSetRows: [ + [ + { + name: 'groupby', + config: { + ...sharedControls.groupby, + label: 'Column', + required: true, + }, + }, + ], + ], }, { label: t('UI Configuration'), @@ -77,8 +89,7 @@ const config: ControlPanelConfig = { default: enableEmptyFilter, renderTrigger: true, description: t( - 'User must select a value for this filter when filter is in single select mode. ' + - 'If selection is empty, an always false filter is emitted.', + 'User must select a value before applying the filter', ), }, }, diff --git a/superset-frontend/src/filters/components/Select/types.ts b/superset-frontend/src/filters/components/Select/types.ts index 36052e8696a9..11e1d1d66fa0 100644 --- a/superset-frontend/src/filters/components/Select/types.ts +++ b/superset-frontend/src/filters/components/Select/types.ts @@ -39,7 +39,7 @@ interface PluginFilterSelectCustomizeProps { defaultToFirstItem: boolean; inputRef?: RefObject; searchAllOptions: boolean; - sortAscending: boolean; + sortAscending?: boolean; sortMetric?: string; } diff --git a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx index 9f6f42df1ee9..2ec290c21ca1 100644 --- a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx @@ -17,66 +17,92 @@ * under the License. */ import { styled } from '@superset-ui/core'; -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl'; +import { NO_TIME_RANGE } from 'src/explore/constants'; import { PluginFilterTimeProps } from './types'; -import { Styles } from '../common'; +import { FilterPluginStyle } from '../common'; -const DEFAULT_VALUE = 'Last week'; - -const TimeFilterStyles = styled(Styles)` - overflow-x: scroll; +const TimeFilterStyles = styled(FilterPluginStyle)` + overflow-x: auto; `; -const ControlContainer = styled.div` - display: inline-block; +const ControlContainer = styled.div<{ + validateStatus?: 'error' | 'warning' | 'info'; +}>` + padding: 2px; + & > span, + & > span:hover { + border: 2px solid transparent; + display: inline-block; + border: ${({ theme, validateStatus }) => + validateStatus && `2px solid ${theme.colors[validateStatus]?.base}`}; + } + &:focus { + & > span { + border: 2px solid + ${({ theme, validateStatus }) => + validateStatus + ? theme.colors[validateStatus]?.base + : theme.colors.primary.base}; + outline: 0; + box-shadow: 0 0 0 2px + ${({ validateStatus }) => + validateStatus + ? 'rgba(224, 67, 85, 12%)' + : 'rgba(32, 167, 201, 0.2)'}; + } + } `; export default function TimeFilterPlugin(props: PluginFilterTimeProps) { const { - formData, setDataMask, setFocusedFilter, unsetFocusedFilter, width, + height, filterState, + formData: { inputRef }, } = props; - const { defaultValue } = formData; - - const [value, setValue] = useState(defaultValue ?? DEFAULT_VALUE); - - const handleTimeRangeChange = (timeRange: string): void => { - setValue(timeRange); + const handleTimeRangeChange = (timeRange?: string): void => { + const isSet = timeRange && timeRange !== NO_TIME_RANGE; setDataMask({ - extraFormData: { - time_range: timeRange, + extraFormData: isSet + ? { + time_range: timeRange, + } + : {}, + filterState: { + value: isSet ? timeRange : undefined, }, - filterState: { value: timeRange }, }); }; useEffect(() => { - handleTimeRangeChange(filterState.value ?? DEFAULT_VALUE); + handleTimeRangeChange(filterState.value); }, [filterState.value]); - useEffect(() => { - handleTimeRangeChange(defaultValue ?? DEFAULT_VALUE); - }, [defaultValue]); - - return ( + return props.formData?.inView ? ( // @ts-ignore - + - ); + ) : null; } diff --git a/superset-frontend/src/filters/components/Time/controlPanel.ts b/superset-frontend/src/filters/components/Time/controlPanel.ts index 1b5f12b9a78c..9fcbe067f878 100644 --- a/superset-frontend/src/filters/components/Time/controlPanel.ts +++ b/superset-frontend/src/filters/components/Time/controlPanel.ts @@ -17,10 +17,47 @@ * under the License. */ import { ControlPanelConfig } from '@superset-ui/chart-controls'; +import { t } from '@superset-ui/core'; +import { sharedControls } from '@superset-ui/chart-controls/lib'; const config: ControlPanelConfig = { // For control input types, see: superset-frontend/src/explore/components/controls/index.js - controlPanelSections: [], + controlPanelSections: [ + { + label: t('Query'), + expanded: true, + controlSetRows: [ + [ + { + name: 'groupby', + config: { + ...sharedControls.groupby, + label: 'Column', + required: true, + }, + }, + ], + ], + }, + { + label: t('UI Configuration'), + expanded: true, + controlSetRows: [ + [ + { + name: 'enableEmptyFilter', + config: { + type: 'CheckboxControl', + label: t('Required'), + default: false, + renderTrigger: true, + description: t('User must select a value for this filter.'), + }, + }, + ], + ], + }, + ], }; export default config; diff --git a/superset-frontend/src/filters/components/TimeColumn/TimeColumnFilterPlugin.tsx b/superset-frontend/src/filters/components/TimeColumn/TimeColumnFilterPlugin.tsx index 08456baa85d3..0f616a6a709b 100644 --- a/superset-frontend/src/filters/components/TimeColumn/TimeColumnFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/TimeColumn/TimeColumnFilterPlugin.tsx @@ -24,12 +24,11 @@ import { tn, } from '@superset-ui/core'; import React, { useEffect, useState } from 'react'; -import { Select } from 'src/common/components'; -import { Styles, StyledSelect } from '../common'; +import { Select } from 'src/components'; +import { FormItemProps } from 'antd/lib/form'; +import { FilterPluginStyle, StyledFormItem, StatusMessage } from '../common'; import { PluginFilterTimeColumnProps } from './types'; -const { Option } = Select; - export default function PluginFilterTimeColumn( props: PluginFilterTimeColumnProps, ) { @@ -63,16 +62,16 @@ export default function PluginFilterTimeColumn( }); }; - useEffect(() => { - handleChange(filterState.value ?? null); - }, [JSON.stringify(filterState.value)]); - useEffect(() => { handleChange(defaultValue ?? null); // I think after Config Modal update some filter it re-creates default value for all other filters // so we can process it like this `JSON.stringify` or start to use `Immer` }, [JSON.stringify(defaultValue)]); + useEffect(() => { + handleChange(filterState.value ?? null); + }, [JSON.stringify(filterState.value)]); + const timeColumns = (data || []).filter( row => row.dtype === GenericDataType.TEMPORAL, ); @@ -81,29 +80,44 @@ export default function PluginFilterTimeColumn( timeColumns.length === 0 ? t('No time columns') : tn('%s option', '%s options', timeColumns.length, timeColumns.length); + + const formItemData: FormItemProps = {}; + if (filterState.validateMessage) { + formItemData.extra = ( + + {filterState.validateMessage} + + ); + } + + const options = timeColumns.map( + (row: { column_name: string; verbose_name: string | null }) => { + const { column_name: columnName, verbose_name: verboseName } = row; + return { + label: verboseName ?? columnName, + value: columnName, + }; + }, + ); + return ( - - + - {timeColumns.map( - (row: { column_name: string; verbose_name: string | null }) => { - const { column_name: columnName, verbose_name: verboseName } = row; - return ( - - ); - }, - )} - - + + + ); } diff --git a/superset-frontend/src/filters/components/TimeGrain/controlPanel.ts b/superset-frontend/src/filters/components/TimeGrain/controlPanel.ts index 5b7bc3dc3bb3..448c0cea69ac 100644 --- a/superset-frontend/src/filters/components/TimeGrain/controlPanel.ts +++ b/superset-frontend/src/filters/components/TimeGrain/controlPanel.ts @@ -17,9 +17,29 @@ * under the License. */ import { ControlPanelConfig } from '@superset-ui/chart-controls'; +import { t } from '@superset-ui/core'; const config: ControlPanelConfig = { - controlPanelSections: [], + controlPanelSections: [ + { + label: t('UI Configuration'), + expanded: true, + controlSetRows: [ + [ + { + name: 'enableEmptyFilter', + config: { + type: 'CheckboxControl', + label: t('Required'), + default: false, + renderTrigger: true, + description: t('User must select a value for this filter.'), + }, + }, + ], + ], + }, + ], }; export default config; diff --git a/superset-frontend/src/filters/components/common.ts b/superset-frontend/src/filters/components/common.ts index 1a57f2ae341b..af1fe9c79176 100644 --- a/superset-frontend/src/filters/components/common.ts +++ b/superset-frontend/src/filters/components/common.ts @@ -17,14 +17,22 @@ * under the License. */ import { styled } from '@superset-ui/core'; -import { Select } from 'src/common/components'; import { PluginFilterStylesProps } from './types'; +import FormItem from '../../components/Form/FormItem'; -export const Styles = styled.div` - height: ${({ height }) => height}px; +export const FilterPluginStyle = styled.div` + min-height: ${({ height }) => height}px; width: ${({ width }) => width}px; `; -export const StyledSelect = styled(Select)` - width: 100%; +export const StyledFormItem = styled(FormItem)` + &.ant-row.ant-form-item { + margin: 0; + } +`; + +export const StatusMessage = styled.div<{ + status?: 'error' | 'warning' | 'info'; +}>` + color: ${({ theme, status = 'error' }) => theme.colors[status]?.base}; `; diff --git a/superset-frontend/src/messageToasts/components/Toast.tsx b/superset-frontend/src/messageToasts/components/Toast.tsx index 9018ed6ae2c6..5f7416aa4dd1 100644 --- a/superset-frontend/src/messageToasts/components/Toast.tsx +++ b/superset-frontend/src/messageToasts/components/Toast.tsx @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { styled } from '@superset-ui/core'; +import { styled, css, SupersetTheme } from '@superset-ui/core'; import cx from 'classnames'; import Interweave from 'interweave'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import Icon, { IconName } from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { ToastType } from 'src/messageToasts/constants'; import { ToastMeta } from '../types'; @@ -34,8 +34,9 @@ const ToastContainer = styled.div` } `; -const StyledIcon = styled(Icon)` - min-width: ${({ theme }) => theme.gridUnit * 5}px; +const StyledIcon = (theme: SupersetTheme) => css` + min-width: ${theme.gridUnit * 5}px; + color: ${theme.colors.grayscale.base}; `; interface ToastPresenterProps { @@ -76,16 +77,17 @@ export default function Toast({ toast, onCloseToast }: ToastPresenterProps) { }; }, [handleClosePress, toast.duration]); - let iconName: IconName = 'circle-check-solid'; let className = 'toast--success'; + let icon = StyledIcon(theme)} />; + if (toast.toastType === ToastType.WARNING) { - iconName = 'warning-solid'; + icon = ; className = 'toast--warning'; } else if (toast.toastType === ToastType.DANGER) { - iconName = 'error-solid'; + icon = ; className = 'toast--danger'; } else if (toast.toastType === ToastType.INFO) { - iconName = 'info-solid'; + icon = ; className = 'toast--info'; } @@ -95,7 +97,7 @@ export default function Toast({ toast, onCloseToast }: ToastPresenterProps) { data-test="toast-container" role="alert" > - + {icon} { + dispatch(setReport(json)); + }) + .catch(() => + dispatch( + addDangerToast( + t( + 'There was an issue fetching reports attached to this dashboard.', + ), + ), + ), + ); + }; +} + +const structureFetchAction = (dispatch, getState) => { + const state = getState(); + const { user, dashboardInfo, charts, explore } = state; + if (dashboardInfo) { + dispatch( + fetchUISpecificReport( + user.userId, + 'dashboard_id', + 'dashboards', + dashboardInfo.id, + ), + ); + } else { + const [chartArr] = Object.keys(charts); + dispatch( + fetchUISpecificReport( + explore.user.userId, + 'chart_id', + 'charts', + charts[chartArr].id, + ), + ); + } +}; + +export const ADD_REPORT = 'ADD_REPORT'; + +export const addReport = report => dispatch => + SupersetClient.post({ + endpoint: `/api/v1/report/`, + jsonPayload: report, + }) + .then(({ json }) => { + dispatch({ type: ADD_REPORT, json }); + dispatch(addSuccessToast(t('The report has been created'))); + }) + .catch(async e => { + const parsedError = await getClientErrorObject(e); + const errorMessage = parsedError.message; + const errorArr = Object.keys(errorMessage); + const error = errorMessage[errorArr[0]]; + dispatch( + addDangerToast( + t('An error occurred while editing this report: %s', error), + ), + ); + }); + +export const EDIT_REPORT = 'EDIT_REPORT'; + +export function editReport(id, report) { + return function (dispatch) { + SupersetClient.put({ + endpoint: `/api/v1/report/${id}`, + jsonPayload: report, + }) + .then(({ json }) => { + dispatch({ type: EDIT_REPORT, json }); + }) + .catch(() => + dispatch( + addDangerToast(t('An error occurred while editing this report.')), + ), + ); + }; +} + +export function toggleActive(report, isActive) { + return function toggleActiveThunk(dispatch) { + return SupersetClient.put({ + endpoint: encodeURI(`/api/v1/report/${report.id}`), + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + active: isActive, + }), + }) + .catch(() => { + dispatch( + addDangerToast( + t('We were unable to active or deactivate this report.'), + ), + ); + }) + .finally(() => { + dispatch(structureFetchAction); + }); + }; +} + +export function deleteActiveReport(report) { + return function deleteActiveReportThunk(dispatch) { + return SupersetClient.delete({ + endpoint: encodeURI(`/api/v1/report/${report.id}`), + }) + .catch(() => { + dispatch(addDangerToast(t('Your report could not be deleted'))); + }) + .finally(() => { + dispatch(structureFetchAction); + dispatch(addSuccessToast(t('Deleted: %s', report.name))); + }); + }; +} diff --git a/superset-frontend/src/dashboard/reducers/datasources.js b/superset-frontend/src/reports/reducers/reports.js similarity index 59% rename from superset-frontend/src/dashboard/reducers/datasources.js rename to superset-frontend/src/reports/reducers/reports.js index 616c3c134ffd..8b582d0d0cc1 100644 --- a/superset-frontend/src/dashboard/reducers/datasources.js +++ b/superset-frontend/src/reports/reducers/reports.js @@ -16,30 +16,39 @@ * specific language governing permissions and limitations * under the License. */ -import { SET_DATASOURCE } from '../actions/datasources'; -import { HYDRATE_DASHBOARD } from '../actions/hydrate'; +/* eslint-disable camelcase */ +import { SET_REPORT, ADD_REPORT, EDIT_REPORT } from '../actions/reports'; -export default function datasourceReducer(datasources = {}, action) { +export default function reportsReducer(state = {}, action) { const actionHandlers = { - [HYDRATE_DASHBOARD]() { - return action.data.datasources; + [SET_REPORT]() { + return { + ...action.report.result.reduce( + (obj, report) => ({ ...obj, [report.id]: report }), + {}, + ), + }; }, - [SET_DATASOURCE]() { - return action.datasource; + [ADD_REPORT]() { + const report = action.json.result; + report.id = action.json.id; + return { + ...state, + [action.json.id]: report, + }; + }, + [EDIT_REPORT]() { + const report = action.json.result; + report.id = action.json.id; + return { + ...state, + [action.json.id]: report, + }; }, }; if (action.type in actionHandlers) { - if (action.key) { - return { - ...datasources, - [action.key]: actionHandlers[action.type]( - datasources[action.key], - action, - ), - }; - } return actionHandlers[action.type](); } - return datasources; + return state; } diff --git a/superset-frontend/src/setup/setupColors.ts b/superset-frontend/src/setup/setupColors.ts index 77b1b69454cb..4068dd0de0d0 100644 --- a/superset-frontend/src/setup/setupColors.ts +++ b/superset-frontend/src/setup/setupColors.ts @@ -58,7 +58,10 @@ export default function setupColors( if (extraSequentialColorSchemes?.length > 0) { extraSequentialColorSchemes.forEach(scheme => { - categoricalSchemeRegistry.registerValue(scheme.id, scheme); + sequentialSchemeRegistry.registerValue( + scheme.id, + new SequentialScheme(scheme), + ); }); } diff --git a/superset-frontend/src/setup/setupErrorMessages.ts b/superset-frontend/src/setup/setupErrorMessages.ts index ed244614d066..a0626c171ce0 100644 --- a/superset-frontend/src/setup/setupErrorMessages.ts +++ b/superset-frontend/src/setup/setupErrorMessages.ts @@ -35,10 +35,18 @@ export default function setupErrorMessages() { ErrorTypeEnum.BACKEND_TIMEOUT_ERROR, TimeoutErrorMessage, ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.DATABASE_NOT_FOUND_ERROR, + DatabaseErrorMessage, + ); errorMessageComponentRegistry.registerValue( ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR, DatabaseErrorMessage, ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.GENERIC_BACKEND_ERROR, + DatabaseErrorMessage, + ); errorMessageComponentRegistry.registerValue( ErrorTypeEnum.COLUMN_DOES_NOT_EXIST_ERROR, DatabaseErrorMessage, @@ -51,10 +59,46 @@ export default function setupErrorMessages() { ErrorTypeEnum.MISSING_TEMPLATE_PARAMS_ERROR, ParameterErrorMessage, ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.INVALID_TEMPLATE_PARAMS_ERROR, + ParameterErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.RESULTS_BACKEND_NOT_CONFIGURED_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.DML_NOT_ALLOWED_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.INVALID_CTAS_QUERY_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.INVALID_CVAS_QUERY_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.QUERY_SECURITY_ACCESS_ERROR, + DatabaseErrorMessage, + ); errorMessageComponentRegistry.registerValue( ErrorTypeEnum.CONNECTION_INVALID_HOSTNAME_ERROR, DatabaseErrorMessage, ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.RESULTS_BACKEND_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.ASYNC_WORKERS_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.SQLLAB_TIMEOUT_ERROR, + DatabaseErrorMessage, + ); errorMessageComponentRegistry.registerValue( ErrorTypeEnum.CONNECTION_PORT_CLOSED_ERROR, DatabaseErrorMessage, @@ -83,6 +127,14 @@ export default function setupErrorMessages() { ErrorTypeEnum.SCHEMA_DOES_NOT_EXIST_ERROR, DatabaseErrorMessage, ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.OBJECT_DOES_NOT_EXIST_ERROR, + DatabaseErrorMessage, + ); + errorMessageComponentRegistry.registerValue( + ErrorTypeEnum.SYNTAX_ERROR, + DatabaseErrorMessage, + ); errorMessageComponentRegistry.registerValue( ErrorTypeEnum.CONNECTION_DATABASE_PERMISSIONS_ERROR, DatabaseErrorMessage, diff --git a/superset-frontend/src/types/Chart.ts b/superset-frontend/src/types/Chart.ts index 02b9025c8387..4c670482b43b 100644 --- a/superset-frontend/src/types/Chart.ts +++ b/superset-frontend/src/types/Chart.ts @@ -47,6 +47,7 @@ export type Slice = { description: string | null; cache_timeout: number | null; form_data?: QueryFormData; + query_context?: object; }; export default Chart; diff --git a/superset-frontend/src/types/Dashboard.ts b/superset-frontend/src/types/Dashboard.ts index 14de6d110c40..96a92d567f12 100644 --- a/superset-frontend/src/types/Dashboard.ts +++ b/superset-frontend/src/types/Dashboard.ts @@ -19,7 +19,7 @@ import Owner from './Owner'; import Role from './Role'; -type Dashboard = { +export interface Dashboard { id: number; slug?: string | null; url: string; @@ -35,6 +35,6 @@ type Dashboard = { charts: string[]; // just chart names, unfortunately... owners: Owner[]; roles: Role[]; -}; +} export default Dashboard; diff --git a/superset-frontend/src/types/react-lines-ellipsis.d.ts b/superset-frontend/src/types/react-lines-ellipsis.d.ts new file mode 100644 index 000000000000..a0772baccfe7 --- /dev/null +++ b/superset-frontend/src/types/react-lines-ellipsis.d.ts @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +declare module 'react-lines-ellipsis' { + interface ReactLinesEllipsisProps { + basedOn?: 'letters' | 'words'; + className?: string; + component?: string; + ellipsis?: string; + isClamped?: () => boolean; + maxLine?: number | string; + onReflow?: ({ clamped, text }: { clamped: boolean; text: string }) => any; + style?: React.CSSProperties; + text?: string; + trimRight?: boolean; + winWidth?: number; + } + + // eslint-disable-next-line react/prefer-stateless-function + class LinesEllipsis extends React.Component { + static defaultProps?: ReactLinesEllipsisProps; + } + + export default LinesEllipsis; +} + +declare module 'react-lines-ellipsis/lib/responsiveHOC' { + export default function responsiveHOC():

( + WrappedComponent: React.ComponentType

, + ) => React.ComponentClass

; +} diff --git a/superset-frontend/src/utils/downloadAsImage.ts b/superset-frontend/src/utils/downloadAsImage.ts index ad23d366e5bf..8ec472f4081f 100644 --- a/superset-frontend/src/utils/downloadAsImage.ts +++ b/superset-frontend/src/utils/downloadAsImage.ts @@ -64,15 +64,23 @@ export default function downloadAsImage( ); } + // Mapbox controls are loaded from different origin, causing CORS error + // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#exceptions + const filter = (node: Element) => { + if (typeof node.className === 'string') { + return ( + node.className !== 'mapboxgl-control-container' && + !node.className.includes('ant-dropdown') + ); + } + return true; + }; + return domToImage .toJpeg(elementToPrint, { quality: 0.95, bgcolor: GRAY_BACKGROUND_COLOR, - // Mapbox controls are loaded from different origin, causing CORS error - // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#exceptions - filter: (node: Element) => - node.className !== 'mapboxgl-control-container', - ...domToImageOptions, + filter, }) .then(dataUrl => { const link = document.createElement('a'); diff --git a/superset-frontend/src/utils/export.ts b/superset-frontend/src/utils/export.ts new file mode 100644 index 000000000000..4f3ea75d7675 --- /dev/null +++ b/superset-frontend/src/utils/export.ts @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import parseCookie from 'src/utils/parseCookie'; +import rison from 'rison'; +import shortid from 'shortid'; + +export default function handleResourceExport( + resource: string, + ids: number[], + done: () => void, + interval = 200, +): void { + const token = shortid.generate(); + const url = `/api/v1/${resource}/export/?q=${rison.encode( + ids, + )}&token=${token}`; + + // create new iframe for export + const iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = url; + document.body.appendChild(iframe); + + const timer = window.setInterval(() => { + const cookie: { [cookieId: string]: string } = parseCookie(); + if (cookie[token] === 'done') { + window.clearInterval(timer); + document.body.removeChild(iframe); + done(); + } + }, interval); +} diff --git a/superset-frontend/src/utils/urlUtils.ts b/superset-frontend/src/utils/urlUtils.ts index ebb3a1df8343..f0d9fa8e6ba6 100644 --- a/superset-frontend/src/utils/urlUtils.ts +++ b/superset-frontend/src/utils/urlUtils.ts @@ -17,15 +17,17 @@ * under the License. */ import { SupersetClient } from '@superset-ui/core'; +import rison from 'rison'; import { getClientErrorObject } from './getClientErrorObject'; import { URL_PARAMS } from '../constants'; -export type UrlParamType = 'string' | 'number' | 'boolean' | 'object'; +export type UrlParamType = 'string' | 'number' | 'boolean' | 'object' | 'rison'; export type UrlParam = typeof URL_PARAMS[keyof typeof URL_PARAMS]; export function getUrlParam(param: UrlParam & { type: 'string' }): string; export function getUrlParam(param: UrlParam & { type: 'number' }): number; export function getUrlParam(param: UrlParam & { type: 'boolean' }): boolean; export function getUrlParam(param: UrlParam & { type: 'object' }): object; +export function getUrlParam(param: UrlParam & { type: 'rison' }): object; export function getUrlParam({ name, type }: UrlParam): unknown { const urlParam = new URLSearchParams(window.location.search).get(name); switch (type) { @@ -53,6 +55,15 @@ export function getUrlParam({ name, type }: UrlParam): unknown { return null; } return urlParam !== 'false' && urlParam !== '0'; + case 'rison': + if (!urlParam) { + return null; + } + try { + return rison.decode(urlParam); + } catch { + return null; + } default: return urlParam; } diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx index e36dd8039833..445a87527f8c 100644 --- a/superset-frontend/src/views/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -32,13 +32,11 @@ import Menu from 'src/components/Menu/Menu'; import FlashProvider from 'src/components/FlashProvider'; import { theme } from 'src/preamble'; import ToastPresenter from 'src/messageToasts/containers/ToastPresenter'; -import setupPlugins from 'src/setup/setupPlugins'; import setupApp from 'src/setup/setupApp'; import { routes, isFrontendRoute } from 'src/views/routes'; import { store } from './store'; setupApp(); -setupPlugins(); const container = document.getElementById('app'); const bootstrap = JSON.parse(container?.getAttribute('data-bootstrap') ?? '{}'); diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx index 25cdd290506b..8575a526a375 100644 --- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx +++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.test.jsx @@ -36,7 +36,7 @@ const mockData = { id: 1, name: 'test report', description: 'test report description', - chart: { id: 1, slice_name: 'test chart' }, + chart: { id: 1, slice_name: 'test chart', viz_type: 'table' }, database: { id: 1, database_name: 'test database' }, sql: 'SELECT NaN', }; @@ -76,7 +76,7 @@ fetchMock.get(dashboardEndpoint, { }); fetchMock.get(chartEndpoint, { - result: [], + result: [{ text: 'table chart', value: 1 }], }); async function mountAndWait(props = mockedProps) { @@ -226,12 +226,12 @@ describe('AlertReportModal', () => { expect(input.props().value).toEqual('SELECT NaN'); }); - it('renders one select element when in report mode', () => { + it('renders two select element when in report mode', () => { expect(wrapper.find(Select)).toExist(); - expect(wrapper.find(Select)).toHaveLength(1); + expect(wrapper.find(Select)).toHaveLength(2); }); - it('renders two select elements when in alert mode', async () => { + it('renders three select elements when in alert mode', async () => { const props = { ...mockedProps, isReport: false, @@ -240,7 +240,7 @@ describe('AlertReportModal', () => { const addWrapper = await mountAndWait(props); expect(addWrapper.find(Select)).toExist(); - expect(addWrapper.find(Select)).toHaveLength(2); + expect(addWrapper.find(Select)).toHaveLength(3); }); it('renders value input element when in alert mode', async () => { @@ -260,6 +260,22 @@ describe('AlertReportModal', () => { expect(wrapper.find(Radio)).toHaveLength(2); }); + it('renders text option for text-based charts', async () => { + const props = { + ...mockedProps, + alert: mockData, + }; + const textWrapper = await mountAndWait(props); + + const chartOption = textWrapper.find('input[value="chart"]'); + act(() => { + chartOption.props().onChange({ target: { value: 'chart' } }); + }); + await waitForComponentToPaint(textWrapper); + + expect(textWrapper.find('input[value="TEXT"]')).toExist(); + }); + it('renders input element for working timeout', () => { expect(wrapper.find('input[name="working_timeout"]')).toExist(); }); diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx index 9ab653da5c04..c39783ebbeaa 100644 --- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx +++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx @@ -17,13 +17,20 @@ * under the License. */ import React, { FunctionComponent, useState, useEffect } from 'react'; -import { styled, t, SupersetClient } from '@superset-ui/core'; +import { + styled, + t, + SupersetClient, + css, + SupersetTheme, +} from '@superset-ui/core'; import rison from 'rison'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { Switch } from 'src/components/Switch'; import Modal from 'src/components/Modal'; +import TimezoneSelector from 'src/components/TimezoneSelector'; import { Radio } from 'src/components/Radio'; import { AsyncSelect, NativeGraySelect as Select } from 'src/components/Select'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; @@ -45,6 +52,12 @@ import { const SELECT_PAGE_SIZE = 2000; // temporary fix for paginated query const TIMEOUT_MIN = 1; +const TEXT_BASED_VISUALIZATION_TYPES = [ + 'pivot_table', + 'pivot_table_v2', + 'table', + 'paired_ttest', +]; type SelectValue = { value: string; @@ -117,6 +130,7 @@ const DEFAULT_WORKING_TIMEOUT = 3600; const DEFAULT_CRON_VALUE = '0 * * * *'; // every hour const DEFAULT_ALERT = { active: true, + creation_method: 'alerts_reports', crontab: DEFAULT_CRON_VALUE, log_retention: DEFAULT_RETENTION, working_timeout: DEFAULT_WORKING_TIMEOUT, @@ -135,8 +149,9 @@ const StyledModal = styled(Modal)` } `; -const StyledIcon = styled(Icon)` - margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0; +const StyledIcon = (theme: SupersetTheme) => css` + margin: auto ${theme.gridUnit * 2}px auto 0; + color: ${theme.colors.grayscale.base}; `; const StyledSectionContainer = styled.div` @@ -344,6 +359,10 @@ const StyledNotificationAddButton = styled.div` } `; +const timezoneHeaderStyle = (theme: SupersetTheme) => css` + margin: ${theme.gridUnit * 3}px 0; +`; + type NotificationAddStatus = 'active' | 'disabled' | 'hidden'; interface NotificationMethodAddProps { @@ -408,6 +427,9 @@ const AlertReportModal: FunctionComponent = ({ const [dashboardOptions, setDashboardOptions] = useState([]); const [chartOptions, setChartOptions] = useState([]); + // Chart metadata + const [chartVizType, setChartVizType] = useState(''); + const isEditMode = alert !== null; const formatOptionEnabled = contentType === 'chart' && @@ -710,6 +732,11 @@ const AlertReportModal: FunctionComponent = ({ return result; }; + const getChartVisualizationType = (chart: SelectValue) => + SupersetClient.get({ + endpoint: `/api/v1/chart/${chart.value}`, + }).then(response => setChartVizType(response.json.result.viz_type)); + // Updating alert/report state const updateAlertState = (name: string, value: any) => { setCurrentAlert(currentAlertData => ({ @@ -762,6 +789,7 @@ const AlertReportModal: FunctionComponent = ({ }; const onChartChange = (chart: SelectValue) => { + getChartVisualizationType(chart); updateAlertState('chart', chart || undefined); updateAlertState('dashboard', null); }; @@ -798,6 +826,10 @@ const AlertReportModal: FunctionComponent = ({ updateAlertState('log_retention', retention); }; + const onTimezoneChange = (timezone: string) => { + updateAlertState('timezone', timezone); + }; + const onContentTypeChange = (event: any) => { const { target } = event; @@ -860,10 +892,7 @@ const AlertReportModal: FunctionComponent = ({ useEffect(() => { if ( isEditMode && - (!currentAlert || - !currentAlert.id || - (alert && alert.id !== currentAlert.id) || - (isHidden && show)) + (!currentAlert?.id || alert?.id !== currentAlert.id || (isHidden && show)) ) { if (alert && alert.id !== null && !loading && !fetchError) { const id = alert.id || 0; @@ -912,6 +941,10 @@ const AlertReportModal: FunctionComponent = ({ setConditionNotNull(resource.validator_type === 'not null'); + if (resource.chart) { + setChartVizType((resource.chart as ChartObject).viz_type); + } + setCurrentAlert({ ...resource, chart: resource.chart @@ -998,9 +1031,9 @@ const AlertReportModal: FunctionComponent = ({ title={

{isEditMode ? ( - + ) : ( - + )} {isEditMode ? t(`Edit ${isReport ? 'Report' : 'Alert'}`) @@ -1077,7 +1110,7 @@ const AlertReportModal: FunctionComponent = ({ = ({ * updateAlertState('crontab', newVal)} /> +
{t('Timezone')}
+
timezoneHeaderStyle(theme)} + > + +

{t('Schedule settings')}

@@ -1243,17 +1284,6 @@ const AlertReportModal: FunctionComponent = ({ {t('Dashboard')} {t('Chart')} - {formatOptionEnabled && ( -
- - {t('Send as PNG')} - {t('Send as CSV')} - -
- )} = ({ cacheOptions onChange={onDashboardChange} /> + {formatOptionEnabled && ( +
+ + {t('Send as PNG')} + {t('Send as CSV')} + {TEXT_BASED_VISUALIZATION_TYPES.includes(chartVizType) && ( + {t('Send as text')} + )} + +
+ )}

{t('Notification method')}

* diff --git a/superset-frontend/src/views/CRUD/alert/components/NotificationMethod.tsx b/superset-frontend/src/views/CRUD/alert/components/NotificationMethod.tsx index 7a43d2d590b3..0e4b1b7e09be 100644 --- a/superset-frontend/src/views/CRUD/alert/components/NotificationMethod.tsx +++ b/superset-frontend/src/views/CRUD/alert/components/NotificationMethod.tsx @@ -17,9 +17,9 @@ * under the License. */ import React, { FunctionComponent, useState } from 'react'; -import { styled, t } from '@superset-ui/core'; +import { styled, t, useTheme } from '@superset-ui/core'; import { NativeGraySelect as Select } from 'src/components/Select'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { StyledInputContainer } from '../AlertReportModal'; const StyledNotificationMethod = styled.div` @@ -74,6 +74,7 @@ export const NotificationMethod: FunctionComponent = ({ const [recipientValue, setRecipientValue] = useState( recipients || '', ); + const theme = useTheme(); if (!setting) { return null; @@ -144,7 +145,7 @@ export const NotificationMethod: FunctionComponent = ({ className="delete-button" onClick={() => onRemove(index)} > - + ) : null}

diff --git a/superset-frontend/src/views/CRUD/alert/components/RecipientIcon.tsx b/superset-frontend/src/views/CRUD/alert/components/RecipientIcon.tsx index 3a8fff81d109..b2e8652c537e 100644 --- a/superset-frontend/src/views/CRUD/alert/components/RecipientIcon.tsx +++ b/superset-frontend/src/views/CRUD/alert/components/RecipientIcon.tsx @@ -16,38 +16,38 @@ * specific language governing permissions and limitations * under the License. */ -import { styled, t } from '@superset-ui/core'; -import React from 'react'; +import { t, SupersetTheme, css } from '@superset-ui/core'; +import React, { ReactElement } from 'react'; import { Tooltip } from 'src/components/Tooltip'; -import Icon, { IconName } from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import { RecipientIconName } from '../types'; -const StyledIcon = styled(Icon)` - color: ${({ theme }) => theme.colors.grayscale.light1}; - margin-right: ${({ theme }) => theme.gridUnit * 2}px; +const StyledIcon = (theme: SupersetTheme) => css` + color: ${theme.colors.grayscale.light1}; + margin-right: ${theme.gridUnit * 2}px; `; export default function RecipientIcon({ type }: { type: string }) { - const recipientIconConfig = { - name: '', + const recipientIconConfig: { icon: null | ReactElement; label: string } = { + icon: null, label: '', }; switch (type) { case RecipientIconName.email: - recipientIconConfig.name = 'email'; + recipientIconConfig.icon = ; recipientIconConfig.label = t(`${RecipientIconName.email}`); break; case RecipientIconName.slack: - recipientIconConfig.name = 'slack'; + recipientIconConfig.icon = ; recipientIconConfig.label = t(`${RecipientIconName.slack}`); break; default: - recipientIconConfig.name = ''; + recipientIconConfig.icon = null; recipientIconConfig.label = ''; } - return recipientIconConfig.name.length ? ( + return recipientIconConfig.icon ? ( - + {recipientIconConfig.icon} ) : null; } diff --git a/superset-frontend/src/views/CRUD/alert/types.ts b/superset-frontend/src/views/CRUD/alert/types.ts index 6df1501222e1..f67a6042dfe8 100644 --- a/superset-frontend/src/views/CRUD/alert/types.ts +++ b/superset-frontend/src/views/CRUD/alert/types.ts @@ -27,6 +27,7 @@ type user = { export type ChartObject = { id: number; slice_name: string; + viz_type: string; }; export type DashboardObject = { @@ -73,8 +74,9 @@ export type AlertObject = { name?: string; owners?: Array; sql?: string; + timezone?: string; recipients?: Array; - report_format?: 'PNG' | 'CSV'; + report_format?: 'PNG' | 'CSV' | 'TEXT'; type?: string; validator_config_json?: { op?: Operator; diff --git a/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx b/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx index a824b59cb3f1..e207da03d5fd 100644 --- a/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx +++ b/superset-frontend/src/views/CRUD/annotation/AnnotationList.tsx @@ -279,6 +279,7 @@ function AnnotationList({ /> refreshData()} diff --git a/superset-frontend/src/views/CRUD/annotation/AnnotationModal.test.jsx b/superset-frontend/src/views/CRUD/annotation/AnnotationModal.test.jsx index e6686d174195..0b2f4cb9a697 100644 --- a/superset-frontend/src/views/CRUD/annotation/AnnotationModal.test.jsx +++ b/superset-frontend/src/views/CRUD/annotation/AnnotationModal.test.jsx @@ -45,6 +45,7 @@ const store = mockStore({}); const mockedProps = { addDangerToast: () => {}, + addSuccessToast: () => {}, annotation: mockData, onAnnotationAdd: jest.fn(() => []), onHide: () => {}, diff --git a/superset-frontend/src/views/CRUD/annotation/AnnotationModal.tsx b/superset-frontend/src/views/CRUD/annotation/AnnotationModal.tsx index f7794968605f..cf0c2411f3ed 100644 --- a/superset-frontend/src/views/CRUD/annotation/AnnotationModal.tsx +++ b/superset-frontend/src/views/CRUD/annotation/AnnotationModal.tsx @@ -21,15 +21,17 @@ import { styled, t } from '@superset-ui/core'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; import { RangePicker } from 'src/components/DatePicker'; import moment from 'moment'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import Modal from 'src/components/Modal'; import withToasts from 'src/messageToasts/enhancers/withToasts'; +import { StyledIcon } from 'src/views/CRUD/utils'; import { JsonEditor } from 'src/components/AsyncAceEditor'; import { AnnotationObject } from './types'; interface AnnotationModalProps { addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; annnotationLayerId: number; annotation?: AnnotationObject | null; onAnnotationAdd?: (annotation?: AnnotationObject) => void; @@ -47,10 +49,6 @@ const StyledJsonEditor = styled(JsonEditor)` border: 1px solid ${({ theme }) => theme.colors.secondary.light2}; `; -const StyledIcon = styled(Icon)` - margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0; -`; - const AnnotationContainer = styled.div` margin-bottom: ${({ theme }) => theme.gridUnit * 5}px; @@ -85,6 +83,7 @@ const AnnotationContainer = styled.div` const AnnotationModal: FunctionComponent = ({ addDangerToast, + addSuccessToast, annnotationLayerId, annotation = null, onAnnotationAdd, @@ -96,7 +95,6 @@ const AnnotationModal: FunctionComponent = ({ currentAnnotation, setCurrentAnnotation, ] = useState(null); - const [isHidden, setIsHidden] = useState(true); const isEditMode = annotation !== null; // annotation fetch logic @@ -122,13 +120,12 @@ const AnnotationModal: FunctionComponent = ({ }); }; - // Functions const hide = () => { - setIsHidden(true); - - // Reset annotation - resetAnnotation(); - + if (isEditMode) { + setCurrentAnnotation(resource); + } else { + resetAnnotation(); + } onHide(); }; @@ -153,6 +150,8 @@ const AnnotationModal: FunctionComponent = ({ } hide(); + + addSuccessToast(t('The annotation has been updated')); }); } } else if (currentAnnotation) { @@ -167,6 +166,8 @@ const AnnotationModal: FunctionComponent = ({ } hide(); + + addSuccessToast(t('The annotation has been saved')); }); } }; @@ -236,7 +237,7 @@ const AnnotationModal: FunctionComponent = ({ (!currentAnnotation || !currentAnnotation.id || (annotation && annotation.id !== currentAnnotation.id) || - (isHidden && show)) + show) ) { if (annotation && annotation.id !== null && !loading) { const id = annotation.id || 0; @@ -245,7 +246,7 @@ const AnnotationModal: FunctionComponent = ({ } } else if ( !isEditMode && - (!currentAnnotation || currentAnnotation.id || (isHidden && show)) + (!currentAnnotation || currentAnnotation.id || show) ) { resetAnnotation(); } @@ -266,11 +267,6 @@ const AnnotationModal: FunctionComponent = ({ currentAnnotation ? currentAnnotation.end_dttm : '', ]); - // Show/hide - if (isHidden && show) { - setIsHidden(false); - } - return ( = ({ title={

{isEditMode ? ( - + ) : ( - + )} {isEditMode ? t('Edit annotation') : t('Add annotation')}

diff --git a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayerModal.tsx b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayerModal.tsx index 675e81b868f0..118f9117a05c 100644 --- a/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayerModal.tsx +++ b/superset-frontend/src/views/CRUD/annotationlayers/AnnotationLayerModal.tsx @@ -20,7 +20,8 @@ import React, { FunctionComponent, useState, useEffect } from 'react'; import { styled, t } from '@superset-ui/core'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; +import { StyledIcon } from 'src/views/CRUD/utils'; import Modal from 'src/components/Modal'; import withToasts from 'src/messageToasts/enhancers/withToasts'; @@ -39,10 +40,6 @@ const StyledAnnotationLayerTitle = styled.div` ${({ theme }) => theme.gridUnit * 4}px auto; `; -const StyledIcon = styled(Icon)` - margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0; -`; - const LayerContainer = styled.div` margin-bottom: ${({ theme }) => theme.gridUnit * 10}px; @@ -234,9 +231,9 @@ const AnnotationLayerModal: FunctionComponent = ({ title={

{isEditMode ? ( - + ) : ( - + )} {isEditMode ? t('Edit annotation layer properties') diff --git a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx index cc4f066f95d1..96a384e5e7f7 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx @@ -17,10 +17,9 @@ * under the License. */ import React from 'react'; -import { t } from '@superset-ui/core'; +import { t, useTheme } from '@superset-ui/core'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; -import Icon from 'src/components/Icon'; import Icons from 'src/components/Icons'; import Chart from 'src/types/Chart'; @@ -29,7 +28,7 @@ import Label from 'src/components/Label'; import { Dropdown, Menu } from 'src/common/components'; import FaveStar from 'src/components/FaveStar'; import FacePile from 'src/components/FacePile'; -import { handleChartDelete, handleBulkChartExport, CardStyles } from '../utils'; +import { handleChartDelete, CardStyles } from '../utils'; interface ChartCardProps { chart: Chart; @@ -45,6 +44,7 @@ interface ChartCardProps { chartFilter?: string; userId?: number; showThumbnails?: boolean; + handleBulkChartExport: (chartsToExport: Chart[]) => void; } export default function ChartCard({ @@ -61,11 +61,13 @@ export default function ChartCard({ favoriteStatus, chartFilter, userId, + handleBulkChartExport, }: ChartCardProps) { const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); + const theme = useTheme(); const menu = ( @@ -132,7 +134,7 @@ export default function ChartCard({ return ( { - if (!bulkSelectEnabled) { + if (!bulkSelectEnabled && chart.url) { window.location.href = chart.url; } }} @@ -148,7 +150,7 @@ export default function ChartCard({ url={bulkSelectEnabled ? undefined : chart.url} imgURL={chart.thumbnail_url || ''} imgFallbackURL="/static/assets/images/chart-card-fallback.svg" - description={t('Last modified %s', chart.changed_on_delta_humanized)} + description={t('Modified %s', chart.changed_on_delta_humanized)} coverLeft={} coverRight={ @@ -166,7 +168,7 @@ export default function ChartCard({ isStarred={favoriteStatus} /> - + } diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.test.jsx b/superset-frontend/src/views/CRUD/chart/ChartList.test.jsx index 00d74033c688..894aa4ec6147 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.test.jsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.test.jsx @@ -142,7 +142,7 @@ describe('ChartList', () => { }); it('renders a table view', async () => { - wrapper.find('[data-test="list-view"]').first().simulate('click'); + wrapper.find('[aria-label="list-view"]').first().simulate('click'); await waitForComponentToPaint(wrapper); expect(wrapper.find('table')).toExist(); }); diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 4f2211ebe728..e4aff8192cca 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -25,11 +25,11 @@ import { import React, { useMemo, useState } from 'react'; import rison from 'rison'; import { uniqBy } from 'lodash'; +import moment from 'moment'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { createErrorHandler, createFetchRelated, - handleBulkChartExport, handleChartDelete, } from 'src/views/CRUD/utils'; import { @@ -37,6 +37,7 @@ import { useFavoriteStatus, useListViewResource, } from 'src/views/CRUD/hooks'; +import handleResourceExport from 'src/utils/export'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; import FaveStar from 'src/components/FaveStar'; @@ -47,6 +48,7 @@ import ListView, { ListViewProps, SelectOption, } from 'src/components/ListView'; +import Loading from 'src/components/Loading'; import { getFromLocalStorage } from 'src/utils/localStorageHelpers'; import withToasts from 'src/messageToasts/enhancers/withToasts'; import PropertiesModal from 'src/explore/components/PropertiesModal'; @@ -156,6 +158,10 @@ function ChartList(props: ChartListProps) { const [importingChart, showImportModal] = useState(false); const [passwordFields, setPasswordFields] = useState([]); + const [preparingExport, setPreparingExport] = useState(false); + + const { userId } = props.user; + const userKey = getFromLocalStorage(userId.toString(), null); const openChartImportModal = () => { showImportModal(true); @@ -177,6 +183,14 @@ function ChartList(props: ChartListProps) { hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; + const handleBulkChartExport = (chartsToExport: Chart[]) => { + const ids = chartsToExport.map(({ id }) => id); + handleResourceExport('chart', ids, () => { + setPreparingExport(false); + }); + setPreparingExport(true); + }; + function handleBulkChartDelete(chartsToDelete: Chart[]) { SupersetClient.delete({ endpoint: `/api/v1/chart/?q=${rison.encode( @@ -259,23 +273,33 @@ function ChartList(props: ChartListProps) { Cell: ({ row: { original: { - changed_by_name: changedByName, + last_saved_by: lastSavedBy, changed_by_url: changedByUrl, }, }, - }: any) => {changedByName}, + }: any) => ( + + {lastSavedBy?.first_name + ? `${lastSavedBy?.first_name} ${lastSavedBy?.last_name}` + : null} + + ), Header: t('Modified by'), - accessor: 'changed_by.first_name', + accessor: 'last_saved_by.first_name', size: 'xl', }, { Cell: ({ row: { - original: { changed_on_delta_humanized: changedOn }, + original: { last_saved_at: lastSavedAt }, }, - }: any) => {changedOn}, + }: any) => ( + + {lastSavedAt ? moment.utc(lastSavedAt).fromNow() : null} + + ), Header: t('Last modified'), - accessor: 'changed_on_delta_humanized', + accessor: 'last_saved_at', size: 'xl', }, { @@ -488,7 +512,7 @@ function ChartList(props: ChartListProps) { ), ), ), - paginate: false, + paginate: true, }, ...(props.user.userId ? [favoritesFilter] : []), { @@ -521,8 +545,6 @@ function ChartList(props: ChartListProps) { ]; function renderCard(chart: Chart) { - const { userId } = props.user; - const userKey = getFromLocalStorage(userId.toString(), null); return ( ); } @@ -630,6 +653,11 @@ function ChartList(props: ChartListProps) { loading={loading} pageSize={PAGE_SIZE} renderCard={renderCard} + showThumbnails={ + userKey + ? userKey.thumbnails + : isFeatureEnabled(FeatureFlag.THUMBNAILS) + } defaultViewMode={ isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW) ? 'card' @@ -653,6 +681,7 @@ function ChartList(props: ChartListProps) { passwordFields={passwordFields} setPasswordFields={setPasswordFields} /> + {preparingExport && } ); } diff --git a/superset-frontend/src/views/CRUD/csstemplates/CssTemplateModal.tsx b/superset-frontend/src/views/CRUD/csstemplates/CssTemplateModal.tsx index 0f80b6703776..4b4baa61f3b8 100644 --- a/superset-frontend/src/views/CRUD/csstemplates/CssTemplateModal.tsx +++ b/superset-frontend/src/views/CRUD/csstemplates/CssTemplateModal.tsx @@ -20,7 +20,8 @@ import React, { FunctionComponent, useState, useEffect } from 'react'; import { styled, t } from '@superset-ui/core'; import { useSingleViewResource } from 'src/views/CRUD/hooks'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; +import { StyledIcon } from 'src/views/CRUD/utils'; import Modal from 'src/components/Modal'; import withToasts from 'src/messageToasts/enhancers/withToasts'; import { CssEditor } from 'src/components/AsyncAceEditor'; @@ -45,10 +46,6 @@ const StyledCssEditor = styled(CssEditor)` border: 1px solid ${({ theme }) => theme.colors.secondary.light2}; `; -const StyledIcon = styled(Icon)` - margin: auto ${({ theme }) => theme.gridUnit * 2}px auto 0; -`; - const TemplateContainer = styled.div` margin-bottom: ${({ theme }) => theme.gridUnit * 10}px; @@ -228,9 +225,9 @@ const CssTemplateModal: FunctionComponent = ({ title={

{isEditMode ? ( - + ) : ( - + )} {isEditMode ? t('Edit CSS template properties') diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx index 93de8ead611d..5f84e41a41d2 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx @@ -18,12 +18,8 @@ */ import React from 'react'; import { Link, useHistory } from 'react-router-dom'; -import { t } from '@superset-ui/core'; -import { - handleDashboardDelete, - handleBulkDashboardExport, - CardStyles, -} from 'src/views/CRUD/utils'; +import { t, useTheme } from '@superset-ui/core'; +import { handleDashboardDelete, CardStyles } from 'src/views/CRUD/utils'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { Dropdown, Menu } from 'src/common/components'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; @@ -49,6 +45,7 @@ interface DashboardCardProps { dashboardFilter?: string; userId?: number; showThumbnails?: boolean; + handleBulkDashboardExport: (dashboardsToExport: Dashboard[]) => void; } function DashboardCard({ @@ -64,12 +61,14 @@ function DashboardCard({ favoriteStatus, saveFavoriteStatus, showThumbnails, + handleBulkDashboardExport, }: DashboardCardProps) { const history = useHistory(); const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = hasPerm('can_read'); + const theme = useTheme(); const menu = ( {canEdit && openDashboardEditModal && ( @@ -160,10 +159,7 @@ function DashboardCard({ linkComponent={Link} imgURL={dashboard.thumbnail_url} imgFallbackURL="/static/assets/images/dashboard-card-fallback.svg" - description={t( - 'Last modified %s', - dashboard.changed_on_delta_humanized, - )} + description={t('Modified %s', dashboard.changed_on_delta_humanized)} coverLeft={} actions={ - + } diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx index 230ccc5e48e8..4bb2043bc7db 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.test.jsx @@ -143,7 +143,7 @@ describe('DashboardList', () => { }); it('renders a table view', async () => { - wrapper.find('[data-test="list-view"]').first().simulate('click'); + wrapper.find('[aria-label="list-view"]').first().simulate('click'); await waitForComponentToPaint(wrapper); expect(wrapper.find('table')).toExist(); }); diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 9831c2640590..9c2bd841c093 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -25,10 +25,11 @@ import { createFetchRelated, createErrorHandler, handleDashboardDelete, - handleBulkDashboardExport, } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import handleResourceExport from 'src/utils/export'; +import Loading from 'src/components/Loading'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; import ListView, { ListViewProps, @@ -123,6 +124,7 @@ function DashboardList(props: DashboardListProps) { const [importingDashboard, showImportModal] = useState(false); const [passwordFields, setPasswordFields] = useState([]); + const [preparingExport, setPreparingExport] = useState(false); const openDashboardImportModal = () => { showImportModal(true); @@ -137,6 +139,9 @@ function DashboardList(props: DashboardListProps) { refreshData(); }; + const { userId } = props.user; + const userKey = getFromLocalStorage(userId.toString(), null); + const canCreate = hasPerm('can_write'); const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); @@ -155,8 +160,28 @@ function DashboardList(props: DashboardListProps) { ({ json = {} }) => { setDashboards( dashboards.map(dashboard => { - if (dashboard.id === json.id) { - return json.result; + if (dashboard.id === json?.result?.id) { + const { + changed_by_name, + changed_by_url, + changed_by, + dashboard_title = '', + slug = '', + json_metadata = '', + changed_on_delta_humanized, + url = '', + } = json.result; + return { + ...dashboard, + changed_by_name, + changed_by_url, + changed_by, + dashboard_title, + slug, + json_metadata, + changed_on_delta_humanized, + url, + }; } return dashboard; }), @@ -170,6 +195,14 @@ function DashboardList(props: DashboardListProps) { ); } + const handleBulkDashboardExport = (dashboardsToExport: Dashboard[]) => { + const ids = dashboardsToExport.map(({ id }) => id); + handleResourceExport('dashboard', ids, () => { + setPreparingExport(false); + }); + setPreparingExport(true); + }; + function handleBulkDashboardDelete(dashboardsToDelete: Dashboard[]) { return SupersetClient.delete({ endpoint: `/api/v1/dashboard/?q=${rison.encode( @@ -468,8 +501,6 @@ function DashboardList(props: DashboardListProps) { ]; function renderCard(dashboard: Dashboard) { - const { userId } = props.user; - const userKey = getFromLocalStorage(userId.toString(), null); return ( ); } @@ -580,6 +612,11 @@ function DashboardList(props: DashboardListProps) { initialSort={initialSort} loading={loading} pageSize={PAGE_SIZE} + showThumbnails={ + userKey + ? userKey.thumbnails + : isFeatureEnabled(FeatureFlag.THUMBNAILS) + } renderCard={renderCard} defaultViewMode={ isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW) @@ -605,6 +642,7 @@ function DashboardList(props: DashboardListProps) { passwordFields={passwordFields} setPasswordFields={setPasswordFields} /> + {preparingExport && } ); } diff --git a/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx b/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx index f4eec7f0cabd..e8d693d679c8 100644 --- a/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx +++ b/superset-frontend/src/views/CRUD/data/components/SyntaxHighlighterCopy/index.tsx @@ -26,7 +26,7 @@ import jsonSyntax from 'react-syntax-highlighter/dist/cjs/languages/hljs/json'; import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github'; import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light'; import { ToastProps } from 'src/messageToasts/enhancers/withToasts'; -import Icon from 'src/components/Icon'; +import Icons from 'src/components/Icons'; import copyTextToClipboard from 'src/utils/copy'; SyntaxHighlighter.registerLanguage('sql', sqlSyntax); @@ -49,6 +49,7 @@ const SyntaxHighlighterWrapper = styled.div` left: 512px; visibility: hidden; margin: -4px; + color: ${({ theme }) => theme.colors.grayscale.base}; } `; @@ -78,9 +79,8 @@ export default function SyntaxHighlighterCopy({ } return ( - { e.preventDefault(); diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx index fda65baa2703..560ab1f5cd68 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx @@ -18,7 +18,7 @@ */ import { SupersetClient, t, styled } from '@superset-ui/core'; import React, { useState, useMemo } from 'react'; -import rison from 'rison'; +import Loading from 'src/components/Loading'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { useListViewResource } from 'src/views/CRUD/hooks'; import { createErrorHandler } from 'src/views/CRUD/utils'; @@ -30,6 +30,7 @@ import Icons from 'src/components/Icons'; import ListView, { FilterOperator, Filters } from 'src/components/ListView'; import { commonMenuData } from 'src/views/CRUD/data/common'; import ImportModelsModal from 'src/components/ImportModal/index'; +import handleResourceExport from 'src/utils/export'; import DatabaseModal from './DatabaseModal'; import { DatabaseObject } from './types'; @@ -97,6 +98,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { ); const [importingDatabase, showImportModal] = useState(false); const [passwordFields, setPasswordFields] = useState([]); + const [preparingExport, setPreparingExport] = useState(false); const openDatabaseImportModal = () => { showImportModal(true); @@ -203,9 +205,14 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { } function handleDatabaseExport(database: DatabaseObject) { - return window.location.assign( - `/api/v1/database/export/?q=${rison.encode([database.id])}`, - ); + if (database.id === undefined) { + return; + } + + handleResourceExport('database', [database.id], () => { + setPreparingExport(false); + }); + setPreparingExport(true); } const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; @@ -236,7 +243,9 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { row: { original: { allow_run_async: allowRunAsync }, }, - }: any) => , + }: { + row: { original: { allow_run_async: boolean } }; + }) => , size: 'sm', }, { @@ -469,6 +478,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { passwordFields={passwordFields} setPasswordFields={setPasswordFields} /> + {preparingExport && } ); } diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx index 5c7c729da589..e2a3f0338daa 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm.tsx @@ -16,16 +16,29 @@ * specific language governing permissions and limitations * under the License. */ -import React, { FormEvent } from 'react'; -import { SupersetTheme, JsonObject } from '@superset-ui/core'; +import React, { FormEvent, useState } from 'react'; +import { SupersetTheme, JsonObject, t } from '@superset-ui/core'; import { InputProps } from 'antd/lib/input'; +import { Switch, Select, Button } from 'src/common/components'; +import InfoTooltip from 'src/components/InfoTooltip'; import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput'; +import FormLabel from 'src/components/Form/FormLabel'; +import { DeleteFilled, CloseOutlined } from '@ant-design/icons'; import { - StyledFormHeader, formScrollableStyles, validatedFormStyles, + CredentialInfoForm, + toggleStyle, + infoTooltip, + StyledFooterButton, + StyledCatalogTable, } from './styles'; -import { DatabaseForm } from '../types'; +import { CatalogObject, DatabaseForm, DatabaseObject } from '../types'; + +enum CredentialInfoOptions { + jsonUpload, + copyPaste, +} export const FormFieldOrder = [ 'host', @@ -34,27 +47,255 @@ export const FormFieldOrder = [ 'username', 'password', 'database_name', + 'credentials_info', + 'catalog', + 'query', + 'encryption', ]; interface FieldPropTypes { required: boolean; + hasTooltip?: boolean; + tooltipText?: (valuse: any) => string; + onParametersChange: (value: any) => string; + onParametersUploadFileChange: (value: any) => string; changeMethods: { onParametersChange: (value: any) => string } & { onChange: (value: any) => string; + } & { + onQueryChange: (value: any) => string; + } & { onParametersUploadFileChange: (value: any) => string } & { + onAddTableCatalog: () => void; + onRemoveTableCatalog: (idx: number) => void; }; validationErrors: JsonObject | null; getValidation: () => void; + db?: DatabaseObject; + isEditMode?: boolean; + sslForced?: boolean; + defaultDBName?: string; + editNewDb?: boolean; } +const CredentialsInfo = ({ + changeMethods, + isEditMode, + db, + editNewDb, +}: FieldPropTypes) => { + const [uploadOption, setUploadOption] = useState( + CredentialInfoOptions.jsonUpload.valueOf(), + ); + const [fileToUpload, setFileToUpload] = useState( + null, + ); + return ( + + {!isEditMode && ( + <> + + {t('How do you want to enter service account credentials?')} + + + + )} + {uploadOption === CredentialInfoOptions.copyPaste || + isEditMode || + editNewDb ? ( +
+ {t('Service Account')} +